Skip to content

Commit

Permalink
Merge pull request #3330 from Sintun/master
Browse files Browse the repository at this point in the history
Enable api access to table detector results, resolves #1714
  • Loading branch information
stweil authored Mar 17, 2021
2 parents 4cd56dc + 288b8ca commit 122daf1
Show file tree
Hide file tree
Showing 8 changed files with 256 additions and 13 deletions.
1 change: 1 addition & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 23 additions & 0 deletions include/tesseract/baseapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@

#include <cstdio>
#include <vector> // for std::vector
#include <tuple> // for std::tuple

struct Pix;
struct Pixa;
Expand Down Expand Up @@ -531,6 +532,28 @@ class TESS_API TessBaseAPI {
* 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
Expand Down
59 changes: 59 additions & 0 deletions src/api/baseapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -2062,6 +2119,8 @@ void TessBaseAPI::ClearResults() {
delete paragraph_models_;
paragraph_models_ = nullptr;
}

uniqueInstance<std::vector<TessTable>>().clear();
}

/**
Expand Down
66 changes: 66 additions & 0 deletions src/ccstruct/tabletransfer.h
Original file line number Diff line number Diff line change
@@ -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_
5 changes: 5 additions & 0 deletions src/textord/colfind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include "strokewidth.h"
#include "tablefind.h"
#include "workingpartset.h"
#include "tabletransfer.h"

#include <algorithm>

Expand Down Expand Up @@ -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
Expand Down
78 changes: 65 additions & 13 deletions src/textord/tablefind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

#include "colpartitionset.h"
#include "tablerecog.h"
#include "tabletransfer.h"

namespace tesseract {

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -773,31 +780,35 @@ 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);
}
#endif
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);
}
#endif
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);
}
#endif
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);
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit 122daf1

Please sign in to comment.