Merge pull request #3330 from Sintun/master

Enable api access to table detector results, resolves #1714
This commit is contained in:
Stefan Weil 2021-03-17 13:39:05 +01:00 committed by GitHub
commit 122daf1d64
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 256 additions and 13 deletions

View File

@ -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

View File

@ -34,6 +34,7 @@
#include <cstdio>
#include <vector> // for std::vector
#include <tuple> // 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<int,int,int,int> 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<std::tuple<int,int,int,int> > 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<std::tuple<int,int,int,int> > GetTableCols(
unsigned i///< Index of the table, for upper limit \see GetNumberOfTables()
);
/**
* Make a HTML-formatted string with hOCR markup from the internal

View File

@ -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 <tesseract/baseapi.h>
#include <tesseract/ocrclass.h> // for ETEXT_DESC
@ -1294,6 +1295,62 @@ char *TessBaseAPI::GetUTF8Text() {
return result;
}
size_t TessBaseAPI::GetNumberOfTables()
{
return constUniqueInstance<std::vector<TessTable>>().size();
}
std::tuple<int,int,int,int> TessBaseAPI::GetTableBoundingBox(unsigned i)
{
const std::vector<TessTable>& t = constUniqueInstance<std::vector<TessTable>>();
if(i >= t.size())
return std::tuple<int,int,int,int>(0, 0, 0, 0);
const int height = tesseract_->ImageHeight();
return std::make_tuple<int,int,int,int>(
t[i].box.left(), height - t[i].box.top(),
t[i].box.right(), height - t[i].box.bottom());
}
std::vector<std::tuple<int,int,int,int>> TessBaseAPI::GetTableRows(unsigned i)
{
const std::vector<TessTable>& t = constUniqueInstance<std::vector<TessTable>>();
if(i >= t.size())
return std::vector<std::tuple<int,int,int,int>>();
std::vector<std::tuple<int,int,int,int>> 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<int,int,int,int>(
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<std::tuple<int,int,int,int> > TessBaseAPI::GetTableCols(unsigned i)
{
const std::vector<TessTable>& t = constUniqueInstance<std::vector<TessTable>>();
if(i >= t.size())
return std::vector<std::tuple<int,int,int,int>>();
std::vector<std::tuple<int,int,int,int>> 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<int,int,int,int>(
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<std::vector<TessTable>>().clear();
}
/**

View File

@ -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 <memory>
#include <vector>
#include "rect.h"
namespace tesseract {
/// Structure for data transfer from table detector
struct TessTable {
tesseract::TBOX box;
std::vector<tesseract::TBOX> rows;
std::vector<tesseract::TBOX> 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>();
* a.xyz();
* uniqueInstance<A>(make_unique<A>(42)); // replace instance
* a.foo();
* or
* uniqueInstance<A>().xyz();
*/
template<typename T>
T& uniqueInstance(std::unique_ptr<T> new_instance = nullptr)
{
static std::unique_ptr<T> _instance = std::make_unique<T>();
if(new_instance)
_instance = std::move(new_instance);
return *_instance.get();
}
/// return const version of \see uniqueInstance
template<typename T>
const T& constUniqueInstance(std::unique_ptr<T> new_instance = nullptr)
{
return uniqueInstance<T>(std::move(new_instance));
}
} // namespace tesseract
#endif // TESSERACT_CCSTRUCT_TABLETRANSFER_H_

View File

@ -38,6 +38,7 @@
#include "strokewidth.h"
#include "tablefind.h"
#include "workingpartset.h"
#include "tabletransfer.h"
#include <algorithm>
@ -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<TessTable>& tables = uniqueInstance<std::vector<TessTable>>();
for(TessTable& mt: tables)
mt.box.rotate_large(reskew_);
}
// Computes the rotations for the block (to make textlines horizontal) and

View File

@ -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<ColPartition, ColPartition_CLIST, ColPartition_C_IT> 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<TessTable>& tables = uniqueInstance<std::vector<TessTable>>();
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

View File

@ -301,6 +301,37 @@ void StructuredTable::Display(ScrollView *window, ScrollView::Color color) {
#endif
std::vector<TBOX> StructuredTable::getRows()
{
if(cell_y_.size() < 2)
return std::vector<TBOX>();
std::vector<TBOX> 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<TBOX> StructuredTable::getCols()
{
if(cell_x_.size() < 2)
return std::vector<TBOX>();
std::vector<TBOX> 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();

View File

@ -21,6 +21,7 @@
#define TABLERECOG_H_
#include "colpartitiongrid.h"
#include <vector>
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<TBOX> getRows();
/// Calculate bounding boxes of the columns and return them.
std::vector<TBOX> getCols();
protected:
// Clear the structure information.