22# include "config_auto.h"
30#ifndef DISABLED_LEGACY_ENGINE
58#ifndef GRAPHICS_DISABLED
59static BOOL_VAR(textord_tabfind_show_initial_partitions,
false,
"Show partition bounds");
60static BOOL_VAR(textord_tabfind_show_reject_blobs,
false,
"Show blobs rejected as noise");
61static INT_VAR(textord_tabfind_show_partitions, 0,
62 "Show partition bounds, waiting if >1 (ScrollView)");
63static BOOL_VAR(textord_tabfind_show_columns,
false,
"Show column bounds (ScrollView)");
64static BOOL_VAR(textord_tabfind_show_blocks,
false,
"Show final block bounds (ScrollView)");
66static BOOL_VAR(textord_tabfind_find_tables,
true,
"run table detection");
68#ifndef GRAPHICS_DISABLED
69ScrollView *ColumnFinder::blocks_win_ =
nullptr;
79 bool cjk_script,
double aligned_gap_fraction, TabVector_LIST *vlines,
80 TabVector_LIST *hlines,
int vertical_x,
int vertical_y)
81 :
TabFind(gridsize, bleft, tright, vlines, vertical_x, vertical_y, resolution)
82 , cjk_script_(cjk_script)
84 , mean_column_gap_(tright.
x() - bleft.
x())
85 , tabfind_aligned_gap_fraction_(aligned_gap_fraction)
88 , rotation_(1.0f, 0.0f)
89 , rerotate_(1.0f, 0.0f)
90 , text_rotation_(0.0f, 0.0f)
91 , best_columns_(nullptr)
92 , stroke_width_(nullptr)
93 , part_grid_(gridsize, bleft, tright)
94 , nontext_map_(nullptr)
95 , projection_(resolution)
97 , equation_detect_(nullptr) {
98 TabVector_IT h_it(&horizontal_lines_);
99 h_it.add_list_after(hlines);
103 for (
auto set : column_sets_) {
106 delete[] best_columns_;
107 delete stroke_width_;
108#ifndef GRAPHICS_DISABLED
109 delete input_blobs_win_;
112 while (denorm_ !=
nullptr) {
113 DENORM *dead_denorm = denorm_;
120 ColPartition_IT part_it(&noise_parts_);
121 for (part_it.mark_cycle_pt(); !part_it.cycled_list(); part_it.forward()) {
128 part_it.set_to_list(&good_parts_);
129 for (part_it.mark_cycle_pt(); !part_it.cycled_list(); part_it.forward()) {
137 BLOBNBOX_IT bb_it(&image_bblobs_);
138 for (bb_it.mark_cycle_pt(); !bb_it.cycled_list(); bb_it.forward()) {
140 delete bblob->
cblob();
154 delete stroke_width_;
158#ifndef GRAPHICS_DISABLED
159 if (textord_tabfind_show_blocks) {
160 input_blobs_win_ =
MakeWindow(0, 0,
"Filtered Input Blobs");
174 stroke_width_->
Clear();
187 BLOBNBOX_CLIST *osd_blobs) {
203 int recognition_rotation) {
204 const FCOORD anticlockwise90(0.0f, 1.0f);
205 const FCOORD clockwise90(0.0f, -1.0f);
206 const FCOORD rotation180(-1.0f, 0.0f);
207 const FCOORD norotation(1.0f, 0.0f);
209 text_rotation_ = norotation;
212 rotation_ = norotation;
213 if (recognition_rotation == 1) {
214 rotation_ = anticlockwise90;
215 }
else if (recognition_rotation == 2) {
216 rotation_ = rotation180;
217 }
else if (recognition_rotation == 3) {
218 rotation_ = clockwise90;
224 if (recognition_rotation & 1) {
225 vertical_text_lines = !vertical_text_lines;
231 if (vertical_text_lines) {
232 rotation_.
rotate(anticlockwise90);
233 text_rotation_.
rotate(clockwise90);
236 rerotate_ =
FCOORD(rotation_.
x(), -rotation_.
y());
237 if (rotation_.
x() != 1.0f || rotation_.
y() != 0.0f) {
252 tprintf(
"Vertical=%d, orientation=%d, final rotation=(%f, %f)+(%f,%f)\n", vertical_text_lines,
253 recognition_rotation, rotation_.
x(), rotation_.
y(), text_rotation_.
x(),
259 denorm_->
SetupNormalization(
nullptr, &rotation_,
nullptr, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f);
289 BLOBNBOX_LIST *diacritic_blobs, TO_BLOCK_LIST *to_blocks) {
290 photo_mask_pix |= nontext_map_;
296 denorm_, cjk_script_, &projection_, diacritic_blobs,
297 &part_grid_, &big_parts_);
300 pixa_debug, &part_grid_, &big_parts_);
303 pixa_debug, &part_grid_, &big_parts_);
309 ColPartition_IT p_it(&big_parts_);
310 for (p_it.mark_cycle_pt(); !p_it.cycled_list(); p_it.forward()) {
311 p_it.data()->DisownBoxesNoAssert();
314 delete stroke_width_;
315 stroke_width_ =
nullptr;
341 ReflectForRtl(input_block, &image_bblobs_);
353 FindTabVectors(&horizontal_lines_, &image_bblobs_, input_block, min_gutter_width_,
354 tabfind_aligned_gap_fraction_, &part_grid_, &deskew_, &reskew_);
356 auto *new_denorm =
new DENORM;
357 new_denorm->
SetupNormalization(
nullptr, &deskew_, denorm_, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
359 denorm_ = new_denorm;
365 if (!MakeColumns(
false)) {
374#ifndef GRAPHICS_DISABLED
375 if (textord_tabfind_show_reject_blobs) {
385 GridSplitPartitions();
389 GridMergePartitions();
392 InsertRemainingNoise(input_block);
394 GridInsertHLinePartitions();
395 GridInsertVLinePartitions();
400#ifndef GRAPHICS_DISABLED
401 if (textord_tabfind_show_initial_partitions) {
408#ifndef DISABLED_LEGACY_ENGINE
409 if (equation_detect_) {
413 if (textord_tabfind_find_tables) {
424 GridRemoveUnderlinePartitions();
434#ifndef GRAPHICS_DISABLED
435 if (textord_tabfind_show_partitions) {
437 if (window !=
nullptr) {
442 if (window !=
nullptr && textord_tabfind_show_partitions > 1) {
453 ReleaseBlobsAndCleanupUnused(input_block);
461 TransformToBlocks(blocks, to_blocks);
464 tprintf(
"Found %d blocks, %d to_blocks\n", blocks->length(), to_blocks->length());
467#ifndef GRAPHICS_DISABLED
468 if (textord_tabfind_show_blocks) {
469 DisplayBlocks(blocks);
472 RotateAndReskewBlocks(input_is_rtl, to_blocks);
474#ifndef GRAPHICS_DISABLED
475 if (blocks_win_ !=
nullptr) {
476 bool waiting =
false;
480 if (event->type ==
SVET_INPUT && event->parameter !=
nullptr) {
481 if (*event->parameter ==
'd') {
487 blocks_win_ =
nullptr;
501 deskew->
set_y(-deskew->
y());
504#ifndef DISABLED_LEGACY_ENGINE
506 equation_detect_ = detect;
512#ifndef GRAPHICS_DISABLED
515void ColumnFinder::DisplayBlocks(BLOCK_LIST *blocks) {
516 if (blocks_win_ ==
nullptr) {
519 blocks_win_->
Clear();
522 BLOCK_IT block_it(blocks);
524 for (block_it.mark_cycle_pt(); !block_it.cycled_list(); block_it.forward()) {
525 BLOCK *block = block_it.data();
526 block->pdblk.plot(blocks_win_, serial++,
535 ScrollView *col_win =
MakeWindow(50, 300,
"Columns");
539 ColPartitionSet *columns = best_columns_[
i];
540 if (columns !=
nullptr) {
550bool ColumnFinder::MakeColumns(
bool single_column) {
555 if (!single_column) {
561 bool good_only =
true;
564 ColPartitionSet *line_set = part_sets.at(
i);
565 if (line_set !=
nullptr && line_set->LegalColumnCandidate()) {
566 ColPartitionSet *column_candidate = line_set->Copy(good_only);
567 if (column_candidate !=
nullptr) {
568 column_candidate->AddToColumnSetsIfUnique(&column_sets_,
WidthCB());
572 good_only = !good_only;
573 }
while (column_sets_.empty() && !good_only);
575 PrintColumnCandidates(
"Column candidates");
578 ImproveColumnCandidates(&column_sets_, &column_sets_);
580 PrintColumnCandidates(
"Improved columns");
583 ImproveColumnCandidates(&part_sets, &column_sets_);
586 if (single_column_set !=
nullptr) {
592 PrintColumnCandidates(
"Final Columns");
594 bool has_columns = !column_sets_.empty();
597 bool any_multi_column = AssignColumns(part_sets);
598#ifndef GRAPHICS_DISABLED
599 if (textord_tabfind_show_columns) {
600 DisplayColumnBounds(&part_sets);
603 ComputeMeanColumnGap(any_multi_column);
605 for (
auto line_set : part_sets) {
606 if (line_set !=
nullptr) {
607 line_set->RelinquishParts();
621 column_sets->clear();
622 if (src_sets == column_sets) {
623 src_sets = &temp_cols;
625 int set_size = temp_cols.size();
627 bool good_only =
true;
629 for (
int i = 0;
i < set_size; ++
i) {
630 ColPartitionSet *column_candidate = temp_cols.at(
i);
632 ColPartitionSet *improved = column_candidate->Copy(good_only);
633 if (improved !=
nullptr) {
634 improved->ImproveColumnCandidate(
WidthCB(), src_sets);
635 improved->AddToColumnSetsIfUnique(column_sets,
WidthCB());
638 good_only = !good_only;
639 }
while (column_sets->empty() && !good_only);
640 if (column_sets->empty()) {
642 *column_sets = temp_cols;
645 for (
auto data : temp_cols) {
652void ColumnFinder::PrintColumnCandidates(
const char *title) {
653 int set_size = column_sets_.size();
654 tprintf(
"Found %d %s:\n", set_size, title);
656 for (
int i = 0;
i < set_size; ++
i) {
657 ColPartitionSet *column_set = column_sets_.at(
i);
672bool ColumnFinder::AssignColumns(
const PartSetVector &part_sets) {
673 int set_count = part_sets.size();
676 best_columns_ =
new ColPartitionSet *[set_count];
677 for (
int y = 0;
y < set_count; ++
y) {
678 best_columns_[
y] =
nullptr;
680 int column_count = column_sets_.size();
690 bool *any_columns_possible =
new bool[set_count];
691 int *assigned_costs =
new int[set_count];
692 int **column_set_costs =
new int *[set_count];
695 for (
int part_i = 0; part_i < set_count; ++part_i) {
696 ColPartitionSet *line_set = part_sets.at(part_i);
697 bool debug = line_set !=
nullptr &&
WithinTestRegion(2, line_set->bounding_box().left(),
698 line_set->bounding_box().bottom());
699 column_set_costs[part_i] =
new int[column_count];
700 any_columns_possible[part_i] =
false;
701 assigned_costs[part_i] = INT32_MAX;
702 for (
int col_i = 0; col_i < column_count; ++col_i) {
703 if (line_set !=
nullptr &&
704 column_sets_.at(col_i)->CompatibleColumns(debug, line_set,
WidthCB())) {
705 column_set_costs[part_i][col_i] = column_sets_.at(col_i)->UnmatchedWidth(line_set);
706 any_columns_possible[part_i] =
true;
708 column_set_costs[part_i][col_i] = INT32_MAX;
710 tprintf(
"Set id %d did not match at y=%d, lineset =%p\n",
711 col_i, part_i,
static_cast<void *
>(line_set));
716 bool any_multi_column =
false;
720 while (BiggestUnassignedRange(set_count, any_columns_possible, &start, &end)) {
722 tprintf(
"Biggest unassigned range = %d- %d\n", start, end);
725 int column_set_id = RangeModalColumnSet(column_set_costs, assigned_costs, start, end);
727 tprintf(
"Range modal column id = %d\n", column_set_id);
728 column_sets_.at(column_set_id)->Print();
731 ShrinkRangeToLongestRun(column_set_costs, assigned_costs, any_columns_possible, column_set_id,
734 tprintf(
"Shrunk range = %d- %d\n", start, end);
739 ExtendRangePastSmallGaps(column_set_costs, assigned_costs, any_columns_possible, column_set_id,
742 ExtendRangePastSmallGaps(column_set_costs, assigned_costs, any_columns_possible, column_set_id,
746 tprintf(
"Column id %d applies to range = %d - %d\n", column_set_id, start, end);
749 AssignColumnToRange(column_set_id, start, end, column_set_costs, assigned_costs);
750 if (column_sets_.at(column_set_id)->GoodColumnCount() > 1) {
751 any_multi_column =
true;
756 if (best_columns_[0] ==
nullptr) {
757 AssignColumnToRange(0, 0,
gridheight_, column_set_costs, assigned_costs);
760 for (
int i = 0;
i < set_count; ++
i) {
761 delete[] column_set_costs[
i];
763 delete[] assigned_costs;
764 delete[] any_columns_possible;
765 delete[] column_set_costs;
766 return any_multi_column;
771bool ColumnFinder::BiggestUnassignedRange(
int set_count,
const bool *any_columns_possible,
772 int *best_start,
int *best_end) {
773 int best_range_size = 0;
774 *best_start = set_count;
775 *best_end = set_count;
777 for (
int start = 0; start <
gridheight_; start = end) {
779 while (start < set_count) {
780 if (best_columns_[start] ==
nullptr && any_columns_possible[start]) {
788 while (end < set_count) {
789 if (best_columns_[end] !=
nullptr) {
792 if (any_columns_possible[end]) {
797 if (start < set_count && range_size > best_range_size) {
798 best_range_size = range_size;
803 return *best_start < *best_end;
807int ColumnFinder::RangeModalColumnSet(
int **column_set_costs,
const int *assigned_costs,
int start,
809 int column_count = column_sets_.size();
810 STATS column_stats(0, column_count - 1);
811 for (
int part_i = start; part_i < end; ++part_i) {
812 for (
int col_j = 0; col_j < column_count; ++col_j) {
813 if (column_set_costs[part_i][col_j] < assigned_costs[part_i]) {
814 column_stats.add(col_j, 1);
819 return column_stats.mode();
826void ColumnFinder::ShrinkRangeToLongestRun(
int **column_set_costs,
const int *assigned_costs,
827 const bool *any_columns_possible,
int column_set_id,
828 int *best_start,
int *best_end) {
830 int orig_start = *best_start;
831 int orig_end = *best_end;
832 int best_range_size = 0;
833 *best_start = orig_end;
834 *best_end = orig_end;
836 for (
int start = orig_start; start < orig_end; start = end) {
838 while (start < orig_end) {
839 if (column_set_costs[start][column_set_id] < assigned_costs[start] ||
840 !any_columns_possible[start]) {
847 while (end < orig_end) {
848 if (column_set_costs[end][column_set_id] >= assigned_costs[start] &&
849 any_columns_possible[end]) {
854 if (start < orig_end && end - start > best_range_size) {
855 best_range_size = end - start;
865void ColumnFinder::ExtendRangePastSmallGaps(
int **column_set_costs,
const int *assigned_costs,
866 const bool *any_columns_possible,
int column_set_id,
867 int step,
int end,
int *start) {
869 tprintf(
"Starting expansion at %d, step=%d, limit=%d\n", *start, step, end);
875 int barrier_size = 0;
881 for (
i = *start + step;
i != end;
i += step) {
882 if (column_set_costs[
i][column_set_id] < assigned_costs[
i]) {
886 if (any_columns_possible[
i]) {
891 tprintf(
"At %d, Barrier size=%d\n",
i, barrier_size);
903 for (
i += step;
i != end;
i += step) {
904 if (column_set_costs[
i][column_set_id] < assigned_costs[
i]) {
906 }
else if (any_columns_possible[
i]) {
911 tprintf(
"At %d, good size = %d\n",
i, good_size);
914 if (good_size >= barrier_size) {
917 }
while (good_size >= barrier_size);
921void ColumnFinder::AssignColumnToRange(
int column_set_id,
int start,
int end,
922 int **column_set_costs,
int *assigned_costs) {
923 ColPartitionSet *column_set = column_sets_.at(column_set_id);
924 for (
int i = start;
i < end; ++
i) {
925 assigned_costs[
i] = column_set_costs[
i][column_set_id];
926 best_columns_[
i] = column_set;
931void ColumnFinder::ComputeMeanColumnGap(
bool any_multi_column) {
935 int width_samples = 0;
941 mean_column_gap_ = any_multi_column && gap_samples > 0
942 ? total_gap / gap_samples
943 : width_samples > 0 ? total_width / width_samples : 0;
951static void ReleaseAllBlobsAndDeleteUnused(BLOBNBOX_LIST *blobs) {
952 for (BLOBNBOX_IT blob_it(blobs); !blob_it.empty(); blob_it.forward()) {
953 BLOBNBOX *blob = blob_it.extract();
954 if (blob->owner() ==
nullptr) {
963void ColumnFinder::ReleaseBlobsAndCleanupUnused(TO_BLOCK *block) {
964 ReleaseAllBlobsAndDeleteUnused(&block->blobs);
965 ReleaseAllBlobsAndDeleteUnused(&block->small_blobs);
966 ReleaseAllBlobsAndDeleteUnused(&block->noise_blobs);
967 ReleaseAllBlobsAndDeleteUnused(&block->large_blobs);
968 ReleaseAllBlobsAndDeleteUnused(&image_bblobs_);
972void ColumnFinder::GridSplitPartitions() {
974 GridSearch<ColPartition, ColPartition_CLIST, ColPartition_C_IT> gsearch(&part_grid_);
975 gsearch.StartFullSearch();
976 ColPartition *dont_repeat =
nullptr;
978 while ((part = gsearch.NextFullSearch()) !=
nullptr) {
979 if (part->blob_type() <
BRT_UNKNOWN || part == dont_repeat) {
982 ColPartitionSet *column_set = best_columns_[gsearch.GridY()];
986 part->ColumnRange(
resolution_, column_set, &first_col, &last_col);
996 if (last_col != first_col + 1) {
1000 int y = part->MidY();
1001 TBOX margin_box = part->bounding_box();
1004 tprintf(
"Considering partition for GridSplit:");
1007 ColPartition *column = column_set->GetColumnByIndex(first_col);
1008 if (column ==
nullptr) {
1011 margin_box.set_left(column->RightAtY(
y) + 2);
1012 column = column_set->GetColumnByIndex(last_col);
1013 if (column ==
nullptr) {
1016 margin_box.set_right(column->LeftAtY(
y) - 2);
1020 GridSearch<BLOBNBOX, BLOBNBOX_CLIST, BLOBNBOX_C_IT> rectsearch(
this);
1022 tprintf(
"Searching box (%d,%d)->(%d,%d)\n", margin_box.left(), margin_box.bottom(),
1023 margin_box.right(), margin_box.top());
1026 rectsearch.StartRectSearch(margin_box);
1028 while ((bbox = rectsearch.NextRectSearch()) !=
nullptr) {
1029 if (bbox->bounding_box().overlap(margin_box)) {
1033 if (bbox ==
nullptr) {
1035 gsearch.RemoveBBox();
1036 int x_middle = (margin_box.left() + margin_box.right()) / 2;
1038 tprintf(
"Splitting part at %d:", x_middle);
1041 ColPartition *split_part = part->SplitAt(x_middle);
1042 if (split_part !=
nullptr) {
1046 split_part->Print();
1048 part_grid_.
InsertBBox(
true,
true, split_part);
1052 tprintf(
"Split had no effect\n");
1057 gsearch.RepositionIterator();
1059 tprintf(
"Part cannot be split: blob (%d,%d)->(%d,%d) in column gap\n",
1060 bbox->bounding_box().left(), bbox->bounding_box().bottom(),
1061 bbox->bounding_box().right(), bbox->bounding_box().top());
1068void ColumnFinder::GridMergePartitions() {
1070 GridSearch<ColPartition, ColPartition_CLIST, ColPartition_C_IT> gsearch(&part_grid_);
1071 gsearch.StartFullSearch();
1073 while ((part = gsearch.NextFullSearch()) !=
nullptr) {
1074 if (part->IsUnMergeableType()) {
1078 ColPartitionSet *columns = best_columns_[gsearch.GridY()];
1079 TBOX box = part->bounding_box();
1082 tprintf(
"Considering part for merge at:");
1085 int y = part->MidY();
1086 ColPartition *left_column = columns->ColumnContaining(box.left(),
y);
1087 ColPartition *right_column = columns->ColumnContaining(box.right(),
y);
1088 if (left_column ==
nullptr || right_column != left_column) {
1090 tprintf(
"In different columns\n");
1094 box.set_left(left_column->LeftAtY(
y));
1095 box.set_right(right_column->RightAtY(
y));
1097 bool modified_box =
false;
1098 GridSearch<ColPartition, ColPartition_CLIST, ColPartition_C_IT> rsearch(&part_grid_);
1099 rsearch.SetUniqueMode(
true);
1100 rsearch.StartRectSearch(box);
1101 ColPartition *neighbour;
1103 while ((neighbour = rsearch.NextRectSearch()) !=
nullptr) {
1104 if (neighbour == part || neighbour->IsUnMergeableType()) {
1107 const TBOX &neighbour_box = neighbour->bounding_box();
1109 tprintf(
"Considering merge with neighbour at:");
1112 if (neighbour_box.right() < box.left() || neighbour_box.left() > box.right()) {
1115 if (part->VSignificantCoreOverlap(*neighbour) && part->TypesMatch(*neighbour)) {
1121 const TBOX &part_box = part->bounding_box();
1124 if (neighbour_box.left() > part->right_margin() &&
1125 part_box.right() < neighbour->left_margin()) {
1128 if (neighbour_box.right() < part->left_margin() &&
1129 part_box.left() > neighbour->right_margin()) {
1132 int h_gap = std::max(part_box.left(), neighbour_box.left()) -
1133 std::min(part_box.right(), neighbour_box.right());
1135 part_box.width() < mean_column_gap_ || neighbour_box.width() < mean_column_gap_) {
1137 tprintf(
"Running grid-based merge between:\n");
1141 rsearch.RemoveBBox();
1142 if (!modified_box) {
1144 gsearch.RemoveBBox();
1145 rsearch.RepositionIterator();
1146 modified_box =
true;
1148 part->Absorb(neighbour,
WidthCB());
1150 tprintf(
"Neighbour failed hgap test\n");
1153 tprintf(
"Neighbour failed overlap or typesmatch test\n");
1164 gsearch.RepositionIterator();
1171void ColumnFinder::InsertRemainingNoise(TO_BLOCK *block) {
1172 BLOBNBOX_IT blob_it(&block->noise_blobs);
1173 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
1174 BLOBNBOX *blob = blob_it.data();
1175 if (blob->owner() !=
nullptr) {
1178 TBOX search_box(blob->bounding_box());
1183 rsearch.SetUniqueMode(
true);
1184 rsearch.StartRectSearch(search_box);
1186 ColPartition *best_part =
nullptr;
1187 int best_distance = 0;
1188 while ((part = rsearch.NextRectSearch()) !=
nullptr) {
1189 if (part->IsUnMergeableType()) {
1194 if (best_part ==
nullptr ||
distance < best_distance) {
1199 if (best_part !=
nullptr &&
1200 best_distance < kMaxDistToPartSizeRatio * best_part->median_height()) {
1203 tprintf(
"Adding noise blob with distance %d, thr=%g:box:", best_distance,
1205 blob->bounding_box().print();
1210 best_part->AddBox(blob);
1211 part_grid_.
InsertBBox(
true,
true, best_part);
1212 blob->set_owner(best_part);
1213 blob->set_flow(best_part->flow());
1214 blob->set_region_type(best_part->blob_type());
1221 block->DeleteUnownedNoise();
1225static TBOX BoxFromHLine(
const TabVector *hline) {
1226 int top = std::max(hline->startpt().y(), hline->endpt().y());
1227 int bottom = std::min(hline->startpt().y(), hline->endpt().y());
1228 top += hline->mean_width();
1229 if (top == bottom) {
1236 return TBOX(hline->startpt().x(), bottom, hline->endpt().x(), top);
1241void ColumnFinder::GridRemoveUnderlinePartitions() {
1242 TabVector_IT hline_it(&horizontal_lines_);
1243 for (hline_it.mark_cycle_pt(); !hline_it.cycled_list(); hline_it.forward()) {
1244 TabVector *hline = hline_it.data();
1245 if (hline->intersects_other_lines()) {
1248 TBOX line_box = BoxFromHLine(hline);
1249 TBOX search_box = line_box;
1250 search_box.pad(0, line_box.height());
1252 part_search.SetUniqueMode(
true);
1253 part_search.StartRectSearch(search_box);
1254 ColPartition *covered;
1255 bool touched_table =
false;
1256 bool touched_text =
false;
1257 ColPartition *line_part =
nullptr;
1258 while ((covered = part_search.NextRectSearch()) !=
nullptr) {
1260 touched_table =
true;
1262 }
else if (covered->IsTextType()) {
1264 int text_bottom = covered->median_bottom();
1265 if (line_box.bottom() <= text_bottom && text_bottom <= search_box.top()) {
1266 touched_text =
true;
1268 }
else if (covered->blob_type() ==
BRT_HLINE && line_box.contains(covered->bounding_box()) &&
1270 !
TBOX(covered->bounding_box()).contains(line_box)) {
1271 line_part = covered;
1274 if (line_part !=
nullptr && !touched_table && touched_text) {
1282void ColumnFinder::GridInsertHLinePartitions() {
1283 TabVector_IT hline_it(&horizontal_lines_);
1284 for (hline_it.mark_cycle_pt(); !hline_it.cycled_list(); hline_it.forward()) {
1285 TabVector *hline = hline_it.data();
1286 TBOX line_box = BoxFromHLine(hline);
1287 ColPartition *part =
1289 line_box.bottom(), line_box.right(), line_box.top());
1291 bool any_image =
false;
1293 part_search.SetUniqueMode(
true);
1294 part_search.StartRectSearch(line_box);
1295 ColPartition *covered;
1296 while ((covered = part_search.NextRectSearch()) !=
nullptr) {
1297 if (covered->IsImageType()) {
1311void ColumnFinder::GridInsertVLinePartitions() {
1313 for (vline_it.mark_cycle_pt(); !vline_it.cycled_list(); vline_it.forward()) {
1314 TabVector *vline = vline_it.data();
1315 if (!vline->IsSeparator()) {
1318 int left = std::min(vline->startpt().x(), vline->endpt().x());
1319 int right = std::max(vline->startpt().x(), vline->endpt().x());
1320 right += vline->mean_width();
1321 if (left == right) {
1331 bool any_image =
false;
1333 part_search.SetUniqueMode(
true);
1334 part_search.StartRectSearch(part->bounding_box());
1335 ColPartition *covered;
1336 while ((covered = part_search.NextRectSearch()) !=
nullptr) {
1337 if (covered->IsImageType()) {
1352void ColumnFinder::SetPartitionTypes() {
1353 GridSearch<ColPartition, ColPartition_CLIST, ColPartition_C_IT> gsearch(&part_grid_);
1354 gsearch.StartFullSearch();
1356 while ((part = gsearch.NextFullSearch()) !=
nullptr) {
1357 part->SetPartitionType(
resolution_, best_columns_[gsearch.GridY()]);
1363void ColumnFinder::SmoothPartnerRuns() {
1365 GridSearch<ColPartition, ColPartition_CLIST, ColPartition_C_IT> gsearch(&part_grid_);
1366 gsearch.StartFullSearch();
1368 while ((part = gsearch.NextFullSearch()) !=
nullptr) {
1369 ColPartition *partner = part->SingletonPartner(
true);
1370 if (partner !=
nullptr) {
1371 if (partner->SingletonPartner(
false) != part) {
1372 tprintf(
"Ooops! Partition:(%d partners)", part->upper_partners()->length());
1374 tprintf(
"has singleton partner:(%d partners", partner->lower_partners()->length());
1376 tprintf(
"but its singleton partner is:");
1377 if (partner->SingletonPartner(
false) ==
nullptr) {
1380 partner->SingletonPartner(
false)->Print();
1383 ASSERT_HOST(partner->SingletonPartner(
false) == part);
1384 }
else if (part->SingletonPartner(
false) !=
nullptr) {
1385 ColPartitionSet *column_set = best_columns_[gsearch.GridY()];
1387 part->SmoothPartnerRun(column_count * 2 + 1);
1394void ColumnFinder::AddToTempPartList(ColPartition *part, ColPartition_CLIST *temp_list) {
1395 int mid_y = part->MidY();
1396 ColPartition_C_IT it(temp_list);
1397 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
1398 ColPartition *test_part = it.data();
1402 if (test_part == part->SingletonPartner(
false)) {
1405 int neighbour_bottom = test_part->median_bottom();
1406 int neighbour_top = test_part->median_top();
1407 int neighbour_y = (neighbour_bottom + neighbour_top) / 2;
1408 if (neighbour_y < mid_y) {
1411 if (!part->HOverlaps(*test_part) && !part->WithinSameMargins(*test_part)) {
1415 if (it.cycled_list()) {
1416 it.add_to_end(part);
1418 it.add_before_stay_put(part);
1423void ColumnFinder::EmptyTempPartList(ColPartition_CLIST *temp_list, WorkingPartSet_LIST *work_set) {
1424 ColPartition_C_IT it(temp_list);
1425 while (!it.empty()) {
1432void ColumnFinder::TransformToBlocks(BLOCK_LIST *blocks, TO_BLOCK_LIST *to_blocks) {
1433 WorkingPartSet_LIST work_set;
1434 ColPartitionSet *column_set =
nullptr;
1435 ColPartition_IT noise_it(&noise_parts_);
1439 ColPartition_CLIST temp_part_list;
1441 GridSearch<ColPartition, ColPartition_CLIST, ColPartition_C_IT> gsearch(&part_grid_);
1442 gsearch.StartFullSearch();
1443 int prev_grid_y = -1;
1445 while ((part = gsearch.NextFullSearch()) !=
nullptr) {
1446 int grid_y = gsearch.GridY();
1447 if (grid_y != prev_grid_y) {
1448 EmptyTempPartList(&temp_part_list, &work_set);
1449 prev_grid_y = grid_y;
1451 if (best_columns_[grid_y] != column_set) {
1452 column_set = best_columns_[grid_y];
1457 tprintf(
"Changed column groups at grid index %d, y=%d\n", gsearch.GridY(),
1462 noise_it.add_to_end(part);
1464 AddToTempPartList(part, &temp_part_list);
1467 EmptyTempPartList(&temp_part_list, &work_set);
1469 WorkingPartSet_IT work_it(&work_set);
1470 while (!work_it.empty()) {
1471 WorkingPartSet *working_set = work_it.extract();
1481static void ReflectBlobList(BLOBNBOX_LIST *bblobs) {
1482 BLOBNBOX_IT it(bblobs);
1483 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
1484 it.data()->reflect_box_in_y_axis();
1494void ColumnFinder::ReflectForRtl(TO_BLOCK *input_block, BLOBNBOX_LIST *bblobs) {
1495 ReflectBlobList(bblobs);
1496 ReflectBlobList(&input_block->blobs);
1497 ReflectBlobList(&input_block->small_blobs);
1498 ReflectBlobList(&input_block->noise_blobs);
1499 ReflectBlobList(&input_block->large_blobs);
1501 auto *new_denorm =
new DENORM;
1502 new_denorm->SetupNormalization(
nullptr,
nullptr, denorm_, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.0f);
1503 denorm_ = new_denorm;
1509static void RotateAndExplodeBlobList(
const FCOORD &blob_rotation, BLOBNBOX_LIST *bblobs,
1510 STATS *widths, STATS *heights) {
1511 BLOBNBOX_IT it(bblobs);
1512 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
1513 BLOBNBOX *blob = it.data();
1514 C_BLOB *cblob = blob->cblob();
1515 C_OUTLINE_LIST *outlines = cblob->out_list();
1516 C_OUTLINE_IT ol_it(outlines);
1517 if (!outlines->singleton()) {
1520 for (; !ol_it.empty(); ol_it.forward()) {
1521 C_OUTLINE *outline = ol_it.extract();
1526 it.add_after_stay_put(new_blob);
1531 if (blob_rotation.x() != 1.0f || blob_rotation.y() != 0.0f) {
1532 cblob->rotate(blob_rotation);
1534 blob->compute_bounding_box();
1535 widths->add(blob->bounding_box().width(), 1);
1536 heights->add(blob->bounding_box().height(), 1);
1553void ColumnFinder::RotateAndReskewBlocks(
bool input_is_rtl, TO_BLOCK_LIST *blocks) {
1556 FCOORD tmp = deskew_;
1560 TO_BLOCK_IT it(blocks);
1561 int block_index = 1;
1562 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
1563 TO_BLOCK *to_block = it.data();
1564 BLOCK *block = to_block->block;
1568 block->reflect_polygon_in_y_axis();
1570 block->rotate(reskew_);
1572 block->set_right_to_left(input_is_rtl);
1574 block->set_skew(reskew_);
1575 block->pdblk.set_index(block_index++);
1576 FCOORD blob_rotation = ComputeBlockAndClassifyRotation(block);
1579 STATS widths(0, block->pdblk.bounding_box().width() - 1);
1580 STATS heights(0, block->pdblk.bounding_box().height() - 1);
1581 RotateAndExplodeBlobList(blob_rotation, &to_block->blobs, &widths, &heights);
1582 TO_ROW_IT row_it(to_block->get_rows());
1583 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
1584 TO_ROW *row = row_it.data();
1585 RotateAndExplodeBlobList(blob_rotation, row->blob_list(), &widths, &heights);
1587 block->set_median_size(
static_cast<int>(widths.median() + 0.5),
1588 static_cast<int>(heights.median() + 0.5));
1590 tprintf(
"Block median size = (%d, %d)\n", block->median_size().x(), block->median_size().y());
1600FCOORD ColumnFinder::ComputeBlockAndClassifyRotation(BLOCK *block) {
1609 FCOORD classify_rotation(text_rotation_);
1610 FCOORD block_rotation(1.0f, 0.0f);
1616 if (rerotate_.
x() == 0.0f) {
1617 block_rotation = rerotate_;
1619 block_rotation = FCOORD(0.0f, -1.0f);
1621 block->rotate(block_rotation);
1622 classify_rotation = FCOORD(1.0f, 0.0f);
1624 block_rotation.rotate(rotation_);
1628 FCOORD blob_rotation(block_rotation);
1629 block_rotation.set_y(-block_rotation.y());
1630 block->set_re_rotation(block_rotation);
1631 block->set_classify_rotation(classify_rotation);
1633 tprintf(
"Blk %d, type %d rerotation(%.2f, %.2f), char(%.2f,%.2f), box:", block->pdblk.index(),
1634 block->pdblk.poly_block()->isA(), block->re_rotation().x(), block->re_rotation().y(),
1635 classify_rotation.x(), classify_rotation.y());
1636 block->pdblk.bounding_box().print();
1638 return blob_rotation;
#define BOOL_VAR(name, val, comment)
#define INT_VAR(name, val, comment)
UnicodeText::const_iterator::difference_type distance(const UnicodeText::const_iterator &first, const UnicodeText::const_iterator &last)
void tprintf(const char *format,...)
const int kMaxIncompatibleColumnCount
bool PSM_COL_FIND_ENABLED(int pageseg_mode)
bool textord_debug_printable
GridSearch< ColPartition, ColPartition_CLIST, ColPartition_C_IT > ColPartitionGridSearch
bool PSM_SPARSE(int pageseg_mode)
int textord_debug_tabfind
std::vector< ColPartitionSet * > PartSetVector
const double kHorizontalGapMergeFraction
const double kMaxDistToPartSizeRatio
const double kMinGutterWidthGrid
static BLOBNBOX * RealBlob(C_OUTLINE *outline)
void ReSetAndReFilterBlobs()
BLOBNBOX_LIST small_blobs
void plot_graded_blobs(ScrollView *to_win)
BLOBNBOX_LIST large_blobs
BLOBNBOX_LIST noise_blobs
void ComputeEdgeOffsets(Image thresholds, Image grey)
const DENORM * predecessor() const
void SetupNormalization(const BLOCK *block, const FCOORD *rotation, const DENORM *predecessor, float x_origin, float y_origin, float x_scale, float y_scale, float final_xshift, float final_yshift)
bool right_to_left() const
TDimension y() const
access_function
void set_y(float yin)
rewrite function
void rotate(const FCOORD vec)
static bool WithinTestRegion(int detail_level, int x, int y)
const ICOORD & bleft() const
const ICOORD & tright() const
void DisplayBoxes(ScrollView *window)
void AssertNoDuplicates()
void Init(int gridsize, const ICOORD &bleft, const ICOORD &tright)
void InsertBBox(bool h_spread, bool v_spread, BBC *bbox)
ScrollView * MakeWindow(int x, int y, const char *window_name)
void RemoveBBox(BBC *bbox)
Image ComputeNonTextMask(bool debug, Image photo_map, TO_BLOCK *blob_block)
void GetDeskewVectors(FCOORD *deskew, FCOORD *reskew)
bool IsVerticallyAlignedText(double find_vertical_text_ratio, TO_BLOCK *block, BLOBNBOX_CLIST *osd_blobs)
ColumnFinder(int gridsize, const ICOORD &bleft, const ICOORD &tright, int resolution, bool cjk_script, double aligned_gap_fraction, TabVector_LIST *vlines, TabVector_LIST *hlines, int vertical_x, int vertical_y)
void SetEquationDetect(EquationDetectBase *detect)
int FindBlocks(PageSegMode pageseg_mode, Image scaled_color, int scaled_factor, TO_BLOCK *block, Image photo_mask_pix, Image thresholds_pix, Image grey_pix, DebugPixa *pixa_debug, BLOCK_LIST *blocks, BLOBNBOX_LIST *diacritic_blobs, TO_BLOCK_LIST *to_blocks)
void SetupAndFilterNoise(PageSegMode pageseg_mode, Image photo_mask_pix, TO_BLOCK *input_block)
void CorrectOrientation(TO_BLOCK *block, bool vertical_text_lines, int recognition_rotation)
static ColPartition * MakeLinePartition(BlobRegionType blob_type, const ICOORD &vertical, int left, int bottom, int right, int top)
void ExtractPartitionsAsBlocks(BLOCK_LIST *blocks, TO_BLOCK_LIST *to_blocks)
void SetTabStops(TabFind *tabgrid)
void RefinePartitionPartners(bool get_desperate)
void GridFindMargins(ColPartitionSet **best_columns)
bool MakeColPartSets(PartSetVector *part_sets)
void FindPartitionPartners()
void DeleteUnknownParts(TO_BLOCK *block)
ColPartitionSet * MakeSingleColumnSet(WidthCallback cb)
void FindFigureCaptions()
void ReTypeBlobs(BLOBNBOX_LIST *im_blobs)
void AccumulateColumnWidthsAndGaps(int *total_width, int *width_samples, int *total_gap, int *gap_samples)
void DisplayColumnEdges(int y_bottom, int y_top, ScrollView *win)
void AddToColumnSetsIfUnique(PartSetVector *column_sets, const WidthCallback &cb)
virtual int FindEquationParts(ColPartitionGrid *part_grid, ColPartitionSet **best_columns)=0
static void FindImagePartitions(Image image_pix, const FCOORD &rotation, const FCOORD &rerotation, TO_BLOCK *block, TabFind *tab_grid, DebugPixa *pixa_debug, ColPartitionGrid *part_grid, ColPartition_LIST *big_parts)
static void TransferImagePartsToImageMask(const FCOORD &rerotation, ColPartitionGrid *part_grid, Image image_mask)
void FindTextlineDirectionAndFixBrokenCJK(PageSegMode pageseg_mode, bool cjk_merge, TO_BLOCK *input_block)
void CorrectForRotation(const FCOORD &rerotation, ColPartitionGrid *part_grid)
void RemoveLineResidue(ColPartition_LIST *big_part_list)
void GradeBlobsIntoPartitions(PageSegMode pageseg_mode, const FCOORD &rerotation, TO_BLOCK *block, Image nontext_pix, const DENORM *denorm, bool cjk_script, TextlineProjection *projection, BLOBNBOX_LIST *diacritic_blobs, ColPartitionGrid *part_grid, ColPartition_LIST *big_parts)
void SetNeighboursOnMediumBlobs(TO_BLOCK *block)
void FindLeaderPartitions(TO_BLOCK *block, ColPartitionGrid *part_grid)
bool TestVerticalTextDirection(double find_vertical_text_ratio, TO_BLOCK *block, BLOBNBOX_CLIST *osd_blobs)
TabVector_LIST * dead_vectors()
static void RotateBlobList(const FCOORD &rotation, BLOBNBOX_LIST *blobs)
void InsertBlobsToGrid(bool h_spread, bool v_spread, BLOBNBOX_LIST *blobs, BBGrid< BLOBNBOX, BLOBNBOX_CLIST, BLOBNBOX_C_IT > *grid)
void ResetForVerticalText(const FCOORD &rotate, const FCOORD &rerotate, TabVector_LIST *horizontal_lines, int *min_gutter_width)
void DontFindTabVectors(BLOBNBOX_LIST *image_blobs, TO_BLOCK *block, FCOORD *deskew, FCOORD *reskew)
int resolution_
Of source image in pixels per inch.
bool FindTabVectors(TabVector_LIST *hlines, BLOBNBOX_LIST *image_blobs, TO_BLOCK *block, int min_gutter_width, double tabfind_aligned_gap_fraction, ColPartitionGrid *part_grid, FCOORD *deskew, FCOORD *reskew)
void TidyBlobs(TO_BLOCK *block)
void SetBlockRuleEdges(TO_BLOCK *block)
ICOORD vertical_skew_
Estimate of true vertical in this image.
ScrollView * FindInitialTabVectors(BLOBNBOX_LIST *image_blobs, int min_gutter_width, double tabfind_aligned_gap_fraction, TO_BLOCK *block)
ScrollView * DisplayTabVectors(ScrollView *tab_win)
void Init(int grid_size, const ICOORD &bottom_left, const ICOORD &top_right)
void set_resolution(int resolution)
void InsertCleanPartitions(ColPartitionGrid *grid, TO_BLOCK *block)
void set_left_to_right_language(bool order)
void LocateTables(ColPartitionGrid *grid, ColPartitionSet **columns, WidthCallback width_cb, const FCOORD &reskew)
int DistanceOfBoxFromPartition(const TBOX &box, const ColPartition &part, const DENORM *denorm, bool debug) const
std::unique_ptr< SVEvent > AwaitEvent(SVEventType type)