30 #include "config_auto.h"
45 static BOOL_VAR(equationdetect_save_bi_image,
false,
"Save input bi image");
46 static BOOL_VAR(equationdetect_save_spt_image,
false,
"Save special character image");
47 static BOOL_VAR(equationdetect_save_seed_image,
false,
"Save the seed image");
48 static BOOL_VAR(equationdetect_save_merged_image,
false,
"Save the merged image");
55 static int SortCPByTopReverse(
const void* p1,
const void* p2) {
56 const ColPartition* cp1 = *static_cast<ColPartition* const*>(p1);
57 const ColPartition* cp2 = *static_cast<ColPartition* const*>(p2);
59 const TBOX &box1(cp1->bounding_box()), &box2(cp2->bounding_box());
60 return box2.
top() - box1.top();
63 static int SortCPByBottom(
const void* p1,
const void* p2) {
64 const ColPartition* cp1 = *static_cast<ColPartition* const*>(p1);
65 const ColPartition* cp2 = *static_cast<ColPartition* const*>(p2);
67 const TBOX &box1(cp1->bounding_box()), &box2(cp2->bounding_box());
68 return box1.
bottom() - box2.bottom();
71 static int SortCPByHeight(
const void* p1,
const void* p2) {
72 const ColPartition* cp1 = *static_cast<ColPartition* const*>(p1);
73 const ColPartition* cp2 = *static_cast<ColPartition* const*>(p2);
75 const TBOX &box1(cp1->bounding_box()), &box2(cp2->bounding_box());
76 return box1.
height() - box2.height();
103 const char* equ_name) {
104 const char* default_name =
"equ";
105 if (equ_name ==
nullptr) {
106 equ_name = default_name;
114 tprintf(
"Warning: equation region detection requested,"
115 " but %s failed to load from %s\n", equ_name, equ_datapath);
132 if (to_block ==
nullptr) {
133 tprintf(
"Warning: input to_block is nullptr!\n");
140 for (
int i = 0; i < blob_lists.
size(); ++i) {
141 BLOBNBOX_IT bbox_it(blob_lists[i]);
142 for (bbox_it.mark_cycle_pt (); !bbox_it.cycled_list();
144 bbox_it.data()->set_special_text_type(
BSTT_NONE);
152 BLOBNBOX *blobnbox,
const int height_th) {
160 BLOB_CHOICE_LIST ratings_equ, ratings_lang;
170 const float x_orig = (box.
left() + box.
right()) / 2.0f, y_orig = box.
bottom();
171 std::unique_ptr<TBLOB> normed_blob(
new TBLOB(*tblob));
172 normed_blob->
Normalize(
nullptr,
nullptr,
nullptr, x_orig, y_orig, scaling, scaling,
182 BLOB_CHOICE *lang_choice =
nullptr, *equ_choice =
nullptr;
183 if (ratings_lang.length() > 0) {
184 BLOB_CHOICE_IT choice_it(&ratings_lang);
185 lang_choice = choice_it.data();
187 if (ratings_equ.length() > 0) {
188 BLOB_CHOICE_IT choice_it(&ratings_equ);
189 equ_choice = choice_it.data();
192 const float lang_score = lang_choice ? lang_choice->
certainty() : -FLT_MAX;
193 const float equ_score = equ_choice ? equ_choice->certainty() : -FLT_MAX;
195 const float kConfScoreTh = -5.0f, kConfDiffTh = 1.8;
198 const float diff = fabs(lang_score - equ_score);
202 if (fmax(lang_score, equ_score) < kConfScoreTh) {
205 }
else if (diff > kConfDiffTh && equ_score > lang_score) {
209 }
else if (lang_choice) {
234 if (ids_to_exclude.
empty()) {
235 static const STRING kCharsToEx[] = {
"'",
"`",
"\"",
"\\",
",",
".",
236 "〈",
"〉",
"《",
"》",
"」",
"「",
""};
238 while (kCharsToEx[i] !=
"") {
242 ids_to_exclude.
sort();
249 static const STRING kDigitsChars =
"|";
265 const int classify_integer_matcher =
278 BLOBNBOX_C_IT bbox_it(part->
boxes());
281 for (bbox_it.mark_cycle_pt (); !bbox_it.cycled_list();
283 if (bbox_it.data()->special_text_type() !=
BSTT_SKIP) {
284 blob_heights.
push_back(bbox_it.data()->bounding_box().height());
288 const int height_th = blob_heights[blob_heights.size() / 2] / 3 * 2;
289 for (bbox_it.mark_cycle_pt (); !bbox_it.cycled_list();
291 if (bbox_it.data()->special_text_type() !=
BSTT_SKIP) {
299 classify_class_pruner);
301 classify_integer_matcher);
303 if (equationdetect_save_spt_image) {
312 BLOBNBOX_C_IT blob_it(part->
boxes());
314 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
318 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
327 BLOBNBOX_C_IT blob_it2 = blob_it;
329 while (!blob_it2.at_last()) {
330 BLOBNBOX* nextblob = blob_it2.forward();
332 if (nextblob_box.
left() >= blob_box.
right()) {
335 const float kWidthR = 0.4, kHeightR = 0.3;
337 yoverlap = blob_box.
y_overlap(nextblob_box);
338 const float widthR = static_cast<float>(
339 std::min(nextblob_box.
width(), blob_box.
width())) /
340 std::max(nextblob_box.
width(), blob_box.
width());
341 const float heightR = static_cast<float>(
345 if (xoverlap && yoverlap && widthR > kWidthR && heightR > kHeightR) {
349 blob_box += nextblob_box;
361 tprintf(
"Warning: lang_tesseract_ is nullptr!\n");
364 if (!part_grid || !best_columns) {
365 tprintf(
"part_grid/best_columns is nullptr!!\n");
375 if (equationdetect_save_bi_image) {
391 if (equationdetect_save_seed_image) {
399 for (
int i = 0; i <
cp_seeds_.size(); ++i) {
407 for (
int i = 0; i < seeds_expanded.
size(); ++i) {
416 if (equationdetect_save_merged_image) {
437 if (parts_to_merge.
empty()) {
443 for (
int i = 0; i < parts_to_merge.
size(); ++i) {
444 ASSERT_HOST(parts_to_merge[i] !=
nullptr && parts_to_merge[i] != part);
445 part->
Absorb(parts_to_merge[i],
nullptr);
452 if (parts_updated.
empty()) {
457 for (
int i = 0; i < parts_updated.
size(); ++i) {
466 ASSERT_HOST(seed !=
nullptr && parts_overlap !=
nullptr);
472 const int kRadNeighborCells = 30;
473 search.StartRadSearch((seed_box.left() + seed_box.right()) / 2,
474 (seed_box.top() + seed_box.bottom()) / 2,
476 search.SetUniqueMode(
true);
481 const float kLargeOverlapTh = 0.95;
482 const float kEquXOverlap = 0.4, kEquYOverlap = 0.5;
483 while ((part =
search.NextRadSearch()) !=
nullptr) {
491 y_overlap_fraction = part_box.y_overlap_fraction(seed_box);
494 if (x_overlap_fraction >= kLargeOverlapTh &&
495 y_overlap_fraction >= kLargeOverlapTh) {
499 if ((x_overlap_fraction > kEquXOverlap && y_overlap_fraction > 0.0) ||
500 (x_overlap_fraction > 0.0 && y_overlap_fraction > kEquYOverlap)) {
526 part_box.left(), part_box.bottom(), &grid_x, &grid_y);
555 const int kTextBlobsTh = 20;
580 indented_texts_left.
sort();
581 texts_foreground_density.
sort();
582 float foreground_density_th = 0.15;
583 if (!texts_foreground_density.
empty()) {
585 foreground_density_th = 0.8 * texts_foreground_density[
586 texts_foreground_density.
size() / 2];
589 for (
int i = 0; i < seeds1.
size(); ++i) {
590 const TBOX& box = seeds1[i]->bounding_box();
603 for (
int i = 0; i < seeds2.
size(); ++i) {
604 if (
CheckForSeed2(indented_texts_left, foreground_density_th, seeds2[i])) {
613 const int pix_height = pixGetHeight(pix_bi);
614 Box* box = boxCreate(tbox.
left(), pix_height - tbox.
top(),
616 Pix *pix_sub = pixClipRectangle(pix_bi, box,
nullptr);
618 pixForegroundFraction(pix_sub, &fract);
619 pixDestroy(&pix_sub);
632 float parts_passed = 0.0;
633 for (
int i = 0; i < sub_boxes.
size(); ++i) {
635 if (density < density_th) {
641 const float kSeedPartRatioTh = 0.3;
642 bool retval = (parts_passed / sub_boxes.
size() >= kSeedPartRatioTh);
657 parts_splitted->
clear();
660 bool found_split =
true;
661 while (found_split) {
663 BLOBNBOX_C_IT box_it(right_part->
boxes());
668 int previous_right = INT32_MIN;
671 for (box_it.mark_cycle_pt(); !box_it.cycled_list(); box_it.forward()) {
672 const TBOX& box = box_it.data()->bounding_box();
673 if (previous_right != INT32_MIN &&
674 box.
left() - previous_right > kThreshold) {
677 const int mid_x = (box.
left() + previous_right) / 2;
679 right_part = left_part->
SplitAt(mid_x);
688 previous_right = std::max(previous_right, static_cast<int>(box.
right()));
700 splitted_boxes->
clear();
712 int previous_right = INT32_MIN;
713 BLOBNBOX_C_IT box_it(part->
boxes());
714 for (box_it.mark_cycle_pt(); !box_it.cycled_list(); box_it.forward()) {
715 const TBOX& box = box_it.data()->bounding_box();
716 if (previous_right != INT32_MIN &&
717 box.
left() - previous_right > kThreshold) {
720 previous_right = INT32_MIN;
722 if (previous_right == INT32_MIN) {
728 previous_right = std::max(previous_right, static_cast<int>(box.
right()));
732 if (previous_right != INT32_MIN) {
739 const float foreground_density_th,
745 if (!indented_texts_left.
empty() &&
761 if (sorted_vec.
empty()) {
764 const int kDistTh = static_cast<int>(roundf(0.03 *
resolution_));
770 while (index >= 0 && abs(val - sorted_vec[index--]) < kDistTh) {
776 while (index < sorted_vec.
size() && sorted_vec[index++] - val < kDistTh) {
807 const int kGapTh = static_cast<int>(roundf(
810 search.SetUniqueMode(
true);
813 for (
int i = 0; i <
cp_seeds_.size(); ++i) {
819 if (left_margin + kMarginDiffTh < right_margin &&
820 left_margin < kMarginDiffTh) {
823 part_box.right(), part_box.top(), part_box.bottom());
824 right_to_left =
false;
825 }
else if (left_margin > cps_cx) {
829 part_box.left(), part_box.top(), part_box.bottom());
830 right_to_left =
true;
836 bool side_neighbor_found =
false;
837 while ((neighbor =
search.NextSideSearch(right_to_left)) !=
nullptr) {
840 part_box.x_gap(neighbor_box) > kGapTh ||
841 !part_box.major_y_overlap(neighbor_box) ||
842 part_box.major_x_overlap(neighbor_box)) {
846 side_neighbor_found =
true;
849 if (!side_neighbor_found) {
854 if (neighbor_box.width() > part_box.width() &&
878 if (prev !=
nullptr) {
880 const TBOX &prev_box = prev->bounding_box();
884 int gap = current_box.
y_gap(prev_box);
885 if (gap < std::min(current_box.
height(), prev_box.
height())) {
894 if (ygaps.
size() < 8) {
900 int spacing = 0,
count;
902 spacing += ygaps[
count];
904 return spacing /
count;
908 const bool top_to_bottom,
const int textparts_linespacing) {
921 for (
int i = 0; i <
cp_seeds_.size(); ++i) {
927 if (
IsInline(!top_to_bottom, textparts_linespacing, part)) {
937 const int textparts_linespacing,
945 const float kYGapRatioTh = 1.0;
948 search.StartVerticalSearch(part_box.left(), part_box.right(),
951 search.StartVerticalSearch(part_box.left(), part_box.right(),
954 search.SetUniqueMode(
true);
955 while ((neighbor =
search.NextVerticalSearch(search_bottom)) !=
nullptr) {
957 if (part_box.y_gap(neighbor_box) > kYGapRatioTh *
958 std::min(part_box.height(), neighbor_box.height())) {
967 const float kHeightRatioTh = 0.5;
968 const int kYGapTh = textparts_linespacing > 0 ?
969 textparts_linespacing + static_cast<int>(roundf(0.02 *
resolution_)):
971 if (part_box.x_overlap(neighbor_box) &&
972 part_box.y_gap(neighbor_box) <= kYGapTh &&
974 static_cast<float>(std::min(part_box.height(), neighbor_box.height())) /
975 std::max(part_box.height(), neighbor_box.height()) > kHeightRatioTh) {
987 const int kSeedMathBlobsCount = 2;
988 const int kSeedMathDigitBlobsCount = 5;
994 math_blobs + digit_blobs <= kSeedMathDigitBlobsCount) {
1002 const float math_density_high,
1003 const float math_density_low,
1009 if (math_digit_density > math_density_high) {
1013 math_digit_density > math_density_low) {
1026 const int kXGapTh = static_cast<int>(roundf(0.5 *
resolution_));
1027 const int kRadiusTh = static_cast<int>(roundf(3.0 *
resolution_));
1028 const int kYGapTh = static_cast<int>(roundf(0.5 *
resolution_));
1033 search.StartRadSearch((part_box.left() + part_box.right()) / 2,
1034 (part_box.top() + part_box.bottom()) / 2, kRadiusTh);
1035 search.SetUniqueMode(
true);
1036 bool left_indented =
false, right_indented =
false;
1037 while ((neighbor =
search.NextRadSearch()) !=
nullptr &&
1038 (!left_indented || !right_indented)) {
1039 if (neighbor == part) {
1044 if (part_box.major_y_overlap(neighbor_box) &&
1045 part_box.x_gap(neighbor_box) < kXGapTh) {
1056 if (!part_box.x_overlap(neighbor_box) || part_box.y_overlap(neighbor_box)) {
1060 if (part_box.y_gap(neighbor_box) < kYGapTh) {
1061 const int left_gap = part_box.left() - neighbor_box.left();
1062 const int right_gap = neighbor_box.right() - part_box.right();
1063 if (left_gap > kXGapTh) {
1064 left_indented =
true;
1066 if (right_gap > kXGapTh) {
1067 right_indented =
true;
1072 if (left_indented && right_indented) {
1075 if (left_indented) {
1078 if (right_indented) {
1085 if (seed ==
nullptr ||
1098 if (parts_to_merge.
empty()) {
1106 for (
int i = 0; i < parts_to_merge.
size(); ++i) {
1111 for (
int j = 0; j <
cp_seeds_.size(); ++j) {
1121 seed->
Absorb(part,
nullptr);
1128 const bool search_left,
1131 ASSERT_HOST(seed !=
nullptr && parts_to_merge !=
nullptr);
1132 const float kYOverlapTh = 0.6;
1133 const int kXGapTh = static_cast<int>(roundf(0.2 *
resolution_));
1137 const int x = search_left ? seed_box.
left() : seed_box.right();
1138 search.StartSideSearch(x, seed_box.bottom(), seed_box.top());
1139 search.SetUniqueMode(
true);
1143 while ((part =
search.NextSideSearch(search_left)) !=
nullptr) {
1148 if (part_box.x_gap(seed_box) > kXGapTh) {
1153 if ((part_box.left() >= seed_box.left() && search_left) ||
1154 (part_box.right() <= seed_box.right() && !search_left)) {
1171 if (part_box.y_overlap_fraction(seed_box) < kYOverlapTh &&
1172 seed_box.y_overlap_fraction(part_box) < kYOverlapTh) {
1184 const bool search_bottom,
1187 ASSERT_HOST(seed !=
nullptr && parts_to_merge !=
nullptr &&
1189 const float kXOverlapTh = 0.4;
1190 const int kYGapTh = static_cast<int>(roundf(0.2 *
resolution_));
1194 const int y = search_bottom ? seed_box.
bottom() : seed_box.top();
1195 search.StartVerticalSearch(
1197 search.SetUniqueMode(
true);
1202 int skipped_min_top = std::numeric_limits<int>::max(), skipped_max_bottom = -1;
1203 while ((part =
search.NextVerticalSearch(search_bottom)) !=
nullptr) {
1209 if (part_box.y_gap(seed_box) > kYGapTh) {
1214 if ((part_box.bottom() >= seed_box.bottom() && search_bottom) ||
1215 (part_box.top() <= seed_box.top() && !search_bottom)) {
1219 bool skip_part =
false;
1232 if (part_box.x_overlap_fraction(seed_box) < kXOverlapTh &&
1233 seed_box.x_overlap_fraction(part_box) < kXOverlapTh) {
1239 if (skipped_min_top > part_box.top()) {
1240 skipped_min_top = part_box.
top();
1242 if (skipped_max_bottom < part_box.bottom()) {
1243 skipped_max_bottom = part_box.bottom();
1258 for (
int i = 0; i < parts.
size(); i++) {
1259 const TBOX& part_box(parts[i]->bounding_box());
1260 if ((search_bottom && part_box.
top() <= skipped_max_bottom) ||
1261 (!search_bottom && part_box.
bottom() >= skipped_min_top)) {
1271 const TBOX& part_box)
const {
1272 const int kXGapTh = static_cast<int>(roundf(0.25 *
resolution_));
1273 const int kYGapTh = static_cast<int>(roundf(0.05 *
resolution_));
1283 part_box.
y_gap(seed_box) > kYGapTh) &&
1285 part_box.
x_gap(seed_box) > kXGapTh)) {
1321 if (text_parts.
empty()) {
1326 text_parts.
sort(&SortCPByHeight);
1327 const TBOX& text_box = text_parts[text_parts.
size() / 2]->bounding_box();
1328 int med_height = text_box.
height();
1329 if (text_parts.
size() % 2 == 0 && text_parts.
size() > 1) {
1330 const TBOX& text_box =
1331 text_parts[text_parts.
size() / 2 - 1]->bounding_box();
1332 med_height = static_cast<int>(roundf(
1333 0.5 * (text_box.
height() + med_height)));
1337 for (
int i = 0; i < text_parts.
size(); ++i) {
1338 const TBOX& text_box(text_parts[i]->bounding_box());
1339 if (text_box.
height() > med_height) {
1350 for (
int j = 0; j < math_blocks.
size(); ++j) {
1352 text_parts[i]->Absorb(math_blocks[j],
nullptr);
1360 ASSERT_HOST(part !=
nullptr && math_blocks !=
nullptr);
1361 math_blocks->
clear();
1365 int y_gaps[2] = {std::numeric_limits<int>::max(), std::numeric_limits<int>::max()};
1367 int neighbors_left = std::numeric_limits<int>::max(), neighbors_right = 0;
1368 for (
int i = 0; i < 2; ++i) {
1371 const TBOX& neighbor_box = neighbors[i]->bounding_box();
1372 y_gaps[i] = neighbor_box.
y_gap(part_box);
1373 if (neighbor_box.
left() < neighbors_left) {
1374 neighbors_left = neighbor_box.
left();
1376 if (neighbor_box.
right() > neighbors_right) {
1377 neighbors_right = neighbor_box.
right();
1381 if (neighbors[0] == neighbors[1]) {
1383 neighbors[1] =
nullptr;
1384 y_gaps[1] = std::numeric_limits<int>::max();
1388 if (part_box.left() < neighbors_left || part_box.right() > neighbors_right) {
1393 int index = y_gaps[0] < y_gaps[1] ? 0 : 1;
1397 math_blocks->
push_back(neighbors[index]);
1406 math_blocks->
push_back(neighbors[index]);
1415 ColPartition *nearest_neighbor =
nullptr, *neighbor =
nullptr;
1416 const int kYGapTh = static_cast<int>(roundf(
resolution_ * 0.5));
1419 search.SetUniqueMode(
true);
1421 int y = search_bottom ? part_box.
bottom() : part_box.top();
1422 search.StartVerticalSearch(part_box.left(), part_box.right(), y);
1423 int min_y_gap = std::numeric_limits<int>::max();
1424 while ((neighbor =
search.NextVerticalSearch(search_bottom)) !=
nullptr) {
1428 const TBOX& neighbor_box(neighbor->bounding_box());
1429 int y_gap = neighbor_box.
y_gap(part_box);
1430 if (y_gap > kYGapTh) {
1433 if (!neighbor_box.major_x_overlap(part_box) ||
1434 (search_bottom && neighbor_box.bottom() > part_box.bottom()) ||
1435 (!search_bottom && neighbor_box.top() < part_box.top())) {
1438 if (y_gap < min_y_gap) {
1440 nearest_neighbor = neighbor;
1444 return nearest_neighbor;
1452 const int kYGapTh = static_cast<int>(roundf(
resolution_ * 0.1));
1457 STRING* image_name)
const {
1460 snprintf(page,
sizeof(page),
"%04d",
page_count_);
1466 pix = pixConvertTo32(pixBi);
1471 BLOBNBOX_C_IT blob_it(part->
boxes());
1472 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
1477 pixWrite(outfile.
c_str(), pix, IFF_TIFF_LZW);
1484 gsearch.StartFullSearch();
1486 while ((part = gsearch.NextFullSearch()) !=
nullptr) {
1488 Box *box = boxCreate(tbox.
left(), pixGetHeight(pix) - tbox.
top(),
1491 pixRenderBoxArb(pix, box, 5, 255, 0, 0);
1493 pixRenderBoxArb(pix, box, 5, 0, 255, 0);
1495 pixRenderBoxArb(pix, box, 5, 0, 0, 255);
1500 pixWrite(outfile.
c_str(), pix, IFF_TIFF_LZW);
1508 tprintf(
"Printing special blobs density values for ColParition (t=%d,b=%d) ",
1509 h - box.top(), h - box.bottom());
1513 auto type = static_cast<BlobSpecialTextType>(i);