diff --git a/Makefile.am b/Makefile.am index 5b7c04e5..efd0199b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -271,6 +271,7 @@ noinst_HEADERS += src/ccstruct/seam.h noinst_HEADERS += src/ccstruct/split.h noinst_HEADERS += src/ccstruct/statistc.h noinst_HEADERS += src/ccstruct/stepblob.h +noinst_HEADERS += src/ccstruct/tabletransfer.h noinst_HEADERS += src/ccstruct/werd.h if !DISABLED_LEGACY_ENGINE noinst_HEADERS += src/ccstruct/fontinfo.h diff --git a/include/tesseract/baseapi.h b/include/tesseract/baseapi.h index 2f5b19c5..aad75d67 100644 --- a/include/tesseract/baseapi.h +++ b/include/tesseract/baseapi.h @@ -34,6 +34,7 @@ #include #include // for std::vector +#include // for std::tuple struct Pix; struct Pixa; @@ -531,6 +532,28 @@ public: * as UTF8 and must be freed with the delete [] operator. */ char *GetUTF8Text(); + + size_t GetNumberOfTables(); + + /// Return the i-th table bounding box coordinates + /// + ///Gives the (top_left.x, top_left.y, bottom_right.x, bottom_right.y) + /// coordinates of the i-th table. + std::tuple GetTableBoundingBox( + unsigned i///< Index of the table, for upper limit \see GetNumberOfTables() + ); + + /// Get bounding boxes of the rows of a table + /// return values are (top_left.x, top_left.y, bottom_right.x, bottom_right.y) + std::vector > GetTableRows( + unsigned i///< Index of the table, for upper limit \see GetNumberOfTables() + ); + + /// Get bounding boxes of the cols of a table + /// return values are (top_left.x, top_left.y, bottom_right.x, bottom_right.y) + std::vector > GetTableCols( + unsigned i///< Index of the table, for upper limit \see GetNumberOfTables() + ); /** * Make a HTML-formatted string with hOCR markup from the internal diff --git a/src/api/baseapi.cpp b/src/api/baseapi.cpp index e3a15153..61b7b390 100644 --- a/src/api/baseapi.cpp +++ b/src/api/baseapi.cpp @@ -59,6 +59,7 @@ #include "tesseractclass.h" // for Tesseract #include "tprintf.h" // for tprintf #include "werd.h" // for WERD, WERD_IT, W_FUZZY_NON, W_FUZZY_SP +#include "tabletransfer.h" // for detected tables from tablefind.h #include #include // for ETEXT_DESC @@ -1294,6 +1295,62 @@ char *TessBaseAPI::GetUTF8Text() { return result; } +size_t TessBaseAPI::GetNumberOfTables() +{ + return constUniqueInstance>().size(); +} + +std::tuple TessBaseAPI::GetTableBoundingBox(unsigned i) +{ + const std::vector& t = constUniqueInstance>(); + + if(i >= t.size()) + return std::tuple(0, 0, 0, 0); + + const int height = tesseract_->ImageHeight(); + + return std::make_tuple( + t[i].box.left(), height - t[i].box.top(), + t[i].box.right(), height - t[i].box.bottom()); +} + + +std::vector> TessBaseAPI::GetTableRows(unsigned i) +{ + const std::vector& t = constUniqueInstance>(); + + if(i >= t.size()) + return std::vector>(); + + std::vector> rows(t[i].rows.size()); + const int height = tesseract_->ImageHeight(); + + for(unsigned j = 0; j < t[i].rows.size(); ++j) + rows[j] = std::make_tuple( + t[i].rows[j].left(), height - t[i].rows[j].top(), + t[i].rows[j].right(), height - t[i].rows[j].bottom()); + + return rows; +} + +std::vector > TessBaseAPI::GetTableCols(unsigned i) +{ + const std::vector& t = constUniqueInstance>(); + + if(i >= t.size()) + return std::vector>(); + + std::vector> cols(t[i].cols.size()); + const int height = tesseract_->ImageHeight(); + + for(unsigned j = 0; j < t[i].cols.size(); ++j) + cols[j] = std::make_tuple( + t[i].cols[j].left(), height - t[i].cols[j].top(), + t[i].cols[j].right(), height - t[i].cols[j].bottom()); + + return cols; +} + static void AddBoxToTSV(const PageIterator *it, PageIteratorLevel level, std::string &text) { int left, top, right, bottom; it->BoundingBox(level, &left, &top, &right, &bottom); @@ -2062,6 +2119,8 @@ void TessBaseAPI::ClearResults() { delete paragraph_models_; paragraph_models_ = nullptr; } + + uniqueInstance>().clear(); } /** diff --git a/src/ccstruct/tabletransfer.h b/src/ccstruct/tabletransfer.h new file mode 100644 index 00000000..effeb2b9 --- /dev/null +++ b/src/ccstruct/tabletransfer.h @@ -0,0 +1,66 @@ +/****************************************************************************** + * File: tabletransfer.h + * Description: Infrastructure for the transfer of table detection results + * Author: Stefan Brechtken + * + * (C) Copyright 2021, Stefan Brechtken + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ****************************************************************************/ + +#ifndef TESSERACT_CCSTRUCT_TABLETRANSFER_H_ +#define TESSERACT_CCSTRUCT_TABLETRANSFER_H_ +#include +#include +#include "rect.h" + +namespace tesseract { + +/// Structure for data transfer from table detector +struct TessTable { + tesseract::TBOX box; + std::vector rows; + std::vector cols; +}; + +/** \brief You can use this small template function to ensure that one and + * only one object of type T exists. It implements the Singleton Pattern. + * + * T must be default-constructable. + * Usage examples: + * A& a = uniqueInstance(); + * a.xyz(); + * uniqueInstance(make_unique(42)); // replace instance + * a.foo(); + * or + * uniqueInstance().xyz(); + */ +template +T& uniqueInstance(std::unique_ptr new_instance = nullptr) +{ + static std::unique_ptr _instance = std::make_unique(); + + if(new_instance) + _instance = std::move(new_instance); + + return *_instance.get(); +} + +/// return const version of \see uniqueInstance +template +const T& constUniqueInstance(std::unique_ptr new_instance = nullptr) +{ + return uniqueInstance(std::move(new_instance)); +} + +} // namespace tesseract + +#endif // TESSERACT_CCSTRUCT_TABLETRANSFER_H_ diff --git a/src/textord/colfind.cpp b/src/textord/colfind.cpp index bd39cb97..ab2a8c3e 100644 --- a/src/textord/colfind.cpp +++ b/src/textord/colfind.cpp @@ -38,6 +38,7 @@ #include "strokewidth.h" #include "tablefind.h" #include "workingpartset.h" +#include "tabletransfer.h" #include @@ -1530,6 +1531,10 @@ void ColumnFinder::RotateAndReskewBlocks(bool input_is_rtl, TO_BLOCK_LIST *block if (textord_debug_tabfind >= 2) tprintf("Block median size = (%d, %d)\n", block->median_size().x(), block->median_size().y()); } + + std::vector& tables = uniqueInstance>(); + for(TessTable& mt: tables) + mt.box.rotate_large(reskew_); } // Computes the rotations for the block (to make textlines horizontal) and diff --git a/src/textord/tablefind.cpp b/src/textord/tablefind.cpp index f7148645..ba017aaf 100644 --- a/src/textord/tablefind.cpp +++ b/src/textord/tablefind.cpp @@ -28,6 +28,7 @@ #include "colpartitionset.h" #include "tablerecog.h" +#include "tabletransfer.h" namespace tesseract { @@ -263,12 +264,13 @@ void TableFinder::LocateTables(ColPartitionGrid *grid, ColPartitionSet **all_col #ifndef GRAPHICS_DISABLED if (textord_show_tables) { - ScrollView *table_win = MakeWindow(0, 300, "Column Partitions & Neighbors"); + ScrollView *table_win = MakeWindow(0, 300, + "Step 1: Column Partitions & Neighbors"); DisplayColPartitions(table_win, &clean_part_grid_, ScrollView::BLUE); DisplayColPartitions(table_win, &leader_and_ruling_grid_, ScrollView::AQUAMARINE); DisplayColPartitionConnections(table_win, &clean_part_grid_, ScrollView::ORANGE); - table_win = MakeWindow(100, 300, "Fragmented Text"); + table_win = MakeWindow(100, 300, "Step 2: Fragmented Text"); DisplayColPartitions(table_win, &fragmented_text_grid_, ScrollView::BLUE); } #endif // !GRAPHICS_DISABLED @@ -303,7 +305,8 @@ void TableFinder::LocateTables(ColPartitionGrid *grid, ColPartitionSet **all_col #ifndef GRAPHICS_DISABLED if (textord_tablefind_show_mark) { - ScrollView *table_win = MakeWindow(1200, 300, "Table Columns and Regions"); + ScrollView *table_win = MakeWindow(1200, 300, + "Step 7: Table Columns and Regions"); DisplayColSegments(table_win, &table_columns, ScrollView::DARK_TURQUOISE); DisplayColSegments(table_win, &table_regions, ScrollView::YELLOW); } @@ -325,7 +328,8 @@ void TableFinder::LocateTables(ColPartitionGrid *grid, ColPartitionSet **all_col #ifndef GRAPHICS_DISABLED if (textord_show_tables) { - ScrollView *table_win = MakeWindow(1200, 300, "Detected Table Locations"); + ScrollView *table_win = MakeWindow(1200, 300, + "Step 8: Detected Table Locations"); DisplayColPartitions(table_win, &clean_part_grid_, ScrollView::BLUE); DisplayColSegments(table_win, &table_columns, ScrollView::KHAKI); table_grid_.DisplayBoxes(table_win); @@ -339,8 +343,10 @@ void TableFinder::LocateTables(ColPartitionGrid *grid, ColPartitionSet **all_col #ifndef GRAPHICS_DISABLED if (textord_show_tables) { - ScrollView *table_win = MakeWindow(1400, 600, "Recognized Tables"); - DisplayColPartitions(table_win, &clean_part_grid_, ScrollView::BLUE, ScrollView::BLUE); + ScrollView *table_win = MakeWindow(1400, 600, + "Step 10: Recognized Tables"); + DisplayColPartitions(table_win, &clean_part_grid_, + ScrollView::BLUE, ScrollView::BLUE); table_grid_.DisplayBoxes(table_win); } #endif // !GRAPHICS_DISABLED @@ -353,8 +359,9 @@ void TableFinder::LocateTables(ColPartitionGrid *grid, ColPartitionSet **all_col #ifndef GRAPHICS_DISABLED if (textord_show_tables) { - ScrollView *table_win = MakeWindow(1500, 300, "Detected Tables"); - DisplayColPartitions(table_win, &clean_part_grid_, ScrollView::BLUE, ScrollView::BLUE); + ScrollView *table_win = MakeWindow(1500, 300, "Step 11: Detected Tables"); + DisplayColPartitions(table_win, &clean_part_grid_, + ScrollView::BLUE, ScrollView::BLUE); table_grid_.DisplayBoxes(table_win); } #endif // !GRAPHICS_DISABLED @@ -773,7 +780,8 @@ void TableFinder::MarkTablePartitions() { MarkPartitionsUsingLocalInformation(); #ifndef GRAPHICS_DISABLED if (textord_tablefind_show_mark) { - ScrollView *table_win = MakeWindow(300, 300, "Initial Table Partitions"); + ScrollView *table_win = MakeWindow(300, 300, + "Step 3: Initial Table Partitions"); DisplayColPartitions(table_win, &clean_part_grid_, ScrollView::BLUE); DisplayColPartitions(table_win, &leader_and_ruling_grid_, ScrollView::AQUAMARINE); } @@ -781,7 +789,8 @@ void TableFinder::MarkTablePartitions() { FilterFalseAlarms(); #ifndef GRAPHICS_DISABLED if (textord_tablefind_show_mark) { - ScrollView *table_win = MakeWindow(600, 300, "Filtered Table Partitions"); + ScrollView *table_win = MakeWindow(600, 300, + "Step 4: Filtered Table Partitions"); DisplayColPartitions(table_win, &clean_part_grid_, ScrollView::BLUE); DisplayColPartitions(table_win, &leader_and_ruling_grid_, ScrollView::AQUAMARINE); } @@ -789,7 +798,8 @@ void TableFinder::MarkTablePartitions() { SmoothTablePartitionRuns(); #ifndef GRAPHICS_DISABLED if (textord_tablefind_show_mark) { - ScrollView *table_win = MakeWindow(900, 300, "Smoothed Table Partitions"); + ScrollView *table_win = MakeWindow(900, 300, + "Step 5: Smoothed Table Partitions"); DisplayColPartitions(table_win, &clean_part_grid_, ScrollView::BLUE); DisplayColPartitions(table_win, &leader_and_ruling_grid_, ScrollView::AQUAMARINE); } @@ -797,7 +807,8 @@ void TableFinder::MarkTablePartitions() { FilterFalseAlarms(); #ifndef GRAPHICS_DISABLED if (textord_tablefind_show_mark || textord_show_tables) { - ScrollView *table_win = MakeWindow(900, 300, "Final Table Partitions"); + ScrollView *table_win = MakeWindow(900, 300, + "Step 6: Final Table Partitions"); DisplayColPartitions(table_win, &clean_part_grid_, ScrollView::BLUE); DisplayColPartitions(table_win, &leader_and_ruling_grid_, ScrollView::AQUAMARINE); } @@ -1774,7 +1785,7 @@ void TableFinder::RecognizeTables() { ScrollView *table_win = nullptr; #ifndef GRAPHICS_DISABLED if (textord_show_tables) { - table_win = MakeWindow(0, 0, "Table Structure"); + table_win = MakeWindow(0, 0, "Step 9: Table Structure"); DisplayColPartitions(table_win, &fragmented_text_grid_, ScrollView::BLUE, ScrollView::LIGHT_BLUE); // table_grid_.DisplayBoxes(table_win); @@ -1925,6 +1936,25 @@ void TableFinder::DisplayColPartitionConnections(ScrollView *win, ColPartitionGr // assigned to any table to their original types. void TableFinder::MakeTableBlocks(ColPartitionGrid *grid, ColPartitionSet **all_columns, WidthCallback width_cb) { +#ifndef GRAPHICS_DISABLED + ScrollView* table_win = nullptr; + if (textord_show_tables) { + table_win = MakeWindow(0, 0, "Step 12: Final tables"); + DisplayColPartitions(table_win, &fragmented_text_grid_, + ScrollView::BLUE, ScrollView::LIGHT_BLUE); + } +#endif // GRAPHICS_DISABLED + + // initializing recognizer in order to extract table row and columnd info + TableRecognizer recognizer; + { + recognizer.Init(); + recognizer.set_line_grid(&leader_and_ruling_grid_); + recognizer.set_text_grid(&fragmented_text_grid_); + recognizer.set_max_text_height(global_median_xheight_ * 2.0); + recognizer.set_min_height(1.5 * gridheight()); + } + // Since we have table blocks already, remove table tags from all // colpartitions GridSearch gsearch(grid); @@ -1978,8 +2008,30 @@ void TableFinder::MakeTableBlocks(ColPartitionGrid *grid, ColPartitionSet **all_ table_partition->set_flow(BTFT_CHAIN); table_partition->SetBlobTypes(); grid->InsertBBox(true, true, table_partition); + + // Insert table columns and rows into an api accessible object + StructuredTable* table_structure = recognizer.RecognizeTable(table_box); + if (table_structure != nullptr) { +#ifndef GRAPHICS_DISABLED + if (textord_show_tables) { + table_structure->Display(table_win, ScrollView::LIME_GREEN); + } +#endif // GRAPHICS_DISABLED + + std::vector& tables = uniqueInstance>(); + tables.push_back(TessTable{table_box, table_structure->getRows(), + table_structure->getCols()}); + + delete table_structure; + } } } + +#ifndef GRAPHICS_DISABLED + if (textord_show_tables) { + table_grid_.DisplayBoxes(table_win); + } +#endif // GRAPHICS_DISABLED } //////// ColSegment code diff --git a/src/textord/tablerecog.cpp b/src/textord/tablerecog.cpp index ef38e798..1f800a61 100644 --- a/src/textord/tablerecog.cpp +++ b/src/textord/tablerecog.cpp @@ -301,6 +301,37 @@ void StructuredTable::Display(ScrollView *window, ScrollView::Color color) { #endif +std::vector StructuredTable::getRows() +{ + if(cell_y_.size() < 2) + return std::vector(); + + std::vector rows(cell_y_.size() - 1); + unsigned ct = cell_y_.size() - 2; + for(unsigned i = 0; i + 1 < cell_y_.size(); i++) { + const ICOORD left(bounding_box_.left(), cell_y_[i]); + const ICOORD right(bounding_box_.right(), cell_y_[i + 1]); + rows[ct - i] = TBOX(left, right); + } + + return rows; +} + +std::vector StructuredTable::getCols() +{ + if(cell_x_.size() < 2) + return std::vector(); + + std::vector cols(cell_x_.size() - 1); + for(unsigned i = 0; i + 1 < cell_x_.size(); i++) { + const ICOORD top(cell_x_[i], bounding_box_.top()); + const ICOORD bot(cell_x_[i+1], bounding_box_.bottom()); + cols[i] = TBOX(top, bot); + } + + return cols; +} + // Clear structure information. void StructuredTable::ClearStructure() { cell_x_.clear(); diff --git a/src/textord/tablerecog.h b/src/textord/tablerecog.h index 8790f709..a77125ec 100644 --- a/src/textord/tablerecog.h +++ b/src/textord/tablerecog.h @@ -21,6 +21,7 @@ #define TABLERECOG_H_ #include "colpartitiongrid.h" +#include namespace tesseract { @@ -134,6 +135,11 @@ public: // Debug display, draws the table in the given color. If the table is not // valid, the table and "best" grid lines are still drawn in the given color. void Display(ScrollView *window, ScrollView::Color color); + + /// Calculate bounding boxes of the rows and return them. + std::vector getRows(); + /// Calculate bounding boxes of the columns and return them. + std::vector getCols(); protected: // Clear the structure information.