21 #pragma warning(disable:4244) // Conversion warnings
25 #include "config_auto.h"
31 #include "allheaders.h"
154 "Debug table marking steps in detail");
156 "Show page stats used in table finding");
158 "Enables the table recognizer for table layout and filtering.");
171 global_median_xheight_(0),
172 global_median_blob_width_(0),
173 global_median_ledding_(0),
174 left_to_right_language_(true) {
192 const ICOORD& top_right) {
231 BLOBNBOX_CLIST* part_boxes = part->
boxes();
232 BLOBNBOX_C_IT pit(part_boxes);
233 for (pit.mark_cycle_pt(); !pit.cycled_list(); pit.forward()) {
240 if (leader_part ==
NULL) {
244 leader_part->
AddBox(pblob);
246 clean_part->
AddBox(pblob);
253 if (leader_part !=
NULL) {
276 #ifndef GRAPHICS_DISABLED
285 table_win =
MakeWindow(100, 300,
"Fragmented Text");
288 #endif // GRAPHICS_DISABLED
295 ColSegment_LIST column_blocks;
309 ColSegment_LIST table_columns;
315 ColSegment_LIST table_regions;
318 #ifndef GRAPHICS_DISABLED
324 #endif // GRAPHICS_DISABLED
340 #ifndef GRAPHICS_DISABLED
347 #endif // GRAPHICS_DISABLED
354 #ifndef GRAPHICS_DISABLED
361 #endif // GRAPHICS_DISABLED
369 #ifndef GRAPHICS_DISABLED
376 #endif // GRAPHICS_DISABLED
452 if (part->
boxes()->empty()) {
462 bool found_split =
true;
463 while (found_split) {
465 BLOBNBOX_C_IT box_it(right_part->
boxes());
473 for (box_it.mark_cycle_pt(); !box_it.cycled_list(); box_it.forward()) {
474 const TBOX& box = box_it.data()->bounding_box();
476 box.
left() - previous_right > kThreshold) {
479 int mid_x = (box.
left() + previous_right) / 2;
481 right_part = left_part->
SplitAt(mid_x);
489 previous_right =
MAX(previous_right, box.
right());
522 return box.
height() > kHeightRequired &&
523 box.
width() > kWidthRequired &&
524 box.
area() > kAreaRequired;
537 ColSegment_LIST* column_blocks) {
540 if (columns !=
NULL) {
541 ColSegment_LIST new_blocks;
552 ColSegment_LIST* column_blocks) {
553 ColSegment_IT src_it(new_blocks);
554 ColSegment_IT dest_it(column_blocks);
556 for (src_it.mark_cycle_pt(); !src_it.cycled_list(); src_it.forward()) {
559 bool match_found =
false;
561 for (dest_it.mark_cycle_pt(); !dest_it.cycled_list(); dest_it.forward()) {
569 delete src_it.extract();
575 dest_it.add_after_then_move(src_it.extract());
584 return (abs(b1.
left() - b2.
left()) < x_margin) &&
608 int y = part->
MidY();
632 if (right < box.
left()) {
645 if (left > box.
right()) {
697 if (neighbor == part)
703 if (neighbor_box.
top() < part_box.
bottom() &&
704 gap < min_space_below) {
705 min_space_below = gap;
706 below_neighbor = neighbor;
708 else if (part_box.
top() < neighbor_box.
bottom() &&
709 gap < min_space_above) {
710 min_space_above = gap;
711 above_neighbor = neighbor;
723 STATS xheight_stats(0, kMaxVerticalSpacing + 1);
724 STATS width_stats(0, kMaxBlobWidth + 1);
725 STATS ledding_stats(0, kMaxVerticalSpacing + 1);
744 BLOBNBOX_C_IT it(part->
boxes());
745 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
746 xheight_stats.
add(it.data()->bounding_box().height(), 1);
747 width_stats.
add(it.data()->bounding_box().width(), 1);
758 #ifndef GRAPHICS_DISABLED
760 const char* kWindowName =
"X-height (R), X-width (G), and ledding (B)";
766 #endif // GRAPHICS_DISABLED
874 BLOBNBOX_CLIST* part_boxes = part->
boxes();
875 BLOBNBOX_C_IT it(part_boxes);
885 int previous_x1 = -1;
887 int largest_partition_gap_found = -1;
891 const double max_gap = kMaxGapInTextPartition * part->
median_size();
892 const double min_gap = kMinMaxGapInTextPartition * part->
median_size();
894 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
898 if (previous_x1 != -1) {
899 int gap = current_x0 - previous_x1;
910 if (-gap < part->median_size() * kMaxBlobOverlapFactor) {
911 previous_x1 =
MAX(previous_x1, current_x1);
925 if (gap > largest_partition_gap_found)
926 largest_partition_gap_found = gap;
928 previous_x1 = current_x1;
942 if (largest_partition_gap_found == -1)
948 return largest_partition_gap_found < min_gap;
968 const int top = box.
top() + search_size;
969 const int bottom = box.
bottom() - search_size;
973 int x = right_to_left ? box.
right() : box.
left();
1035 int current_spacing = 0;
1036 int upper_spacing = 0;
1042 current_spacing = mid - left;
1043 upper_spacing = upper_mid - left;
1049 current_spacing = right - mid;
1050 upper_spacing = right - upper_mid;
1052 if (current_spacing * kParagraphEndingPreviousLineRatio > upper_spacing)
1058 kStrokeWidthConstantTolerance)) {
1066 kMaxParagraphEndingLeftSpaceMultiple * part->
median_size())
1074 kMinParagraphEndingTextToWhitespaceRatio * upper_part->
space_to_right())
1101 if (top > max_top) {
1105 if (bottom < min_bottom) {
1106 min_bottom = bottom;
1131 if (!upper_part || !lower_part)
1157 ColSegment_IT it(column_blocks);
1158 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
1161 int num_table_cells = 0;
1162 int num_text_cells = 0;
1177 if (!num_table_cells && !num_text_cells) {
1178 delete it.extract();
1191 ColSegment_IT it(segments);
1192 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
1219 bool neighbor_found =
false;
1220 bool modified =
false;
1230 neighbor_found =
false;
1236 if (neighbor == seg)
1258 neighbor_found =
true;
1265 }
while (neighbor_found);
1287 ColSegment_IT it(table_columns);
1307 bool found_neighbours =
false;
1323 found_neighbours =
true;
1325 if (found_neighbours) {
1326 it.add_after_then_move(col);
1337 ColSegment_LIST* table_regions) {
1338 ColSegment_IT cit(table_columns);
1339 ColSegment_IT rit(table_regions);
1348 bool* table_region =
new bool[page_height];
1352 for (
int i = 0; i < page_height; i++) {
1353 table_region[i] =
false;
1357 cit.move_to_first();
1358 for (cit.mark_cycle_pt(); !cit.cycled_list(); cit.forward()) {
1359 TBOX col_box = cit.data()->bounding_box();
1363 for (
int i = intersection_box.
bottom(); i < intersection_box.
top(); i++) {
1364 table_region[i -
bleft().
y()] =
true;
1368 TBOX current_table_box;
1373 for (
int i = 1; i < page_height; i++) {
1375 if (!table_region[i - 1] && table_region[i]) {
1380 if (table_region[i - 1] && !table_region[i]) {
1382 if (!current_table_box.
null_box()) {
1385 rit.add_after_then_move(seg);
1390 delete[] table_region;
1406 bool neighbor_found =
false;
1407 bool modified =
false;
1411 TBOX search_region(box);
1414 neighbor_found =
false;
1420 if (neighbor == seg)
1438 neighbor_found =
true;
1445 }
while (neighbor_found);
1501 ColSegment_CLIST adjusted_tables;
1502 ColSegment_C_IT it(&adjusted_tables);
1508 TBOX grown_box = table_box;
1516 it.add_after_then_move(col);
1527 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
1542 TBOX search_box = table_box;
1556 const TBOX& search_range,
1560 for (
int i = 0; i < 2; ++i) {
1584 const TBOX& search_range,
1599 if (result_box->
contains(part_box))
1614 const TBOX& table_box) {
1632 int num_extra_partitions = 0;
1633 int extra_space_to_right = 0;
1634 int extra_space_to_left = 0;
1637 for (
int i = 0; i < 2; ++i) {
1654 num_extra_partitions++;
1658 extra_space_to_right++;
1659 extra_space_to_left++;
1662 int space_threshold = kSideSpaceMargin * part.
median_size();
1664 extra_space_to_right++;
1666 extra_space_to_left++;
1671 return (extra_space_to_right > num_extra_partitions / 2) ||
1672 (extra_space_to_left > num_extra_partitions / 2);
1686 const int max_distance = kMaxColumnHeaderDistance *
1688 int table_top = table_box->
top();
1691 if (box.
bottom() - table_top > max_distance)
1697 previous_neighbor =
NULL;
1702 if (previous_neighbor ==
NULL) {
1703 previous_neighbor = neighbor;
1720 int* table_xprojection =
new int[page_width];
1729 for (
int i = 0; i < page_width; i++) {
1730 table_xprojection[i] = 0;
1747 BLOBNBOX_CLIST* part_boxes = part->
boxes();
1748 BLOBNBOX_C_IT pit(part_boxes);
1755 int next_position_to_write = 0;
1757 for (pit.mark_cycle_pt(); !pit.cycled_list(); pit.forward()) {
1764 xstart =
MAX(xstart, next_position_to_write);
1765 for (
int i = xstart; i < xend; i++)
1766 table_xprojection[i -
bleft().
x()]++;
1767 next_position_to_write = xend;
1776 delete[] table_xprojection;
1784 for (
int i = 0; i < length; i++) {
1785 if (xprojection[i] > peak_value) {
1786 peak_value = xprojection[i];
1792 if (peak_value < kMinRowsInTable)
1794 double projection_threshold = kSmallTableProjectionThreshold * peak_value;
1795 if (peak_value >= kLargeTableRowCount)
1796 projection_threshold = kLargeTableProjectionThreshold * peak_value;
1798 for (
int i = 0; i < length; i++) {
1799 xprojection[i] = (xprojection[i] >= projection_threshold) ? 1 : 0;
1802 int largest_gap = 0;
1804 for (
int i = 1; i < length; i++) {
1806 if (xprojection[i - 1] && !xprojection[i]) {
1810 if (run_start != -1 && !xprojection[i - 1] && xprojection[i]) {
1811 int gap = i - run_start;
1812 if (gap > largest_gap)
1833 table_win =
MakeWindow(0, 0,
"Table Structure");
1848 ColSegment_CLIST good_tables;
1849 ColSegment_C_IT good_it(&good_tables);
1864 if (table_structure !=
NULL) {
1869 delete table_structure;
1870 good_it.add_after_then_move(found_table);
1879 for (good_it.mark_cycle_pt(); !good_it.cycled_list(); good_it.forward())
1885 ColSegment_LIST *segments,
1887 #ifndef GRAPHICS_DISABLED
1890 ColSegment_IT it(segments);
1891 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
1894 int left_x = box.
left();
1895 int right_x = box.
right();
1896 int top_y = box.
top();
1897 int bottom_y = box.
bottom();
1898 win->
Rectangle(left_x, bottom_y, right_x, top_y);
1906 #ifndef GRAPHICS_DISABLED
1914 int left_x = box.
left();
1915 int right_x = box.
right();
1916 int top_y = box.
top();
1917 int bottom_y = box.
bottom();
1920 win->
Rectangle(left_x, bottom_y, right_x, top_y);
1933 #ifndef GRAPHICS_DISABLED
1941 color = default_color;
1943 color = table_color;
1946 int left_x = box.
left();
1947 int right_x = box.
right();
1948 int top_y = box.
top();
1949 int bottom_y = box.
bottom();
1952 win->
Rectangle(left_x, bottom_y, right_x, top_y);
1967 #ifndef GRAPHICS_DISABLED
1975 int left_x = box.
left();
1976 int right_x = box.
right();
1977 int top_y = box.
top();
1978 int bottom_y = box.
bottom();
1983 int mid_x = (left_x + right_x) / 2;
1984 int mid_y = (top_y + bottom_y) / 2;
1985 int other_x = (upper_box.
left() + upper_box.
right()) / 2;
1986 int other_y = (upper_box.
top() + upper_box.
bottom()) / 2;
1989 win->
Line(mid_x, mid_y, other_x, other_y);
1994 int mid_x = (left_x + right_x) / 2;
1995 int mid_y = (top_y + bottom_y) / 2;
1996 int other_x = (lower_box.
left() + lower_box.
right()) / 2;
1997 int other_y = (lower_box.
top() + lower_box.
bottom()) / 2;
2000 win->
Line(mid_x, mid_y, other_x, other_y);
2013 PIX* pix = pixRead(
"test1.tif");
2015 tprintf(
"Input file test1.tif not found.\n");
2018 int img_height = pixGetHeight(pix);
2019 int img_width = pixGetWidth(pix);
2022 BOXA* text_box_array = boxaCreate(num_boxes);
2023 BOXA* table_box_array = boxaCreate(num_boxes);
2032 BOX* lept_box = boxCreate(box.
left(), img_height - box.
top(),
2036 boxaAddBox(table_box_array, lept_box, L_INSERT);
2038 boxaAddBox(text_box_array, lept_box, L_INSERT);
2041 PIX* out = pixDrawBoxa(pix, text_box_array, 3, 0xff000000);
2042 out = pixDrawBoxa(out, table_box_array, 3, 0x0000ff00);
2044 BOXA* table_array = boxaCreate(num_boxes);
2046 FILE* fptr = fopen(
"tess-table.txt",
"wb");
2061 BOX* lept_box = boxCreate(box.
left(), img_height - box.
top(),
2064 boxaAddBox(table_array, lept_box, L_INSERT);
2065 fprintf(fptr,
"%d %d %d %d TABLE\n", box.
left(),
2070 out = pixDrawBoxa(out, table_array, 5, 0x7fff0000);
2072 pixWrite(
"out.png", out, IFF_PNG);
2074 boxaDestroy(&text_box_array);
2075 boxaDestroy(&table_box_array);
2076 boxaDestroy(&table_array);
2122 if (table_partition) {
2123 table_partition->
Absorb(part, width_cb);
2125 table_partition = part;
2130 if (table_partition) {
2142 grid->
InsertBBox(
true,
true, table_partition);
2151 num_table_cells_(0),
2166 return kBoxColors[type_];
2177 if (num_table_cells_ > kTableColumnThreshold * num_text_cells_)
2179 else if (num_text_cells_ > num_table_cells_)
void GetColumnBlocks(ColPartitionSet **columns, ColSegment_LIST *col_segments)
void InsertRulingPartition(ColPartition *part)
bool textord_tablefind_recognize_tables
void DisplayColPartitionConnections(ScrollView *win, ColPartitionGrid *grid, ScrollView::Color default_color)
const double kAllowBlobWidth
void set_global_median_ledding(int ledding)
const double kMinOverlapWithTable
void set_space_below(int space)
void Init(int gridsize, const ICOORD &bleft, const ICOORD &tright)
const int kLargeTableRowCount
ColPartitionGrid clean_part_grid_
void SetVerticalSpacing(ColPartition *part)
const double kMaxBlobOverlapFactor
ColPartition * ShallowCopy() const
const double kAllowTextHeight
void GridMergeTableRegions()
int space_to_left() const
bool textord_dump_table_images
bool inside_table_column()
int global_median_ledding_
void FilterParagraphEndings()
BBC * NextVerticalSearch(bool top_to_bottom)
const TBOX & bounding_box() const
void DeleteObject(T *object)
const double kSplitPartitionSize
bool AllowTextPartition(const ColPartition &part) const
int direction(EDGEPT *point)
void set_nearest_neighbor_below(ColPartition *part)
bool IsInSameColumnAs(const ColPartition &part) const
void set_space_to_right(int space)
void add(inT32 value, inT32 count)
void RepositionIterator()
#define BOOL_VAR(name, val, comment)
BlobRegionType blob_type() const
const double kMinMaxGapInTextPartition
void AddBox(BLOBNBOX *box)
ColPartition * nearest_neighbor_above() const
void GrowTableBox(const TBOX &table_box, TBOX *result_box)
void SetColumnsType(ColSegment_LIST *col_segments)
const ICOORD & bleft() const
void FilterHeaderAndFooter()
void set_global_median_xheight(int xheight)
ColPartition * SplitAt(int split_x)
BBC * NextSideSearch(bool right_to_left)
const double kStrokeWidthFractionalTolerance
void MoveColSegmentsToGrid(ColSegment_LIST *segments, ColSegmentGrid *col_seg_grid)
void rotate_large(const FCOORD &vec)
const double kParagraphEndingPreviousLineRatio
bool textord_tablefind_show_stats
void SplitAndInsertFragmentedTextPartition(ColPartition *part)
void InsertImagePartition(ColPartition *part)
void SmoothTablePartitionRuns()
const ICOORD & tright() const
void set_max_text_height(int height)
void DisplayColSegments(ScrollView *win, ColSegment_LIST *cols, ScrollView::Color color)
void set_space_to_left(int space)
void set_space_above(int space)
int median_bottom() const
void InsertBBox(bool h_spread, bool v_spread, BBC *bbox)
bool BelongToOneTable(const TBOX &box1, const TBOX &box2)
void InsertCleanPartitions(ColPartitionGrid *grid, TO_BLOCK *block)
void DisplayBoxes(ScrollView *window)
const double kAllowTextWidth
const TBOX & bounding_box() const
void SetPartitionType(int resolution, ColPartitionSet *columns)
const int kRulingVerticalMargin
bool textord_tablefind_show_mark
void WriteToPix(const FCOORD &reskew)
const double kAllowBlobHeight
bool HasWideOrNoInterWordGap(ColPartition *part) const
static void SetPartitionSpacings(ColPartitionGrid *grid, ColPartitionSet **all_columns)
#define CLISTIZE(CLASSNAME)
void SetUniqueMode(bool mode)
inT16 y() const
access_function
void InsertFragmentedTextPartition(ColPartition *part)
ColPartition * SingletonPartner(bool upper)
bool HLineBelongsToTable(const ColPartition &part, const TBOX &table_box)
const double kSmallTableProjectionThreshold
ColPartition * ColumnContaining(int x, int y)
bool HasLeaderAdjacent(const ColPartition &part)
ScrollView::Color BoxColor() const
void set_line_grid(ColPartitionGrid *lines)
BlobTextFlowType flow() const
void set_num_table_cells(int n)
BlobRegionType region_type() const
void GridMergeColumnBlocks()
void InitializePartitions(ColPartitionSet **all_columns)
ScrollView * MakeWindow(int x, int y, const char *window_name)
TBOX bounding_union(const TBOX &box) const
void InsertBox(const TBOX &other)
bool IsHorizontalLine() const
void set_inside_table_column(bool val)
const double kAllowTextArea
bool AllowBlob(const BLOBNBOX &blob) const
const double kMaxXProjectionGapFactor
const double kMaxGapInTextPartition
ColPartitionGrid leader_and_ruling_grid_
const int kMaxColumnHeaderDistance
void SetGlobalSpacings(ColPartitionGrid *grid)
ColSegmentGrid col_seg_grid_
const ICOORD & bleft() const
void FindPartitionPartners()
void GetTableRegions(ColSegment_LIST *table_columns, ColSegment_LIST *table_regions)
bool left_to_right_language_
bool MatchingSizes(const ColPartition &other) const
void InsertLeaderPartition(ColPartition *part)
int space_to_right() const
const int kMinRowsInTable
void set_min_height(int height)
void set_blob_type(BlobRegionType t)
bool major_x_overlap(const TBOX &box) const
int RightAtY(int y) const
void MarkTablePartitions()
ColPartition * CopyButDontOwnBlobs()
PolyBlockType type() const
void StartSideSearch(int x, int ymin, int ymax)
void GroupColumnBlocks(ColSegment_LIST *current_segments, ColSegment_LIST *col_segments)
const double kRequiredFullJustifiedSpacing
void set_text_grid(ColPartitionGrid *text)
void Absorb(ColPartition *other, WidthCallback *cb)
void StartRectSearch(const TBOX &rect)
int global_median_blob_width_
double overlap_fraction(const TBOX &box) const
TBOX intersection(const TBOX &box) const
void set_flow(BlobTextFlowType f)
ScrollView * MakeWindow(int x, int y, const char *window_name)
void GrowTableToIncludeLines(const TBOX &table_box, const TBOX &search_range, TBOX *result_box)
bool MatchingStrokeWidth(const ColPartition &other, double fractional_tolerance, double constant_tolerance) const
bool major_y_overlap(const TBOX &box) const
const double kTableColumnThreshold
void GrowTableToIncludePartials(const TBOX &table_box, const TBOX &search_range, TBOX *result_box)
void LocateTables(ColPartitionGrid *grid, ColPartitionSet **columns, WidthCallback *width_cb, const FCOORD &reskew)
inT16 x() const
access function
void set_global_median_blob_width(int width)
void AdjustTableBoundaries()
StructuredTable * RecognizeTable(const TBOX &guess_box)
const int kMaxVerticalSpacing
void DeleteSingleColumnTables()
void DisplayColSegmentGrid(ScrollView *win, ColSegmentGrid *grid, ScrollView::Color color)
void GetColumnBoxes(int y_bottom, int y_top, ColSegment_LIST *segments)
const double kAllowBlobArea
void Rectangle(int x1, int y1, int x2, int y2)
ColSegmentGrid table_grid_
void MakeTableBlocks(ColPartitionGrid *grid, ColPartitionSet **columns, WidthCallback *width_cb)
bool contains(const FCOORD pt) const
const ICOORD & tright() const
const TBOX & bounding_box() const
void DisplayColPartitions(ScrollView *win, ColPartitionGrid *grid, ScrollView::Color text_color, ScrollView::Color table_color)
void set_left_to_right_language(bool order)
int global_median_xheight_
const int kMaxBoxesInDataPartition
const double kMinParagraphEndingTextToWhitespaceRatio
const int kAdjacentLeaderSearchPadding
ColPartition * nearest_neighbor_below() const
void ClearGridData(void(*free_method)(BBC *))
const TBOX & bounding_box() const
void Init(int grid_size, const ICOORD &bottom_left, const ICOORD &top_right)
void set_num_text_cells(int n)
bool GapInXProjection(int *xprojection, int length)
void plot(ScrollView *window, float xorigin, float yorigin, float xscale, float yscale, ScrollView::Color colour) const
bool ConsecutiveBoxes(const TBOX &b1, const TBOX &b2)
BlobTextFlowType flow() const
void set_nearest_neighbor_above(ColPartition *part)
void Line(int x1, int y1, int x2, int y2)
ColPartitionGrid fragmented_text_grid_
void Display(ScrollView *window, ScrollView::Color color)
void set_bounding_box(const TBOX &other)
void InsertTextPartition(ColPartition *part)
const double kMaxParagraphEndingLeftSpaceMultiple
void IncludeLeftOutColumnHeaders(TBOX *table_box)
bool overlap(const TBOX &box) const
bool VSignificantCoreOverlap(const ColPartition &other) const
void GetTableColumns(ColSegment_LIST *table_columns)
const double kLargeTableProjectionThreshold
const int kSideSpaceMargin
void GridCoords(int x, int y, int *grid_x, int *grid_y) const
const double kStrokeWidthConstantTolerance
const double kMaxTableCellXheight
void MarkPartitionsUsingLocalInformation()
void RefinePartitionPartners(bool get_desperate)
void StartVerticalSearch(int xmin, int xmax, int y)
const int kMinBoxesInTextPartition