38 #include "config_auto.h" 67 "Max number of blobs a big blob can overlap");
70 "Fraction of line spacing for quad");
72 "Fraction of line spacing for outlier");
79 "Factor to expand rows by in expand_rows");
84 "New row made if blob makes row this big");
88 "Min blob height/top to include blob top into xheight stats");
90 "Min pile height to make xheight");
92 "Min pile height to make ascheight");
94 "Min pile height to make descheight");
104 #define MAX_HEIGHT_MODES 12 110 static float MakeRowFromBlobs(
float line_size,
111 BLOBNBOX_IT* blob_it, TO_ROW_IT* row_it) {
113 blob_it->move_to_first();
115 float total_size = 0.0f;
118 for (; !blob_it->empty(); blob_it->forward()) {
119 BLOBNBOX* blob = blob_it->extract();
122 if (row ==
nullptr) {
123 row =
new TO_ROW(blob, top, bottom, line_size);
124 row_it->add_before_then_move(row);
126 row->
add_blob(blob, top, bottom, line_size);
128 total_size += top - bottom;
131 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();
175 float size = MakeRowFromSubBlobs(block, blob_it.data()->cblob(), &row_it);
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->pdblk.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,
297 TO_BLOCK_IT block_it = blocks;
301 for (block_it.mark_cycle_pt (); !block_it.cycled_list ();
302 block_it.forward ()) {
303 POLY_BLOCK* pb = block_it.data()->block->pdblk.poly_block();
304 if (pb !=
nullptr && !pb->
IsText())
306 row_count += block_it.data ()->get_rows ()->length ();
308 TO_ROW_IT row_it(block_it.data()->get_rows());
309 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ())
310 blob_count += row_it.data ()->blob_list ()->length ();
312 if (row_count == 0) {
318 std::vector<float> gradients(blob_count);
320 std::vector<float> errors(blob_count);
323 for (block_it.mark_cycle_pt (); !block_it.cycled_list ();
324 block_it.forward ()) {
325 POLY_BLOCK* pb = block_it.data()->block->pdblk.poly_block();
326 if (pb !=
nullptr && !pb->
IsText())
328 TO_ROW_IT row_it(block_it.data ()->get_rows());
329 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
330 row = row_it.data ();
331 blob_count = row->
blob_list ()->length ();
332 row_err = (int32_t) ceil (row->
line_error ());
336 blob_count /= row_err;
337 for (blob_count /= row_err; blob_count > 0; blob_count--) {
338 gradients[row_index] = row->
line_m ();
345 gradients[row_index] = row->
line_m ();
351 if (row_index == 0) {
353 for (block_it.mark_cycle_pt (); !block_it.cycled_list ();
354 block_it.forward ()) {
355 POLY_BLOCK* pb = block_it.data()->block->pdblk.poly_block();
356 if (pb !=
nullptr && !pb->
IsText())
358 TO_ROW_IT row_it(block_it.data()->get_rows());
359 for (row_it.mark_cycle_pt (); !row_it.cycled_list ();
361 row = row_it.data ();
362 gradients[row_index] = row->
line_m ();
368 row_count = row_index;
370 &gradients[0], row_count);
371 page_m = gradients[row_index];
373 &errors[0], row_count);
374 page_err = errors[row_index];
389 int overlap = std::min(dotbox.
right(), ibox.
right()) -
390 std::max(dotbox.
left(), ibox.
left());
392 (overlap * 2 < ibox.
width() && overlap < dotbox.
width()))
402 const double kHeightFraction = 0.6;
403 double target_height = std::min(dotbox.
bottom(), ibox.
top());
405 target_height *= kHeightFraction;
406 int left_min = dotbox.
left() - dotbox.
width();
407 int middle = (dotbox.
left() + dotbox.
right())/2;
408 int right_max = dotbox.
right() + dotbox.
width();
413 bool found_left =
false;
414 bool found_right =
false;
415 bool in_left =
false;
416 bool in_right =
false;
418 C_OUTLINE_IT o_it = blob->
out_list();
419 for (o_it.mark_cycle_pt(); !o_it.cycled_list(); o_it.forward()) {
423 for (
int step = 0; step < length; pos += outline->
step(step++)) {
426 if (x >= left_min && x < middle && !found_left) {
429 if (y > left_maxy) left_maxy = y;
430 if (y < left_miny) left_miny = y;
432 left_maxy = left_miny = y;
435 }
else if (in_left) {
437 if (left_maxy - left_miny > target_height) {
444 if (x <= right_max && x > middle && !found_right) {
447 if (y > right_maxy) right_maxy = y;
448 if (y < right_miny) right_miny = y;
450 right_maxy = right_miny = y;
453 }
else if (in_right) {
455 if (right_maxy - right_miny > target_height) {
468 TO_ROW_IT row_it = block->
get_rows ();
469 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
470 TO_ROW* row = row_it.data();
474 for (b_it.mark_cycle_pt(); !b_it.cycled_list(); b_it.forward()) {
479 STATS hstats(0, max_height + 1);
480 for (b_it.mark_cycle_pt(); !b_it.cycled_list(); b_it.forward()) {
486 float xheight = hstats.
median();
489 for (b_it.mark_cycle_pt(); !b_it.cycled_list(); b_it.forward()) {
494 if (prev !=
nullptr) {
495 if (dot_of_i(blob, prev, row))
498 if (!b_it.at_last()) {
499 BLOBNBOX* next = b_it.data_relative(1);
500 if (dot_of_i(blob, next, row))
504 delete blob->
cblob();
505 delete b_it.extract();
527 BLOBNBOX_IT blob_it = &block->
blobs;
528 TO_ROW_IT row_it = block->
get_rows ();
530 #ifndef GRAPHICS_DISABLED 547 expand_rows(page_tr, block, gradient, rotation, block_edge, testing_on);
548 blob_it.set_to_list (&block->
blobs);
549 row_it.set_to_list (block->
get_rows ());
550 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ())
551 blob_it.add_list_after (row_it.data ()->blob_list ());
555 blob_it.set_to_list (&block->
blobs);
559 blob_it.set_to_list (&block->
blobs);
587 TO_ROW_IT row_it = block->
get_rows ();
588 BLOBNBOX_IT blob_it = &block->
blobs;
590 if (row_it.length () == 0)
595 min_y = block_box.
bottom () - 1;
596 max_y = block_box.
top () + 1;
597 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
598 line_index = (int32_t) floor (row_it.data ()->intercept ());
599 if (line_index <= min_y)
600 min_y = line_index - 1;
601 if (line_index >= max_y)
602 max_y = line_index + 1;
604 line_count = max_y - min_y + 1;
608 std::vector<int32_t> deltas(line_count);
610 std::vector<int32_t> occupation(line_count);
620 max_y - min_y + 1, &occupation[0], &deltas[0]);
621 #ifndef GRAPHICS_DISABLED 623 draw_occupation(xleft, ybottom, min_y, max_y, &occupation[0], &deltas[0]);
627 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
628 row = row_it.data ();
629 line_index = (int32_t) floor (row->
intercept ());
630 distance = deltas[line_index - min_y];
632 line_index, &row_it, testing_on)) {
633 #ifndef GRAPHICS_DISABLED 638 blob_it.add_list_after (row_it.data ()->blob_list ());
639 delete row_it.extract ();
642 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
643 blob_it.add_list_after (row_it.data ()->blob_list ());
669 tprintf (
"Row at %g(%g), dropout dist=%d,",
673 abs_dist = -distance;
679 if (abs_dist > dist_limit) {
681 tprintf (
" too far - deleting\n");
685 if ((distance < 0 && !row_it->at_last ())
686 || (distance >= 0 && !row_it->at_first ())) {
687 row_offset = row_inc;
689 next_row = row_it->data_relative (row_offset);
690 next_index = (int32_t) floor (next_row->
intercept ());
692 && next_index < line_index
693 && next_index > line_index + distance + distance)
695 && next_index > line_index
696 && next_index < line_index + distance + distance)) {
698 tprintf (
" nearer neighbour (%d) at %g\n",
699 line_index + distance - next_index,
704 else if (next_index == line_index
705 || next_index == line_index + distance + distance) {
708 tprintf (
" equal but more believable at %g (%g/%g)\n",
716 row_offset += row_inc;
718 while ((next_index == line_index
719 || next_index == line_index + distance + distance)
720 && row_offset < row_it->length ());
742 TO_ROW_IT row_it = block->
get_rows ();
747 length = sqrt (gradient * gradient + 1);
748 rotation =
FCOORD (1 / length, -gradient / length);
749 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
750 row = row_it.data ();
752 for (blob_it.mark_cycle_pt (); !blob_it.cycled_list ();
753 blob_it.forward ()) {
754 blob = blob_it.data ();
756 blob_box.
rotate (rotation);
782 TO_ROW_IT row_it = block->
get_rows ();
789 line_count = max_y - min_y + 1;
790 length = sqrt (gradient * gradient + 1);
791 rotation =
FCOORD (1 / length, -gradient / length);
792 for (line_index = 0; line_index < line_count; line_index++)
793 deltas[line_index] = 0;
794 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
795 row = row_it.data ();
797 for (blob_it.mark_cycle_pt (); !blob_it.cycled_list ();
798 blob_it.forward ()) {
799 blob = blob_it.data ();
801 blob_box.
rotate (rotation);
802 int32_t width = blob_box.
right() - blob_box.
left();
803 index = blob_box.
bottom() - min_y;
806 deltas[index] += width;
807 index = blob_box.
top() - min_y;
809 deltas[index] -= width;
812 occupation[0] = deltas[0];
813 for (line_index = 1; line_index < line_count; line_index++)
814 occupation[line_index] = occupation[line_index - 1] + deltas[line_index];
841 if (low_window + high_window < line_count) {
842 for (sum = 0, high_index = 0; high_index < low_window; high_index++)
843 sum += occupation[high_index];
844 for (low_index = 0; low_index < high_window; low_index++, high_index++)
845 sum += occupation[high_index];
846 min_occ = occupation[0];
848 for (test_index = 1; test_index < high_index; test_index++) {
849 if (occupation[test_index] <= min_occ) {
850 min_occ = occupation[test_index];
851 min_index = test_index;
854 for (line_index = 0; line_index < low_window; line_index++)
855 thresholds[line_index] = (sum - min_occ) / divisor + min_occ;
857 for (low_index = 0; high_index < line_count; low_index++, high_index++) {
858 sum -= occupation[low_index];
859 sum += occupation[high_index];
860 if (occupation[high_index] <= min_occ) {
862 min_occ = occupation[high_index];
863 min_index = high_index;
866 if (min_index <= low_index) {
867 min_occ = occupation[low_index + 1];
868 min_index = low_index + 1;
869 for (test_index = low_index + 2; test_index <= high_index;
871 if (occupation[test_index] <= min_occ) {
872 min_occ = occupation[test_index];
874 min_index = test_index;
878 thresholds[line_index++] = (sum - min_occ) / divisor + min_occ;
882 min_occ = occupation[0];
884 for (sum = 0, low_index = 0; low_index < line_count; low_index++) {
885 if (occupation[low_index] < min_occ) {
886 min_occ = occupation[low_index];
887 min_index = low_index;
889 sum += occupation[low_index];
893 for (; line_index < line_count; line_index++)
894 thresholds[line_index] = (sum - min_occ) / divisor + min_occ;
913 int32_t prev_threshold;
915 distance = -line_count;
920 prev_threshold = thresholds[line_index];
922 thresholds[line_index] = distance;
925 while (line_index < line_count
926 && (occupation[line_index] < thresholds[line_index]
927 || occupation[line_index - 1] >= prev_threshold));
928 if (line_index < line_count) {
929 back_index = line_index - 1;
931 while (next_dist < -distance && back_index >= 0) {
932 thresholds[back_index] = next_dist;
940 while (line_index < line_count);
961 float y_bottom, y_top;
965 BLOBNBOX_IT blob_it = &block->
blobs;
966 TO_ROW_IT row_it = block->
get_rows ();
968 #ifndef GRAPHICS_DISABLED 977 if (block->
get_rows ()->length () == 0)
983 if (block->
get_rows ()->length () == 0)
992 row_it.move_to_last ();
994 row = row_it.data ();
995 y_max = row->
max_y ();
996 y_min = row->
min_y ();
1002 if (y_min > y_bottom) {
1004 tprintf(
"Expanding bottom of row at %f from %f to %f\n",
1007 swallowed_row =
true;
1008 while (swallowed_row && !row_it.at_last ()) {
1009 swallowed_row =
false;
1011 test_row = row_it.data_relative (1);
1013 if (test_row->
max_y () > y_bottom) {
1014 if (test_row->
min_y () > y_bottom) {
1018 #ifndef GRAPHICS_DISABLED 1026 blob_it.set_to_list (row->
blob_list ());
1027 blob_it.add_list_after (test_row->
blob_list ());
1029 delete row_it.extract ();
1031 swallowed_row =
true;
1033 else if (test_row->
max_y () < y_min) {
1035 y_bottom = test_row->
max_y ();
1037 tprintf(
"Truncating limit to %f due to touching row at %f\n",
1043 tprintf(
"Not expanding limit beyond %f due to touching row at %f\n",
1050 if (y_max < y_top) {
1052 tprintf(
"Expanding top of row at %f from %f to %f\n",
1054 swallowed_row =
true;
1055 while (swallowed_row && !row_it.at_first ()) {
1056 swallowed_row =
false;
1058 test_row = row_it.data_relative (-1);
1059 if (test_row->
min_y () < y_top) {
1060 if (test_row->
max_y () < y_top) {
1064 blob_it.set_to_list (row->
blob_list ());
1065 #ifndef GRAPHICS_DISABLED 1073 blob_it.add_list_after (test_row->
blob_list ());
1075 delete row_it.extract ();
1077 swallowed_row =
true;
1079 else if (test_row->
min_y () < y_max) {
1081 y_top = test_row->
min_y ();
1083 tprintf(
"Truncating limit to %f due to touching row at %f\n",
1089 tprintf(
"Not expanding limit beyond %f due to touching row at %f\n",
1100 while (!row_it.at_last ());
1116 TO_ROW_IT row_it = block->
get_rows ();
1119 tprintf(
"Adjusting row limits for block(%d,%d)\n",
1122 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
1123 row = row_it.data ();
1126 tprintf(
"Row at %f has min %f, max %f, size %f\n",
1153 TO_ROW_IT row_it = block->
get_rows ();
1155 int16_t rowcount = row_it.length ();
1157 std::vector<TO_ROW*> rows(rowcount);
1160 row_it.move_to_last ();
1162 row = row_it.data ();
1163 if (prev_row !=
nullptr) {
1164 rows[rowcount++] = prev_row;
1167 tprintf (
"Row at %g yields spacing of %g\n",
1173 while (!row_it.at_last ());
1178 tprintf (
"Blob based spacing=(%g,%g), offset=%g",
1183 iqr = rows[row_index]->spacing;
1186 iqr -= rows[row_index]->spacing;
1189 block->
key_row = rows[row_index];
1191 tprintf (
" row based=%g(%g)", rows[row_index]->spacing, iqr);
1195 if (rows[row_index]->spacing < block->line_spacing
1196 && rows[row_index]->spacing > block->
line_size)
1198 block->
line_size = rows[row_index]->spacing;
1200 else if (rows[row_index]->spacing > block->
line_spacing)
1205 if (rows[row_index]->spacing < block->line_spacing)
1206 block->
line_size = rows[row_index]->spacing;
1221 tprintf (
"\nEstimate line size=%g, spacing=%g, offset=%g\n",
1262 int32_t min_height, max_height;
1263 TO_ROW_IT row_it = block->
get_rows();
1264 if (row_it.empty())
return;
1269 STATS row_asc_xheights(min_height, max_height + 1);
1270 STATS row_asc_ascrise(static_cast<int>(min_height * asc_frac_xheight),
1271 static_cast<int>(max_height * asc_frac_xheight) + 1);
1272 int min_desc_height =
static_cast<int>(min_height * desc_frac_xheight);
1273 int max_desc_height =
static_cast<int>(max_height * desc_frac_xheight);
1274 STATS row_asc_descdrop(min_desc_height, max_desc_height + 1);
1275 STATS row_desc_xheights(min_height, max_height + 1);
1276 STATS row_desc_descdrop(min_desc_height, max_desc_height + 1);
1277 STATS row_cap_xheights(min_height, max_height + 1);
1278 STATS row_cap_floating_xheights(min_height, max_height + 1);
1279 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
1280 row = row_it.data();
1288 row_asc_xheights.
add(static_cast<int32_t>(row->
xheight),
1290 row_asc_ascrise.
add(static_cast<int32_t>(row->
ascrise),
1292 row_asc_descdrop.
add(static_cast<int32_t>(-row->
descdrop),
1295 row_desc_xheights.
add(static_cast<int32_t>(row->
xheight),
1297 row_desc_descdrop.
add(static_cast<int32_t>(-row->
descdrop),
1301 &row_cap_xheights, &row_cap_floating_xheights);
1305 float xheight = 0.0;
1306 float ascrise = 0.0;
1307 float descdrop = 0.0;
1311 xheight = row_asc_xheights.
median();
1312 ascrise = row_asc_ascrise.
median();
1313 descdrop = -row_asc_descdrop.
median();
1314 }
else if (row_desc_xheights.
get_total() > 0) {
1316 xheight = row_desc_xheights.
median();
1317 descdrop = -row_desc_descdrop.
median();
1318 }
else if (row_cap_xheights.
get_total() > 0) {
1329 min_height, max_height, &(xheight), &(ascrise));
1337 bool corrected_xheight =
false;
1340 corrected_xheight =
true;
1342 if (corrected_xheight || ascrise <= 0.0) {
1343 ascrise = xheight * asc_frac_xheight;
1345 if (corrected_xheight || descdrop >= 0.0) {
1346 descdrop = -(xheight * desc_frac_xheight);
1351 tprintf(
"Block average xheight=%.4f, ascrise=%.4f, descdrop=%.4f\n",
1352 xheight, ascrise, descdrop);
1355 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
1371 int block_line_size) {
1379 int min_height, max_height;
1381 STATS heights(min_height, max_height + 1);
1382 STATS floating_heights(min_height, max_height + 1);
1384 &heights, &floating_heights);
1390 rotation.
y() == 0.0,
1391 min_height, max_height,
1395 row->
descdrop =
static_cast<float>(
1409 int max_height,
STATS *heights,
STATS *floating_heights) {
1416 if (blob_it.empty())
return;
1417 bool has_rep_chars =
1420 blob = blob_it.data();
1429 top -= gradient * xcentre + row->
parallel_c();
1430 if (top >= min_height && top <= max_height) {
1431 heights->
add(static_cast<int32_t>(floor(top + 0.5)), 1);
1433 floating_heights->
add(static_cast<int32_t>(floor(top + 0.5)), 1);
1441 while (!blob_it.at_first() &&
1442 blob_it.data()->repeated_set() == repeated_set) {
1445 tprintf(
"Skipping repeated char when computing xheight\n");
1450 }
while (!blob_it.at_first());
1470 STATS *heights,
STATS *floating_heights,
bool cap_only,
int min_height,
1471 int max_height,
float *xheight,
float *ascrise) {
1472 int blob_index = heights->
mode();
1473 int blob_count = heights->
pile_count(blob_index);
1475 tprintf(
"min_height=%d, max_height=%d, mode=%d, count=%d, total=%d\n",
1476 min_height, max_height, blob_index, blob_count,
1479 floating_heights->
print();
1481 if (blob_count == 0)
return 0;
1483 bool in_best_pile =
FALSE;
1484 int prev_size = -INT32_MAX;
1488 if (cap_only && mode_count > 1)
1492 tprintf(
"found %d modes: ", mode_count);
1493 for (x = 0; x < mode_count; x++)
tprintf(
"%d ", modes[x]);
1497 for (x = 0; x < mode_count - 1; x++) {
1498 if (modes[x] != prev_size + 1)
1499 in_best_pile =
FALSE;
1500 int modes_x_count = heights->
pile_count(modes[x]) -
1503 (in_best_pile || modes_x_count > best_count)) {
1504 for (
int asc = x + 1; asc < mode_count; asc++) {
1506 static_cast<float>(modes[asc]) / static_cast<float>(modes[x]);
1511 if (modes_x_count > best_count) {
1512 in_best_pile =
true;
1513 best_count = modes_x_count;
1516 tprintf(
"X=%d, asc=%d, count=%d, ratio=%g\n",
1517 modes[x], modes[asc]-modes[x], modes_x_count, ratio);
1519 prev_size = modes[x];
1520 *xheight =
static_cast<float>(modes[x]);
1521 *ascrise =
static_cast<float>(modes[asc] - modes[x]);
1526 if (*xheight == 0) {
1533 if (floating_heights->
get_total() > 0) {
1534 for (x = min_height; x < max_height; ++x) {
1537 blob_index = heights->
mode();
1538 for (x = min_height; x < max_height; ++x) {
1542 *xheight =
static_cast<float>(blob_index);
1544 best_count = heights->
pile_count(blob_index);
1546 tprintf(
"Single mode xheight set to %g\n", *xheight);
1548 tprintf(
"Multi-mode xheight set to %g, asc=%g\n", *xheight, *ascrise);
1566 int xheight_blob_count,
STATS *asc_heights) {
1570 i_min =
static_cast<int>(
1577 int num_potential_asc = 0;
1578 for (
int i = i_min; i <= i_max; ++i) {
1579 num_potential_asc += asc_heights->
pile_count(i);
1581 int32_t min_height =
1583 int32_t max_height =
1589 STATS heights (min_height, max_height + 1);
1590 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
1591 blob = blob_it.data();
1595 height = (gradient * xcentre + row->
parallel_c() -
1597 if (height >= min_height && height <= max_height)
1598 heights.
add(static_cast<int>(floor(height + 0.5)), 1);
1601 int blob_index = heights.
mode();
1602 int blob_count = heights.
pile_count(blob_index);
1603 float total_fraction =
1605 if (static_cast<float>(blob_count + num_potential_asc) <
1606 xheight_blob_count * total_fraction) {
1609 int descdrop = blob_count > 0 ? -blob_index : 0;
1611 tprintf(
"Descdrop: %d (potential ascenders %d, descenders %d)\n",
1612 descdrop, num_potential_asc, blob_count);
1633 int32_t least_count;
1634 int32_t least_index;
1637 src_count = max_height + 1 - min_height;
1639 least_count = INT32_MAX;
1641 for (src_index = 0; src_index < src_count; src_index++) {
1642 pile_count = heights->
pile_count(min_height + src_index);
1643 if (pile_count > 0) {
1644 if (dest_count < maxmodes) {
1645 if (pile_count < least_count) {
1647 least_count = pile_count;
1648 least_index = dest_count;
1650 modes[dest_count++] = min_height + src_index;
1651 }
else if (pile_count >= least_count) {
1652 while (least_index < maxmodes - 1) {
1653 modes[least_index] = modes[least_index + 1];
1658 modes[maxmodes - 1] = min_height + src_index;
1659 if (pile_count == least_count) {
1661 least_index = maxmodes - 1;
1665 for (dest_count = 1; dest_count < maxmodes; dest_count++) {
1666 pile_count = heights->
pile_count(modes[dest_count]);
1667 if (pile_count < least_count) {
1669 least_count = pile_count;
1670 least_index = dest_count;
1688 float ascrise,
float descdrop) {
1691 tprintf(
"correcting row xheight: row->xheight %.4f" 1692 ", row->acrise %.4f row->descdrop %.4f\n",
1695 bool normal_xheight =
1712 (normal_xheight || cap_xheight)) ||
1713 (row_category ==
ROW_UNKNOWN && normal_xheight)) {
1737 if (row->
xheight < xheight + ascrise && row->
xheight > xheight) {
1740 tprintf(
"all caps with irregular xheight\n");
1749 tprintf(
"corrected row->xheight = %.4f, row->acrise = %.4f, row->descdrop" 1754 static int CountOverlaps(
const TBOX& box,
int min_height,
1755 BLOBNBOX_LIST* blobs) {
1757 BLOBNBOX_IT blob_it(blobs);
1758 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
1785 BLOBNBOX_IT blob_it;
1789 TO_ROW_IT row_it = block->
get_rows();
1794 length = sqrt(1 + gradient * gradient);
1795 g_vec =
FCOORD(1 / length, -gradient / length);
1796 blob_rotation =
FCOORD(rotation.
x(), -rotation.
y());
1797 blob_rotation.
rotate(g_vec);
1798 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
1799 row = row_it.data();
1802 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list();
1803 blob_it.forward()) {
1804 blob = blob_it.data();
1812 rotated_blob, static_cast<int16_t>(row->
intercept()),
1813 static_cast<int16_t>(
1817 under_it.add_after_then_move(blob_it.extract());
1819 tprintf(
"Underlined blob at:");
1820 rotated_blob->bounding_box().print();
1824 }
else if (CountOverlaps(blob->
bounding_box(), min_blob_height,
1827 large_it.add_after_then_move(blob_it.extract());
1829 tprintf(
"Large blob overlaps %d blobs at:",
1830 CountOverlaps(blob_box, min_blob_height,
1835 delete rotated_blob;
1853 #ifndef GRAPHICS_DISABLED 1860 BLOBNBOX_IT blob_it;
1861 BLOBNBOX_IT start_it;
1862 TO_ROW_IT row_it = block->
get_rows ();
1864 #ifndef GRAPHICS_DISABLED 1868 blob_rotation =
FCOORD (rotation.
x (), -rotation.
y ());
1869 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
1871 blob_it.set_to_list (row_it.data ()->blob_list ());
1872 for (blob_it.mark_cycle_pt (); !blob_it.cycled_list ();
1873 blob_it.forward ()) {
1874 blob = blob_it.data ();
1887 if (!blob_it.at_last ()) {
1888 nextblob = blob_it.data_relative(1);
1891 blob->
merge(nextblob);
1898 blob->
chop (&start_it, &blob_it,
1904 #ifndef GRAPHICS_DISABLED 1909 for (blob_it.mark_cycle_pt (); !blob_it.cycled_list ();
1910 blob_it.forward ()) {
1911 blob = blob_it.data ();
1913 blob_box.
rotate (rotation);
1916 blob_box.
right (), blob_box.
top ());
1940 #ifndef GRAPHICS_DISABLED 1943 TO_ROW_IT row_it = block->
get_rows ();
1945 row_it.move_to_first ();
1946 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
1947 if (row_it.data ()->blob_list ()->empty ())
1948 delete row_it.extract ();
1952 #ifndef GRAPHICS_DISABLED 1955 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
1957 block_edge, colour, rotation);
1982 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
1983 if (!blob_it.data()->joined_to_prev()) {
1984 const TBOX& box = blob_it.data()->bounding_box();
1992 error = lms.
Fit(&gradient, &c);
2008 #ifndef GRAPHICS_DISABLED 2011 TO_ROW_IT row_it = block->
get_rows ();
2013 row_it.move_to_first ();
2014 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
2015 if (row_it.data ()->blob_list ()->empty ())
2016 delete row_it.extract ();
2021 #ifndef GRAPHICS_DISABLED 2024 for (row_it.mark_cycle_pt (); !row_it.cycled_list ();
2025 row_it.forward ()) {
2026 row_it.data ()->baseline.plot (
to_win, colour);
2033 make_old_baselines(block, testing_on, gradient);
2035 #ifndef GRAPHICS_DISABLED 2038 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
2039 row_it.data ()->baseline.plot (
to_win, colour);
2064 int32_t *xstarts =
new int32_t[row->
blob_list()->length() + 1];
2069 xstarts[1] = xstarts[segments];
2071 coeffs =
new double[3];
2073 coeffs[1] = row->
line_m ();
2074 coeffs[2] = row->
line_c ();
2106 BLOBNBOX_IT blob_it = row->
blob_list ();
2107 BLOBNBOX_IT new_it = blob_it;
2110 needs_curve =
false;
2112 xstarts[0] = box.
left ();
2114 blobcount = row->
blob_list ()->length ();
2116 tprintf (
"Segmenting baseline of %d blobs at (%d,%d)\n",
2120 blob_it.move_to_last ();
2121 box = blob_it.data ()->bounding_box ();
2122 xstarts[1] = box.
right ();
2126 new_it.mark_cycle_pt ();
2129 middle = (new_box.
left () + new_box.
right ()) / 2.0;
2132 yshifts.
add (yshift, blobindex);
2133 if (new_it.cycled_list ()) {
2134 xstarts[1] = new_box.
right ();
2155 xstarts[segments++] = box.
left ();
2161 middle = (new_box.
left () + new_box.
right ()) / 2.0;
2163 yshifts.
add (yshift, blobindex);
2167 while (!new_it.cycled_list ());
2169 xstarts[segments] = new_box.
right ();
2172 xstarts[--segments] = new_box.
right ();
2175 tprintf (
"Made %d segments on row at (%d,%d)\n",
2198 int blobs_per_segment;
2202 BLOBNBOX_IT blob_it = row->
blob_list ();
2203 BLOBNBOX_IT new_it = blob_it;
2209 xstarts[0] = box.
left ();
2211 while (!blob_it.at_first ()) {
2218 blobs_per_segment = blobcount / segments;
2220 double *coeffs =
new double[segments * 3];
2223 (
"Linear splining baseline of %d blobs at (%d,%d), into %d segments of %d blobs\n",
2224 blobcount, box.
left (), box.
bottom (), segments, blobs_per_segment);
2226 for (index2 = 0; index2 < blobs_per_segment / 2; index2++)
2231 blobindex += blobs_per_segment;
2233 while (index1 < blobindex || (segment == segments && index1 < blobcount)) {
2235 int middle = (box.
left() + box.
right()) / 2;
2238 if (index1 == blobindex - blobs_per_segment / 2
2239 || index1 == blobcount - 1) {
2240 xstarts[segment] = box.
left ();
2244 coeffs[segment * 3 - 3] = 0;
2245 coeffs[segment * 3 - 2] = b;
2246 coeffs[segment * 3 - 1] = c;
2248 if (segment > segments)
2251 blobindex += blobs_per_segment;
2253 while (index2 < blobindex || (segment == segments && index2 < blobcount)) {
2255 int middle = (new_box.
left() + new_box.
right()) / 2;
2258 if (index2 == blobindex - blobs_per_segment / 2
2259 || index2 == blobcount - 1) {
2260 xstarts[segment] = new_box.
left ();
2264 coeffs[segment * 3 - 3] = 0;
2265 coeffs[segment * 3 - 2] = b;
2266 coeffs[segment * 3 - 1] = c;
2269 while (segment <= segments);
2291 float g_length = 1.0f;
2296 float smooth_factor;
2301 TO_ROW *dest_row =
nullptr;
2303 BLOBNBOX_IT blob_it = &block->
blobs;
2304 TO_ROW_IT row_it = block->
get_rows ();
2309 if (gradient !=
nullptr)
2310 g_length = sqrt (1 + *gradient * *gradient);
2311 #ifndef GRAPHICS_DISABLED 2317 smooth_factor = 1.0;
2319 row_count = row_it.length ();
2320 if (!blob_it.empty ()) {
2321 left_x = blob_it.data ()->bounding_box ().left ();
2327 for (blob_it.mark_cycle_pt (); !blob_it.cycled_list (); blob_it.forward ()) {
2328 blob = blob_it.data ();
2329 if (gradient !=
nullptr) {
2334 && last_x - left_x > block->
line_size * 2
2338 / (last_x - left_x);
2344 #ifndef GRAPHICS_DISABLED 2348 if (!row_it.empty ()) {
2349 for (row_it.move_to_first ();
2350 !row_it.at_last () && row_it.data ()->min_y () > top;
2352 row = row_it.data ();
2353 if (row->
min_y () <= top && row->
max_y () >= bottom) {
2361 if (overlap_result ==
NEW_ROW && !reject_misses)
2366 if (!make_new_rows) {
2367 near_dist = row_it.data_relative (-1)->min_y () - top;
2369 if (bottom < row->min_y ()) {
2370 if (row->
min_y () - bottom <=
2378 else if (near_dist > 0
2379 && near_dist < bottom - row->max_y ()) {
2381 dest_row = row_it.data ();
2382 if (dest_row->
min_y () - bottom <=
2390 if (top - row->
max_y () <=
2401 if (overlap_result ==
ASSIGN)
2402 dest_row->
add_blob (blob_it.extract (), top, bottom,
2404 if (overlap_result ==
NEW_ROW) {
2405 if (make_new_rows && top - bottom < block->max_blob_size) {
2407 new TO_ROW (blob_it.extract (), top, bottom,
2410 if (bottom > row_it.data ()->min_y ())
2411 row_it.add_before_then_move (dest_row);
2414 row_it.add_after_then_move (dest_row);
2423 else if (make_new_rows && top - bottom < block->max_blob_size) {
2428 row_it.add_after_then_move(dest_row);
2435 if (overlap_result !=
REJECT) {
2436 tprintf(
"Test blob assigned to row at (%g,%g) on pass %d\n",
2440 tprintf(
"Test blob assigned to no row on pass %d\n", pass);
2443 if (overlap_result !=
REJECT) {
2444 while (!row_it.at_first() &&
2445 row_it.data()->min_y() > row_it.data_relative(-1)->min_y()) {
2446 row = row_it.extract();
2448 row_it.add_before_then_move(row);
2450 while (!row_it.at_last() &&
2451 row_it.data ()->min_y() < row_it.data_relative (1)->min_y()) {
2452 row = row_it.extract();
2455 row_it.add_after_then_move(row);
2457 BLOBNBOX_IT added_blob_it(dest_row->
blob_list());
2458 added_blob_it.move_to_last();
2459 TBOX prev_box = added_blob_it.data_relative(-1)->bounding_box();
2460 if (dest_row->
blob_list()->singleton() ||
2462 block_skew = (1 - smooth_factor) * block_skew
2468 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
2469 if (row_it.data()->blob_list()->empty())
2470 delete row_it.extract();
2491 float merge_top, merge_bottom;
2495 BLOBNBOX_IT blob_it;
2498 row = row_it->data ();
2499 bestover = top - bottom;
2500 if (top > row->
max_y ())
2501 bestover -= top - row->
max_y ();
2502 if (bottom < row->min_y ())
2504 bestover -= row->
min_y () - bottom;
2506 tprintf(
"Test blob y=(%g,%g), row=(%f,%f), size=%g, overlap=%f\n",
2507 bottom, top, row->
min_y(), row->
max_y(), rowsize, bestover);
2511 if (!row_it->at_last ()) {
2513 test_row = row_it->data ();
2514 if (test_row->
min_y () <= top && test_row->
max_y () >= bottom) {
2516 test_row->
max_y () >
2519 test_row->
min_y () <
2521 if (merge_top - merge_bottom <= rowsize) {
2523 tprintf (
"Merging rows at (%g,%g), (%g,%g)\n",
2527 test_row->
set_limits (merge_bottom, merge_top);
2528 blob_it.set_to_list (test_row->
blob_list ());
2529 blob_it.add_list_after (row->
blob_list ());
2531 row_it->backward ();
2532 delete row_it->extract ();
2536 overlap = top - bottom;
2537 if (top > test_row->
max_y ())
2538 overlap -= top - test_row->
max_y ();
2539 if (bottom < test_row->min_y ())
2540 overlap -= test_row->
min_y () - bottom;
2541 if (bestover >= rowsize - 1 && overlap >= rowsize - 1) {
2544 if (overlap > bestover) {
2549 tprintf(
"Test blob y=(%g,%g), row=(%f,%f), size=%g, overlap=%f->%f\n",
2550 bottom, top, test_row->
min_y(), test_row->
max_y(),
2551 rowsize, overlap, bestover);
2556 while (!row_it->at_last ()
2557 && test_row->
min_y () <= top && test_row->
max_y () >= bottom);
2558 while (row_it->data () != row)
2559 row_it->backward ();
2577 const void *item2) {
2599 const void *item2) {
2601 const TO_ROW *row1 = *
reinterpret_cast<const TO_ROW* const*
>(item1);
2603 const TO_ROW *row2 = *
reinterpret_cast<const TO_ROW* const*
>(item2);
2621 const void *item2) {
2623 const TO_ROW *row1 = *
reinterpret_cast<const TO_ROW* const*
>(item1);
2625 const TO_ROW *row2 = *
reinterpret_cast<const TO_ROW* const*
>(item2);
2643 int num_repeated_sets = 0;
2644 if (!box_it.empty()) {
2647 int repeat_length = 1;
2650 BLOBNBOX_IT test_it(box_it);
2651 for (test_it.forward(); !test_it.at_first();) {
2652 bblob = test_it.data();
2656 bblob = test_it.data();
2665 num_repeated_sets++;
2666 for (; repeat_length > 0; box_it.forward(), --repeat_length) {
2667 bblob = box_it.data();
2674 }
while (!box_it.at_first());
ROW_CATEGORY get_row_category(const TO_ROW *row)
bool textord_show_parallel_rows
TBOX box_next_pre_chopped(BLOBNBOX_IT *it)
void delete_non_dropout_rows(TO_BLOCK *block, float gradient, FCOORD rotation, int32_t block_edge, bool testing_on)
bool textord_new_initial_xheight
void DrawTo(int x, int y)
void set_repeated_set(int set_id)
double textord_expansion_factor
int32_t pile_count(int32_t value) const
int textord_min_blobs_in_row
double textord_min_linesize
#define BOOL_VAR(name, val, comment)
void rotate(const FCOORD &vec)
OVERLAP_STATE most_overlapping_row(TO_ROW_IT *row_it, TO_ROW *&best_row, float top, float bottom, float rowsize, bool testing_blob)
bool textord_old_baselines
const int kMinLeaderCount
void compute_dropout_distances(int32_t *occupation, int32_t *thresholds, int32_t line_count)
int32_t min_bucket() const
bool textord_show_expanded_rows
void pre_associate_blobs(ICOORD page_tr, TO_BLOCK *block, FCOORD rotation, bool testing_on)
bool textord_single_height_mode
double textord_excess_blobsize
int textord_max_blob_overlaps
#define double_VAR(name, val, comment)
void correct_row_xheight(TO_ROW *row, float xheight, float ascrise, float descdrop)
int num_repeated_sets() const
void expand_rows(ICOORD page_tr, TO_BLOCK *block, float gradient, FCOORD rotation, int32_t block_edge, bool testing_on)
double textord_descx_ratio_max
void add_blob(BLOBNBOX *blob, float top, float bottom, float row_size)
double textord_chop_width
void rotate(const FCOORD vec)
int16_t y() const
access_function
int32_t compute_height_modes(STATS *heights, int32_t min_height, int32_t max_height, int32_t *modes, int32_t maxmodes)
int textord_lms_line_trials
bool within_error_margin(float test, float num, float margin)
int32_t max_bucket() const
void compute_line_occupation(TO_BLOCK *block, float gradient, int32_t min_y, int32_t max_y, int32_t *occupation, int32_t *deltas)
bool textord_fix_xheight_bug
void mark_repeated_chars(TO_ROW *row)
BlobTextFlowType flow() const
double textord_occupancy_threshold
void compute_row_xheight(TO_ROW *row, const FCOORD &rotation, float gradient, int block_line_size)
const ICOORD & start_pos() const
void compute_row_stats(TO_BLOCK *block, bool testing_on)
void make_initial_textrows(ICOORD page_tr, TO_BLOCK *block, FCOORD rotation, bool testing_on)
double textord_spline_shift_fraction
int32_t choose_nth_item(int32_t index, float *array, int32_t count)
bool segment_baseline(TO_ROW *row, TO_BLOCK *block, int32_t &segments, int32_t *xstarts)
double textord_descheight_mode_fraction
bool textord_test_landscape
void merge(BLOBNBOX *nextblob)
int textord_skewsmooth_offset
bool textord_fix_makerow_bug
double textord_spline_outlier_fraction
int textord_spline_medianwin
void SetCursor(int x, int y)
bool textord_parallel_baselines
double textord_min_blob_height_fraction
static const double kDescenderFraction
void set_num_repeated_sets(int num_sets)
double textord_xheight_error_margin
void compute_block_xheight(TO_BLOCK *block, float gradient)
int blob_x_order(const void *item1, const void *item2)
float make_rows(ICOORD page_tr, TO_BLOCK_LIST *port_blocks)
double * linear_spline_baseline(TO_ROW *row, TO_BLOCK *block, int32_t &segments, int32_t xstarts[])
float make_single_row(ICOORD page_tr, bool allow_sub_blobs, TO_BLOCK *block, TO_BLOCK_LIST *blocks)
double textord_linespace_iqrlimit
void set_line(float new_m, float new_c, float new_error)
static const double kXHeightFraction
void compute_occupation_threshold(int32_t low_window, int32_t high_window, int32_t line_count, int32_t *occupation, int32_t *thresholds)
TBOX deskew_block_coords(TO_BLOCK *block, float gradient)
void adjust_row_limits(TO_BLOCK *block)
bool textord_biased_skewcalc
void cleanup_rows_making(ICOORD page_tr, TO_BLOCK *block, float gradient, FCOORD rotation, int32_t block_edge, bool testing_on)
void Add(const ICOORD &pt)
void set_parallel_line(float gradient, float new_c, float new_error)
bool textord_show_final_rows
void assign_blobs_to_rows(TO_BLOCK *block, float *gradient, int pass, bool reject_misses, bool make_new_rows, bool drawing_skew)
int16_t x() const
access function
void set_limits(float new_min, float new_max)
int compute_xheight_from_modes(STATS *heights, STATS *floating_heights, bool cap_only, int min_height, int max_height, float *xheight, float *ascrise)
bool rep_chars_marked() const
bool major_overlap(const TBOX &box) const
double textord_width_limit
bool major_x_overlap(const TBOX &box) const
bool textord_interpolating_skew
void get_min_max_xheight(int block_linesize, int *min_height, int *max_height)
bool joined_to_prev() const
void fit_lms_line(TO_ROW *row)
FCOORD classify_rotation() const
bool textord_show_initial_rows
void separate_underlines(TO_BLOCK *block, float gradient, FCOORD rotation, bool testing_on)
double textord_ascx_ratio_min
int32_t pathlength() const
double Fit(ICOORD *pt1, ICOORD *pt2)
int textord_skewsmooth_offset2
DLLSYM void tprintf(const char *format,...)
float initial_min_y() const
bool textord_show_final_blobs
void chop(BLOBNBOX_IT *start_it, BLOBNBOX_IT *blob_it, FCOORD rotation, float xheight)
double textord_xheight_mode_fraction
void make_baseline_spline(TO_ROW *row, TO_BLOCK *block)
bool test_underline(bool testing_on, C_BLOB *blob, int16_t baseline, int16_t xheight)
void plot_parallel_row(TO_ROW *row, float gradient, int32_t left, ScrollView::Color colour, FCOORD rotation)
void plot_to_row(TO_ROW *row, ScrollView::Color colour, FCOORD rotation)
int textord_spline_minblobs
static C_OUTLINE * deep_copy(const C_OUTLINE *src)
void compute_page_skew(TO_BLOCK_LIST *blocks, float &page_m, float &page_err)
double textord_ascx_ratio_max
void add(int32_t value, int32_t count)
EXTERN ScrollView * to_win
void draw_occupation(int32_t xleft, int32_t ybottom, int32_t min_y, int32_t max_y, int32_t occupation[], int32_t thresholds[])
void make_spline_rows(TO_BLOCK *block, float gradient, bool testing_on)
void fill_heights(TO_ROW *row, float gradient, int min_height, int max_height, STATS *heights, STATS *floating_heights)
float believability() const
C_BLOB * crotate_cblob(C_BLOB *blob, FCOORD rotation)
static const double kAscenderFraction
bool contains(const FCOORD pt) const
C_OUTLINE_LIST * out_list()
static const double kXHeightCapRatio
void fit_parallel_rows(TO_BLOCK *block, float gradient, FCOORD rotation, int32_t block_edge, bool testing_on)
void bounding_box(ICOORD &bottom_left, ICOORD &top_right) const
get box
double textord_underline_width
void vigorous_noise_removal(TO_BLOCK *block)
const TBOX & bounding_box() const
void Rectangle(int x1, int y1, int x2, int y2)
ScrollView * create_to_win(ICOORD page_tr)
BLOBNBOX_LIST large_blobs
void fit_parallel_lms(float gradient, TO_ROW *row)
int row_spacing_order(const void *item1, const void *item2)
double textord_ascheight_mode_fraction
double textord_descx_ratio_min
int32_t compute_row_descdrop(TO_ROW *row, float gradient, int xheight_blob_count, STATS *asc_heights)
double ConstrainedFit(const FCOORD &direction, double min_dist, double max_dist, bool debug, ICOORD *line_pt)
void add(float value, int32_t key)
static C_BLOB * FakeBlob(const TBOX &box)
int row_y_order(const void *item1, const void *item2)
ICOORD step(int index) const
EXTERN bool textord_oldbl_debug
bool textord_straight_baselines
BLOBNBOX_LIST * blob_list()
int32_t get_total() const
#define INT_VAR(name, val, comment)
BLOBNBOX_LIST small_blobs
void CheckInverseFlagAndDirection()
BLOBNBOX_LIST noise_blobs
bool textord_debug_xheights
bool find_best_dropout_row(TO_ROW *row, int32_t distance, float dist_limit, int32_t line_index, TO_ROW_IT *row_it, bool testing_on)