Tesseract
3.02
|
#include <tablefind.h>
Definition at line 131 of file tablefind.h.
tesseract::TableFinder::TableFinder | ( | ) |
Definition at line 168 of file tablefind.cpp.
: resolution_(0), global_median_xheight_(0), global_median_blob_width_(0), global_median_ledding_(0), left_to_right_language_(true) { }
tesseract::TableFinder::~TableFinder | ( | ) |
Definition at line 176 of file tablefind.cpp.
{ // ColPartitions and ColSegments created by this class for storage in grids // need to be deleted explicitly. clean_part_grid_.ClearGridData(&DeleteObject<ColPartition>); leader_and_ruling_grid_.ClearGridData(&DeleteObject<ColPartition>); fragmented_text_grid_.ClearGridData(&DeleteObject<ColPartition>); col_seg_grid_.ClearGridData(&DeleteObject<ColSegment>); table_grid_.ClearGridData(&DeleteObject<ColSegment>); }
void tesseract::TableFinder::AdjustTableBoundaries | ( | ) | [protected] |
Definition at line 1485 of file tablefind.cpp.
{ // Iterate the table regions in the grid ColSegment_CLIST adjusted_tables; ColSegment_C_IT it(&adjusted_tables); ColSegmentGridSearch gsearch(&table_grid_); gsearch.StartFullSearch(); ColSegment* table = NULL; while ((table = gsearch.NextFullSearch()) != NULL) { const TBOX& table_box = table->bounding_box(); TBOX grown_box = table_box; GrowTableBox(table_box, &grown_box); // To prevent a table from expanding again, do not insert the // modified box back to the grid. Instead move it to a list and // and remove it from the grid. The list is moved later back to the grid. if (!grown_box.null_box()) { ColSegment* col = new ColSegment(); col->InsertBox(grown_box); it.add_after_then_move(col); } gsearch.RemoveBBox(); delete table; } // clear table grid to move final tables in it // TODO(nbeato): table_grid_ should already be empty. The above loop // removed everything. Maybe just assert it is empty? table_grid_.Clear(); it.move_to_first(); // move back final tables to table_grid_ for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) { ColSegment* seg = it.extract(); table_grid_.InsertBBox(true, true, seg); } }
bool tesseract::TableFinder::AllowBlob | ( | const BLOBNBOX & | blob | ) | const [protected] |
Definition at line 502 of file tablefind.cpp.
{ const TBOX& box = blob.bounding_box(); const double kHeightRequired = global_median_xheight_ * kAllowBlobHeight; const double kWidthRequired = global_median_blob_width_ * kAllowBlobWidth; const int median_area = global_median_xheight_ * global_median_blob_width_; const double kAreaRequired = median_area * kAllowBlobArea; // Keep comparisons strictly greater to disallow 0! return box.height() > kHeightRequired && box.width() > kWidthRequired && box.area() > kAreaRequired; }
bool tesseract::TableFinder::AllowTextPartition | ( | const ColPartition & | part | ) | const [protected] |
Definition at line 489 of file tablefind.cpp.
{ const double kHeightRequired = global_median_xheight_ * kAllowTextHeight; const double kWidthRequired = global_median_blob_width_ * kAllowTextWidth; const int median_area = global_median_xheight_ * global_median_blob_width_; const double kAreaPerBlobRequired = median_area * kAllowTextArea; // Keep comparisons strictly greater to disallow 0! return part.median_size() > kHeightRequired && part.median_width() > kWidthRequired && part.bounding_box().area() > kAreaPerBlobRequired * part.boxes_count(); }
Definition at line 1444 of file tablefind.cpp.
{ // Check the obvious case. Most likely not true because overlapping boxes // should already be merged, but seems like a good thing to do in case things // change. if (box1.overlap(box2)) return true; // Check for ColPartitions spanning both table regions TBOX bbox = box1.bounding_union(box2); // Start a rect search on bbox GridSearch<ColPartition, ColPartition_CLIST, ColPartition_C_IT> rectsearch(&clean_part_grid_); rectsearch.StartRectSearch(bbox); ColPartition* part = NULL; while ((part = rectsearch.NextRectSearch()) != NULL) { const TBOX& part_box = part->bounding_box(); // return true if a colpartition spanning both table regions is found if (part_box.overlap(box1) && part_box.overlap(box2)) return true; } return false; }
const ICOORD & tesseract::TableFinder::bleft | ( | ) | const [protected] |
Definition at line 387 of file tablefind.cpp.
{ return clean_part_grid_.bleft(); }
void tesseract::TableFinder::DeleteSingleColumnTables | ( | ) | [protected] |
Definition at line 1702 of file tablefind.cpp.
{ int page_width = tright().x() - bleft().x(); ASSERT_HOST(page_width > 0); // create an integer array to hold projection on x-axis int* table_xprojection = new int[page_width]; // Iterate through all tables in the table grid GridSearch<ColSegment, ColSegment_CLIST, ColSegment_C_IT> table_search(&table_grid_); table_search.StartFullSearch(); ColSegment* table; while ((table = table_search.NextFullSearch()) != NULL) { TBOX table_box = table->bounding_box(); // reset the projection array for (int i = 0; i < page_width; i++) { table_xprojection[i] = 0; } // Start a rect search on table_box GridSearch<ColPartition, ColPartition_CLIST, ColPartition_C_IT> rectsearch(&clean_part_grid_); rectsearch.SetUniqueMode(true); rectsearch.StartRectSearch(table_box); ColPartition* part; while ((part = rectsearch.NextRectSearch()) != NULL) { if (!part->IsTextType()) continue; // Do not consider non-text partitions if (part->flow() == BTFT_LEADER) continue; // Assume leaders are in tables TBOX part_box = part->bounding_box(); // Do not consider partitions partially covered by the table if (part_box.overlap_fraction(table_box) < kMinOverlapWithTable) continue; BLOBNBOX_CLIST* part_boxes = part->boxes(); BLOBNBOX_C_IT pit(part_boxes); // Make sure overlapping blobs don't artificially inflate the number // of rows in the table. This happens frequently with things such as // decimals and split characters. Do this by assuming the column // partition is sorted mostly left to right and just clip // bounding boxes by the previous box's extent. int next_position_to_write = 0; for (pit.mark_cycle_pt(); !pit.cycled_list(); pit.forward()) { BLOBNBOX *pblob = pit.data(); // ignore blob height for the purpose of projection since we // are only interested in finding valleys int xstart = pblob->bounding_box().left(); int xend = pblob->bounding_box().right(); xstart = MAX(xstart, next_position_to_write); for (int i = xstart; i < xend; i++) table_xprojection[i - bleft().x()]++; next_position_to_write = xend; } } // Find largest valley between two reasonable peaks in the table if (!GapInXProjection(table_xprojection, page_width)) { table_search.RemoveBBox(); delete table; } } delete[] table_xprojection; }
void tesseract::TableFinder::DisplayColPartitionConnections | ( | ScrollView * | win, |
ColPartitionGrid * | grid, | ||
ScrollView::Color | default_color | ||
) | [protected] |
Definition at line 1949 of file tablefind.cpp.
{ #ifndef GRAPHICS_DISABLED // Iterate the ColPartitions in the grid. GridSearch<ColPartition, ColPartition_CLIST, ColPartition_C_IT> gsearch(grid); gsearch.StartFullSearch(); ColPartition* part = NULL; while ((part = gsearch.NextFullSearch()) != NULL) { const TBOX& box = part->bounding_box(); int left_x = box.left(); int right_x = box.right(); int top_y = box.top(); int bottom_y = box.bottom(); ColPartition* upper_part = part->nearest_neighbor_above(); if (upper_part) { TBOX upper_box = upper_part->bounding_box(); int mid_x = (left_x + right_x) / 2; int mid_y = (top_y + bottom_y) / 2; int other_x = (upper_box.left() + upper_box.right()) / 2; int other_y = (upper_box.top() + upper_box.bottom()) / 2; win->Brush(ScrollView::NONE); win->Pen(color); win->Line(mid_x, mid_y, other_x, other_y); } ColPartition* lower_part = part->nearest_neighbor_below(); if (lower_part) { TBOX lower_box = lower_part->bounding_box(); int mid_x = (left_x + right_x) / 2; int mid_y = (top_y + bottom_y) / 2; int other_x = (lower_box.left() + lower_box.right()) / 2; int other_y = (lower_box.top() + lower_box.bottom()) / 2; win->Brush(ScrollView::NONE); win->Pen(color); win->Line(mid_x, mid_y, other_x, other_y); } } win->UpdateWindow(); #endif }
void tesseract::TableFinder::DisplayColPartitions | ( | ScrollView * | win, |
ColPartitionGrid * | grid, | ||
ScrollView::Color | text_color, | ||
ScrollView::Color | table_color | ||
) | [protected] |
Definition at line 1915 of file tablefind.cpp.
{ #ifndef GRAPHICS_DISABLED ScrollView::Color color = default_color; // Iterate the ColPartitions in the grid. GridSearch<ColPartition, ColPartition_CLIST, ColPartition_C_IT> gsearch(grid); gsearch.StartFullSearch(); ColPartition* part = NULL; while ((part = gsearch.NextFullSearch()) != NULL) { color = default_color; if (part->type() == PT_TABLE) color = table_color; const TBOX& box = part->bounding_box(); int left_x = box.left(); int right_x = box.right(); int top_y = box.top(); int bottom_y = box.bottom(); win->Brush(ScrollView::NONE); win->Pen(color); win->Rectangle(left_x, bottom_y, right_x, top_y); } win->UpdateWindow(); #endif }
void tesseract::TableFinder::DisplayColPartitions | ( | ScrollView * | win, |
ColPartitionGrid * | grid, | ||
ScrollView::Color | default_color | ||
) | [protected] |
Definition at line 1943 of file tablefind.cpp.
{ DisplayColPartitions(win, grid, default_color, ScrollView::YELLOW); }
void tesseract::TableFinder::DisplayColSegmentGrid | ( | ScrollView * | win, |
ColSegmentGrid * | grid, | ||
ScrollView::Color | color | ||
) | [protected] |
Definition at line 1890 of file tablefind.cpp.
{ #ifndef GRAPHICS_DISABLED // Iterate the ColPartitions in the grid. GridSearch<ColSegment, ColSegment_CLIST, ColSegment_C_IT> gsearch(grid); gsearch.StartFullSearch(); ColSegment* seg = NULL; while ((seg = gsearch.NextFullSearch()) != NULL) { const TBOX& box = seg->bounding_box(); int left_x = box.left(); int right_x = box.right(); int top_y = box.top(); int bottom_y = box.bottom(); win->Brush(ScrollView::NONE); win->Pen(color); win->Rectangle(left_x, bottom_y, right_x, top_y); } win->UpdateWindow(); #endif }
void tesseract::TableFinder::DisplayColSegments | ( | ScrollView * | win, |
ColSegment_LIST * | cols, | ||
ScrollView::Color | color | ||
) | [protected] |
Definition at line 1870 of file tablefind.cpp.
{ #ifndef GRAPHICS_DISABLED win->Pen(color); win->Brush(ScrollView::NONE); ColSegment_IT it(segments); for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) { ColSegment* col = it.data(); const TBOX& box = col->bounding_box(); int left_x = box.left(); int right_x = box.right(); int top_y = box.top(); int bottom_y = box.bottom(); win->Rectangle(left_x, bottom_y, right_x, top_y); } win->UpdateWindow(); #endif }
void tesseract::TableFinder::FilterFalseAlarms | ( | ) | [protected] |
Definition at line 988 of file tablefind.cpp.
{ FilterParagraphEndings(); FilterHeaderAndFooter(); // TODO(nbeato): Fully justified text as non-table? }
void tesseract::TableFinder::FilterHeaderAndFooter | ( | ) | [protected] |
Definition at line 1074 of file tablefind.cpp.
{ // Consider top-most text colpartition as header and bottom most as footer ColPartition* header = NULL; ColPartition* footer = NULL; int max_top = MIN_INT32; int min_bottom = MAX_INT32; ColPartitionGridSearch gsearch(&clean_part_grid_); gsearch.StartFullSearch(); ColPartition* part = NULL; while ((part = gsearch.NextFullSearch()) != NULL) { if (!part->IsTextType()) continue; // Consider only text partitions int top = part->bounding_box().top(); int bottom = part->bounding_box().bottom(); if (top > max_top) { max_top = top; header = part; } if (bottom < min_bottom) { min_bottom = bottom; footer = part; } } if (header) header->clear_table_type(); if (footer) footer->clear_table_type(); }
void tesseract::TableFinder::FilterParagraphEndings | ( | ) | [protected] |
Definition at line 994 of file tablefind.cpp.
{ // Detect last line of paragraph // Iterate the ColPartitions in the grid. ColPartitionGridSearch gsearch(&clean_part_grid_); gsearch.StartFullSearch(); ColPartition* part = NULL; while ((part = gsearch.NextFullSearch()) != NULL) { if (part->type() != PT_TABLE) continue; // Consider only table partitions // Paragraph ending should have flowing text above it. ColPartition* upper_part = part->nearest_neighbor_above(); if (!upper_part) continue; if (upper_part->type() != PT_FLOWING_TEXT) continue; if (upper_part->bounding_box().width() < 2 * part->bounding_box().width()) continue; // Check if its the last line of a paragraph. // In most cases, a paragraph ending should be left-aligned to text line // above it. Sometimes, it could be a 2 line paragraph, in which case // the line above it is indented. // To account for that, check if the partition center is to // the left of the one above it. int mid = (part->bounding_box().left() + part->bounding_box().right()) / 2; int upper_mid = (upper_part->bounding_box().left() + upper_part->bounding_box().right()) / 2; int current_spacing = 0; // spacing of the current line to margin int upper_spacing = 0; // spacing of the previous line to the margin if (left_to_right_language_) { // Left to right languages, use mid - left to figure out the distance // the middle is from the left margin. int left = MIN(part->bounding_box().left(), upper_part->bounding_box().left()); current_spacing = mid - left; upper_spacing = upper_mid - left; } else { // Right to left languages, use right - mid to figure out the distance // the middle is from the right margin. int right = MAX(part->bounding_box().right(), upper_part->bounding_box().right()); current_spacing = right - mid; upper_spacing = right - upper_mid; } if (current_spacing * kParagraphEndingPreviousLineRatio > upper_spacing) continue; // Paragraphs should have similar fonts. if (!part->MatchingSizes(*upper_part) || !part->MatchingStrokeWidth(*upper_part, kStrokeWidthFractionalTolerance, kStrokeWidthConstantTolerance)) { continue; } // The last line of a paragraph should be left aligned. // TODO(nbeato): This would be untrue if the text was right aligned. // How often is that? if (part->space_to_left() > kMaxParagraphEndingLeftSpaceMultiple * part->median_size()) continue; // The line above it should be right aligned (assuming justified format). // Since we can't assume justified text, we compare whitespace to text. // The above line should have majority spanning text (or the current // line could have fit on the previous line). So compare // whitespace to text. if (upper_part->bounding_box().width() < kMinParagraphEndingTextToWhitespaceRatio * upper_part->space_to_right()) continue; // Ledding above the line should be less than ledding below if (part->space_above() >= part->space_below() || part->space_above() > 2 * global_median_ledding_) continue; // If all checks failed, it is probably text. part->clear_table_type(); } }
void tesseract::TableFinder::FindNeighbors | ( | ) | [protected] |
Definition at line 766 of file tablefind.cpp.
{ ColPartitionGridSearch gsearch(&clean_part_grid_); gsearch.StartFullSearch(); ColPartition* part = NULL; while ((part = gsearch.NextFullSearch()) != NULL) { // TODO(nbeato): Rename this function, meaning is different now. // IT is finding nearest neighbors its own way //SetVerticalSpacing(part); ColPartition* upper = part->SingletonPartner(true); if (upper) part->set_nearest_neighbor_above(upper); ColPartition* lower = part->SingletonPartner(false); if (lower) part->set_nearest_neighbor_below(lower); } }
bool tesseract::TableFinder::GapInXProjection | ( | int * | xprojection, |
int | length | ||
) | [protected] |
Definition at line 1767 of file tablefind.cpp.
{ // Find peak value of the histogram int peak_value = 0; for (int i = 0; i < length; i++) { if (xprojection[i] > peak_value) { peak_value = xprojection[i]; } } // Peak value represents the maximum number of horizontally // overlapping colpartitions, so this can be considered as the // number of rows in the table if (peak_value < kMinRowsInTable) return false; double projection_threshold = kSmallTableProjectionThreshold * peak_value; if (peak_value >= kLargeTableRowCount) projection_threshold = kLargeTableProjectionThreshold * peak_value; // Threshold the histogram for (int i = 0; i < length; i++) { xprojection[i] = (xprojection[i] >= projection_threshold) ? 1 : 0; } // Find the largest run of zeros between two ones int largest_gap = 0; int run_start = -1; for (int i = 1; i < length; i++) { // detect start of a run of zeros if (xprojection[i - 1] && !xprojection[i]) { run_start = i; } // detect end of a run of zeros and update the value of largest gap if (run_start != -1 && !xprojection[i - 1] && xprojection[i]) { int gap = i - run_start; if (gap > largest_gap) largest_gap = gap; run_start = -1; } } return largest_gap > kMaxXProjectionGapFactor * global_median_xheight_; }
void tesseract::TableFinder::GetColumnBlocks | ( | ColPartitionSet ** | columns, |
ColSegment_LIST * | col_segments | ||
) | [protected] |
Definition at line 523 of file tablefind.cpp.
{ for (int i = 0; i < gridheight(); ++i) { ColPartitionSet* columns = all_columns[i]; if (columns != NULL) { ColSegment_LIST new_blocks; // Get boxes from the current vertical position on the grid columns->GetColumnBoxes(i * gridsize(), (i+1) * gridsize(), &new_blocks); // Merge the new_blocks boxes into column_blocks if they are well-aligned GroupColumnBlocks(&new_blocks, column_blocks); } } }
void tesseract::TableFinder::GetTableColumns | ( | ColSegment_LIST * | table_columns | ) | [protected] |
Definition at line 1273 of file tablefind.cpp.
{ ColSegment_IT it(table_columns); // Iterate the ColPartitions in the grid. GridSearch<ColPartition, ColPartition_CLIST, ColPartition_C_IT> gsearch(&clean_part_grid_); gsearch.StartFullSearch(); ColPartition* part; while ((part = gsearch.NextFullSearch()) != NULL) { if (part->inside_table_column() || part->type() != PT_TABLE) continue; // prevent a partition to be assigned to multiple columns const TBOX& box = part->bounding_box(); ColSegment* col = new ColSegment(); col->InsertBox(box); part->set_inside_table_column(true); // Start a search below the current cell to find bottom neighbours // Note: a full search will always process things above it first, so // this should be starting at the highest cell and working its way down. GridSearch<ColPartition, ColPartition_CLIST, ColPartition_C_IT> vsearch(&clean_part_grid_); vsearch.StartVerticalSearch(box.left(), box.right(), box.bottom()); ColPartition* neighbor = NULL; bool found_neighbours = false; while ((neighbor = vsearch.NextVerticalSearch(true)) != NULL) { // only consider neighbors not assigned to any column yet if (neighbor->inside_table_column()) continue; // Horizontal lines should not break the flow if (neighbor->IsHorizontalLine()) continue; // presence of a non-table neighbor marks the end of current // table column if (neighbor->type() != PT_TABLE) break; // add the neighbor partition to the table column const TBOX& neighbor_box = neighbor->bounding_box(); col->InsertBox(neighbor_box); neighbor->set_inside_table_column(true); found_neighbours = true; } if (found_neighbours) { it.add_after_then_move(col); } else { part->set_inside_table_column(false); delete col; } } }
void tesseract::TableFinder::GetTableRegions | ( | ColSegment_LIST * | table_columns, |
ColSegment_LIST * | table_regions | ||
) | [protected] |
Definition at line 1323 of file tablefind.cpp.
{ ColSegment_IT cit(table_columns); ColSegment_IT rit(table_regions); // Iterate through column blocks GridSearch<ColSegment, ColSegment_CLIST, ColSegment_C_IT> gsearch(&col_seg_grid_); gsearch.StartFullSearch(); ColSegment* part; int page_height = tright().y() - bleft().y(); ASSERT_HOST(page_height > 0); // create a bool array to hold projection on y-axis bool* table_region = new bool[page_height]; while ((part = gsearch.NextFullSearch()) != NULL) { TBOX part_box = part->bounding_box(); // reset the projection array for (int i = 0; i < page_height; i++) { table_region[i] = false; } // iterate through all table columns to find regions in the current // page column block cit.move_to_first(); for (cit.mark_cycle_pt(); !cit.cycled_list(); cit.forward()) { TBOX col_box = cit.data()->bounding_box(); // find intersection region of table column and page column TBOX intersection_box = col_box.intersection(part_box); // project table column on the y-axis for (int i = intersection_box.bottom(); i < intersection_box.top(); i++) { table_region[i - bleft().y()] = true; } } // set x-limits of table regions to page column width TBOX current_table_box; current_table_box.set_left(part_box.left()); current_table_box.set_right(part_box.right()); // go through the y-axis projection to find runs of table // regions. Each run makes one table region. for (int i = 1; i < page_height; i++) { // detect start of a table region if (!table_region[i - 1] && table_region[i]) { current_table_box.set_bottom(i + bleft().y()); } // TODO(nbeato): Is it guaranteed that the last row is not a table region? // detect end of a table region if (table_region[i - 1] && !table_region[i]) { current_table_box.set_top(i + bleft().y()); if (!current_table_box.null_box()) { ColSegment* seg = new ColSegment(); seg->InsertBox(current_table_box); rit.add_after_then_move(seg); } } } } delete[] table_region; }
int tesseract::TableFinder::gridheight | ( | ) | const [protected] |
Definition at line 384 of file tablefind.cpp.
{ return clean_part_grid_.gridheight(); }
void tesseract::TableFinder::GridMergeColumnBlocks | ( | ) | [protected] |
Definition at line 1195 of file tablefind.cpp.
{ int margin = gridsize(); // Iterate the Column Blocks in the grid. GridSearch<ColSegment, ColSegment_CLIST, ColSegment_C_IT> gsearch(&col_seg_grid_); gsearch.StartFullSearch(); ColSegment* seg; while ((seg = gsearch.NextFullSearch()) != NULL) { if (seg->type() != COL_TEXT) continue; // only consider text blocks for split detection bool neighbor_found = false; bool modified = false; // Modified at least once // keep expanding current box as long as neighboring table columns // are found above or below it. do { TBOX box = seg->bounding_box(); // slightly expand the search region vertically int top_range = MIN(box.top() + margin, tright().y()); int bottom_range = MAX(box.bottom() - margin, bleft().y()); box.set_top(top_range); box.set_bottom(bottom_range); neighbor_found = false; GridSearch<ColSegment, ColSegment_CLIST, ColSegment_C_IT> rectsearch(&col_seg_grid_); rectsearch.StartRectSearch(box); ColSegment* neighbor = NULL; while ((neighbor = rectsearch.NextRectSearch()) != NULL) { if (neighbor == seg) continue; const TBOX& neighbor_box = neighbor->bounding_box(); // If the neighbor box significantly overlaps with the current // box (due to the expansion of the current box in the // previous iteration of this loop), remove the neighbor box // and expand the current box to include it. if (neighbor_box.overlap_fraction(box) >= 0.9) { seg->InsertBox(neighbor_box); modified = true; rectsearch.RemoveBBox(); gsearch.RepositionIterator(); delete neighbor; continue; } // Only expand if the neighbor box is of table type if (neighbor->type() != COL_TABLE) continue; // Insert the neighbor box into the current column block if (neighbor_box.major_x_overlap(box) && !box.contains(neighbor_box)) { seg->InsertBox(neighbor_box); neighbor_found = true; modified = true; rectsearch.RemoveBBox(); gsearch.RepositionIterator(); delete neighbor; } } } while (neighbor_found); if (modified) { // Because the box has changed, it has to be removed first. gsearch.RemoveBBox(); col_seg_grid_.InsertBBox(true, true, seg); gsearch.RepositionIterator(); } } }
void tesseract::TableFinder::GridMergeTableRegions | ( | ) | [protected] |
Definition at line 1386 of file tablefind.cpp.
{ // Iterate the table regions in the grid. GridSearch<ColSegment, ColSegment_CLIST, ColSegment_C_IT> gsearch(&table_grid_); gsearch.StartFullSearch(); ColSegment* seg = NULL; while ((seg = gsearch.NextFullSearch()) != NULL) { bool neighbor_found = false; bool modified = false; // Modified at least once do { // Start a rectangle search x-bounded by the image and y by the table const TBOX& box = seg->bounding_box(); TBOX search_region(box); search_region.set_left(bleft().x()); search_region.set_right(tright().x()); neighbor_found = false; GridSearch<ColSegment, ColSegment_CLIST, ColSegment_C_IT> rectsearch(&table_grid_); rectsearch.StartRectSearch(search_region); ColSegment* neighbor = NULL; while ((neighbor = rectsearch.NextRectSearch()) != NULL) { if (neighbor == seg) continue; const TBOX& neighbor_box = neighbor->bounding_box(); // Check if a neighbor box has a large overlap with the table // region. This may happen as a result of merging two table // regions in the previous iteration. if (neighbor_box.overlap_fraction(box) >= 0.9) { seg->InsertBox(neighbor_box); rectsearch.RemoveBBox(); gsearch.RepositionIterator(); delete neighbor; modified = true; continue; } // Check if two table regions belong together based on a common // horizontal ruling line if (BelongToOneTable(box, neighbor_box)) { seg->InsertBox(neighbor_box); neighbor_found = true; modified = true; rectsearch.RemoveBBox(); gsearch.RepositionIterator(); delete neighbor; } } } while (neighbor_found); if (modified) { // Because the box has changed, it has to be removed first. gsearch.RemoveBBox(); table_grid_.InsertBBox(true, true, seg); gsearch.RepositionIterator(); } } }
int tesseract::TableFinder::gridsize | ( | ) | const [protected] |
Definition at line 378 of file tablefind.cpp.
{ return clean_part_grid_.gridsize(); }
int tesseract::TableFinder::gridwidth | ( | ) | const [protected] |
Definition at line 381 of file tablefind.cpp.
{ return clean_part_grid_.gridwidth(); }
void tesseract::TableFinder::GroupColumnBlocks | ( | ColSegment_LIST * | current_segments, |
ColSegment_LIST * | col_segments | ||
) | [protected] |
Definition at line 538 of file tablefind.cpp.
{ ColSegment_IT src_it(new_blocks); ColSegment_IT dest_it(column_blocks); // iterate through the source list for (src_it.mark_cycle_pt(); !src_it.cycled_list(); src_it.forward()) { ColSegment* src_seg = src_it.data(); TBOX src_box = src_seg->bounding_box(); bool match_found = false; // iterate through the destination list to find a matching column block for (dest_it.mark_cycle_pt(); !dest_it.cycled_list(); dest_it.forward()) { ColSegment* dest_seg = dest_it.data(); TBOX dest_box = dest_seg->bounding_box(); if (ConsecutiveBoxes(src_box, dest_box)) { // If matching block is found, insert the current block into it // and delete the soure block dest_seg->InsertBox(src_box); match_found = true; delete src_it.extract(); break; } } // If no match is found, just append the source block to column_blocks if (!match_found) { dest_it.add_after_then_move(src_it.extract()); } } }
Definition at line 1519 of file tablefind.cpp.
{ // TODO(nbeato): The growing code is a bit excessive right now. // By removing these lines, the partitions considered need // to have some overlap or be special cases. These lines could // be added again once a check is put in place to make sure that // growing tables don't stomp on a lot of non-table partitions. // search for horizontal ruling lines within the vertical margin // int vertical_margin = kRulingVerticalMargin * gridsize(); TBOX search_box = table_box; // int top = MIN(search_box.top() + vertical_margin, tright().y()); // int bottom = MAX(search_box.bottom() - vertical_margin, bleft().y()); // search_box.set_top(top); // search_box.set_bottom(bottom); GrowTableToIncludePartials(table_box, search_box, result_box); GrowTableToIncludeLines(table_box, search_box, result_box); IncludeLeftOutColumnHeaders(result_box); }
void tesseract::TableFinder::GrowTableToIncludeLines | ( | const TBOX & | table_box, |
const TBOX & | search_range, | ||
TBOX * | result_box | ||
) | [protected] |
Definition at line 1569 of file tablefind.cpp.
{ ColPartitionGridSearch rsearch(&leader_and_ruling_grid_); rsearch.SetUniqueMode(true); rsearch.StartRectSearch(search_range); ColPartition* part = NULL; while ((part = rsearch.NextRectSearch()) != NULL) { // TODO(nbeato) This should also do vertical, but column // boundaries are breaking things. This function needs to be // updated to allow vertical lines as well. if (!part->IsLineType()) continue; // Avoid the following function call if the result of the // function is irrelevant. const TBOX& part_box = part->bounding_box(); if (result_box->contains(part_box)) continue; // Include a partially overlapping horizontal line only if the // extra ColPartitions that will be included due to expansion // have large side spacing w.r.t. columns containing them. if (HLineBelongsToTable(*part, table_box)) *result_box = result_box->bounding_union(part_box); // TODO(nbeato): Vertical } }
void tesseract::TableFinder::GrowTableToIncludePartials | ( | const TBOX & | table_box, |
const TBOX & | search_range, | ||
TBOX * | result_box | ||
) | [protected] |
Definition at line 1541 of file tablefind.cpp.
{ // Rulings are in a different grid, so search 2 grids for rulings, text, // and table partitions that are not entirely within the new box. for (int i = 0; i < 2; ++i) { ColPartitionGrid* grid = (i == 0) ? &fragmented_text_grid_ : &leader_and_ruling_grid_; ColPartitionGridSearch rectsearch(grid); rectsearch.StartRectSearch(search_range); ColPartition* part = NULL; while ((part = rectsearch.NextRectSearch()) != NULL) { // Only include text and table types. if (part->IsImageType()) continue; const TBOX& part_box = part->bounding_box(); // Include partition in the table if more than half of it // is covered by the table if (part_box.overlap_fraction(table_box) > kMinOverlapWithTable) { *result_box = result_box->bounding_union(part_box); continue; } } } }
bool tesseract::TableFinder::HasLeaderAdjacent | ( | const ColPartition & | part | ) | [protected] |
Definition at line 946 of file tablefind.cpp.
{ if (part.flow() == BTFT_LEADER) return true; // Search range is left and right bounded by an offset of the // median xheight. This offset is to allow some tolerance to the // the leaders on the page in the event that the alignment is still // a bit off. const TBOX& box = part.bounding_box(); const int search_size = kAdjacentLeaderSearchPadding * global_median_xheight_; const int top = box.top() + search_size; const int bottom = box.bottom() - search_size; ColPartitionGridSearch hsearch(&leader_and_ruling_grid_); for (int direction = 0; direction < 2; ++direction) { bool right_to_left = (direction == 0); int x = right_to_left ? box.right() : box.left(); hsearch.StartSideSearch(x, bottom, top); ColPartition* leader = NULL; while ((leader = hsearch.NextSideSearch(right_to_left)) != NULL) { // This should not happen, they are in different grids. ASSERT_HOST(&part != leader); // The leader could be a horizontal ruling in the grid. // Make sure it is actually a leader. if (leader->flow() != BTFT_LEADER) continue; // Make sure the leader shares a page column with the partition, // otherwise we are spreading across columns. if (!part.IsInSameColumnAs(*leader)) break; // There should be a significant vertical overlap if (!leader->VSignificantCoreOverlap(part)) continue; // Leader passed all tests, so it is adjacent. return true; } } // No leaders are adjacent to the given partition. return false; }
bool tesseract::TableFinder::HasWideOrNoInterWordGap | ( | ColPartition * | part | ) | const [protected] |
Definition at line 857 of file tablefind.cpp.
{ // Should only get text partitions. ASSERT_HOST(part->IsTextType()); // Blob access BLOBNBOX_CLIST* part_boxes = part->boxes(); BLOBNBOX_C_IT it(part_boxes); // Check if this is a relatively small partition (such as a single word) if (part->bounding_box().width() < kMinBoxesInTextPartition * part->median_size() && part_boxes->length() < kMinBoxesInTextPartition) return true; // Variables used to compute inter-blob spacing. int current_x0 = -1; int current_x1 = -1; int previous_x1 = -1; // Stores the maximum gap detected. int largest_partition_gap_found = -1; // Text partition gap limits. If this is text (and not a table), // there should be at least one gap larger than min_gap and no gap // larger than max_gap. const double max_gap = kMaxGapInTextPartition * part->median_size(); const double min_gap = kMinMaxGapInTextPartition * part->median_size(); for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) { BLOBNBOX* blob = it.data(); current_x0 = blob->bounding_box().left(); current_x1 = blob->bounding_box().right(); if (previous_x1 != -1) { int gap = current_x0 - previous_x1; // TODO(nbeato): Boxes may overlap? Huh? // For example, mag.3B 8003_033.3B.tif in UNLV data. The titles/authors // on the top right of the page are filtered out with this line. // Note 2: Iterating over blobs in a partition, so we are looking for // spacing between the words. if (gap < 0) { // More likely case, the blobs slightly overlap. This can happen // with diacritics (accents) or broken alphabet symbols (characters). // Merge boxes together by taking max of right sides. if (-gap < part->median_size() * kMaxBlobOverlapFactor) { previous_x1 = MAX(previous_x1, current_x1); continue; } // Extreme case, blobs overlap significantly in the same partition... // This should not happen often (if at all), but it does. // TODO(nbeato): investigate cases when this happens. else { // The behavior before was to completely ignore this case. } } // If a large enough gap is found, mark it as a table cell (return true) if (gap > max_gap) return true; if (gap > largest_partition_gap_found) largest_partition_gap_found = gap; } previous_x1 = current_x1; } // Since no large gap was found, return false if the partition is too // long to be a data cell if (part->bounding_box().width() > kMaxBoxesInDataPartition * part->median_size() || part_boxes->length() > kMaxBoxesInDataPartition) return false; // A partition may be a single blob. In this case, it's an isolated symbol // or non-text (such as a ruling or image). // Detect these as table partitions? Shouldn't this be case by case? // The behavior before was to ignore this, making max_partition_gap < 0 // and implicitly return true. Just making it explicit. if (largest_partition_gap_found == -1) return true; // return true if the maximum gap found is smaller than the minimum allowed // max_gap in a text partition. This indicates that there is no signficant // space in the partition, hence it is likely a single word. return largest_partition_gap_found < min_gap; }
bool tesseract::TableFinder::HLineBelongsToTable | ( | const ColPartition & | part, |
const TBOX & | table_box | ||
) | [protected] |
Definition at line 1599 of file tablefind.cpp.
{ if (!part.IsHorizontalLine()) return false; const TBOX& part_box = part.bounding_box(); if (!part_box.major_x_overlap(table_box)) return false; // Do not consider top-most horizontal line since it usually // originates from noise. // TODO(nbeato): I had to comment this out because the ruling grid doesn't // have neighbors solved. // if (!part.nearest_neighbor_above()) // return false; const TBOX bbox = part_box.bounding_union(table_box); // In the "unioned table" box (the table extents expanded by the line), // keep track of how many partitions have significant padding to the left // and right. If more than half of the partitions covered by the new table // have significant spacing, the line belongs to the table and the table // grows to include all of the partitions. int num_extra_partitions = 0; int extra_space_to_right = 0; int extra_space_to_left = 0; // Rulings are in a different grid, so search 2 grids for rulings, text, // and table partitions that are introduced by the new box. for (int i = 0; i < 2; ++i) { ColPartitionGrid* grid = (i == 0) ? &clean_part_grid_ : &leader_and_ruling_grid_; // Start a rect search on bbox ColPartitionGridSearch rectsearch(grid); rectsearch.SetUniqueMode(true); rectsearch.StartRectSearch(bbox); ColPartition* extra_part = NULL; while ((extra_part = rectsearch.NextRectSearch()) != NULL) { // ColPartition already in table const TBOX& extra_part_box = extra_part->bounding_box(); if (extra_part_box.overlap_fraction(table_box) > kMinOverlapWithTable) continue; // Non-text ColPartitions do not contribute if (extra_part->IsImageType()) continue; // Consider this partition. num_extra_partitions++; // presence of a table cell is a strong hint, so just increment the scores // without looking at the spacing. if (extra_part->type() == PT_TABLE || extra_part->IsLineType()) { extra_space_to_right++; extra_space_to_left++; continue; } int space_threshold = kSideSpaceMargin * part.median_size(); if (extra_part->space_to_right() > space_threshold) extra_space_to_right++; if (extra_part->space_to_left() > space_threshold) extra_space_to_left++; } } // tprintf("%d %d %d\n", // num_extra_partitions,extra_space_to_right,extra_space_to_left); return (extra_space_to_right > num_extra_partitions / 2) || (extra_space_to_left > num_extra_partitions / 2); }
void tesseract::TableFinder::IncludeLeftOutColumnHeaders | ( | TBOX * | table_box | ) | [protected] |
Definition at line 1663 of file tablefind.cpp.
{ // Start a search above the current table to look for column headers ColPartitionGridSearch vsearch(&clean_part_grid_); vsearch.StartVerticalSearch(table_box->left(), table_box->right(), table_box->top()); ColPartition* neighbor = NULL; ColPartition* previous_neighbor = NULL; while ((neighbor = vsearch.NextVerticalSearch(false)) != NULL) { // Max distance to find a table heading. const int max_distance = kMaxColumnHeaderDistance * neighbor->median_size(); int table_top = table_box->top(); const TBOX& box = neighbor->bounding_box(); // Do not continue if the next box is way above if (box.bottom() - table_top > max_distance) break; // Unconditionally include partitions of type TABLE or LINE // TODO(faisal): add some reasonable conditions here if (neighbor->type() == PT_TABLE || neighbor->IsLineType()) { table_box->set_top(box.top()); previous_neighbor = NULL; continue; } // If there are two text partitions, one above the other, without a table // cell on their left or right side, consider them a barrier and quit if (previous_neighbor == NULL) { previous_neighbor = neighbor; } else { const TBOX& previous_box = previous_neighbor->bounding_box(); if (!box.major_y_overlap(previous_box)) break; } } }
void tesseract::TableFinder::Init | ( | int | grid_size, |
const ICOORD & | bottom_left, | ||
const ICOORD & | top_right | ||
) |
Definition at line 190 of file tablefind.cpp.
{ // Initialize clean partitions list and grid clean_part_grid_.Init(grid_size, bottom_left, top_right); leader_and_ruling_grid_.Init(grid_size, bottom_left, top_right); fragmented_text_grid_.Init(grid_size, bottom_left, top_right); col_seg_grid_.Init(grid_size, bottom_left, top_right); table_grid_.Init(grid_size, bottom_left, top_right); }
void tesseract::TableFinder::InitializePartitions | ( | ColPartitionSet ** | all_columns | ) | [protected] |
Definition at line 579 of file tablefind.cpp.
{ FindNeighbors(); SetPartitionSpacings(&clean_part_grid_, all_columns); SetGlobalSpacings(&clean_part_grid_); }
void tesseract::TableFinder::InsertCleanPartitions | ( | ColPartitionGrid * | grid, |
TO_BLOCK * | block | ||
) |
Definition at line 202 of file tablefind.cpp.
{ // Calculate stats. This lets us filter partitions in AllowTextPartition() // and filter blobs in AllowBlob(). SetGlobalSpacings(grid); // Iterate the ColPartitions in the grid. ColPartitionGridSearch gsearch(grid); gsearch.SetUniqueMode(true); gsearch.StartFullSearch(); ColPartition* part = NULL; while ((part = gsearch.NextFullSearch()) != NULL) { // Reject partitions with nothing useful inside of them. if (part->blob_type() == BRT_NOISE || part->bounding_box().area() <= 0) continue; ColPartition* clean_part = part->ShallowCopy(); ColPartition* leader_part = NULL; if (part->IsLineType()) { InsertRulingPartition(clean_part); continue; } // Insert all non-text partitions to clean_parts if (!part->IsTextType()) { InsertImagePartition(clean_part); continue; } // Insert text colpartitions after removing noisy components from them // The leaders are split into a separate grid. BLOBNBOX_CLIST* part_boxes = part->boxes(); BLOBNBOX_C_IT pit(part_boxes); for (pit.mark_cycle_pt(); !pit.cycled_list(); pit.forward()) { BLOBNBOX *pblob = pit.data(); // Bad blobs... happens in UNLV set. // news.3G1, page 17 (around x=6) if (!AllowBlob(*pblob)) continue; if (pblob->flow() == BTFT_LEADER) { if (leader_part == NULL) { leader_part = part->ShallowCopy(); leader_part->set_flow(BTFT_LEADER); } leader_part->AddBox(pblob); } else if (pblob->region_type() != BRT_NOISE) { clean_part->AddBox(pblob); } } clean_part->ComputeLimits(); ColPartition* fragmented = clean_part->CopyButDontOwnBlobs(); InsertTextPartition(clean_part); SplitAndInsertFragmentedTextPartition(fragmented); if (leader_part != NULL) { // TODO(nbeato): Note that ComputeLimits does not update the column // information. So the leader may appear to span more columns than it // really does later on when IsInSameColumnAs gets called to test // for adjacent leaders. leader_part->ComputeLimits(); InsertLeaderPartition(leader_part); } } // Make the partition partners better for upper and lower neighbors. clean_part_grid_.FindPartitionPartners(); clean_part_grid_.RefinePartitionPartners(false); }
void tesseract::TableFinder::InsertFragmentedTextPartition | ( | ColPartition * | part | ) | [protected] |
Definition at line 402 of file tablefind.cpp.
{ ASSERT_HOST(part != NULL); if (AllowTextPartition(*part)) { fragmented_text_grid_.InsertBBox(true, true, part); } else { delete part; } }
void tesseract::TableFinder::InsertImagePartition | ( | ColPartition * | part | ) | [protected] |
Definition at line 421 of file tablefind.cpp.
{ // NOTE: If images are placed into a different grid in the future, // the function SetPartitionSpacings needs to be updated. It should // be the only thing that cares about image partitions. clean_part_grid_.InsertBBox(true, true, part); }
void tesseract::TableFinder::InsertLeaderPartition | ( | ColPartition * | part | ) | [protected] |
Definition at line 410 of file tablefind.cpp.
{ ASSERT_HOST(part != NULL); if (!part->IsEmpty() && part->bounding_box().area() > 0) { leader_and_ruling_grid_.InsertBBox(true, true, part); } else { delete part; } }
void tesseract::TableFinder::InsertRulingPartition | ( | ColPartition * | part | ) | [protected] |
Definition at line 418 of file tablefind.cpp.
{ leader_and_ruling_grid_.InsertBBox(true, true, part); }
void tesseract::TableFinder::InsertTextPartition | ( | ColPartition * | part | ) | [protected] |
Definition at line 394 of file tablefind.cpp.
{ ASSERT_HOST(part != NULL); if (AllowTextPartition(*part)) { clean_part_grid_.InsertBBox(true, true, part); } else { delete part; } }
void tesseract::TableFinder::LocateTables | ( | ColPartitionGrid * | grid, |
ColPartitionSet ** | columns, | ||
WidthCallback * | width_cb, | ||
const FCOORD & | reskew | ||
) |
Definition at line 268 of file tablefind.cpp.
{ // initialize spacing, neighbors, and columns InitializePartitions(all_columns); if (textord_show_tables) { ScrollView* table_win = MakeWindow(0, 300, "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"); DisplayColPartitions(table_win, &fragmented_text_grid_, ScrollView::BLUE); } // mark, filter, and smooth candidate table partitions MarkTablePartitions(); // Make single-column blocks from good_columns_ partitions. col_segments are // moved to a grid later which takes the ownership ColSegment_LIST column_blocks; GetColumnBlocks(all_columns, &column_blocks); // Set the ratio of candidate table partitions in each column SetColumnsType(&column_blocks); // Move column segments to col_seg_grid_ MoveColSegmentsToGrid(&column_blocks, &col_seg_grid_); // Detect split in column layout that might have occurred due to the // presence of a table. In such a case, merge the corresponding columns. GridMergeColumnBlocks(); // Group horizontally overlapping table partitions into table columns. // table_columns created here get deleted at the end of this method. ColSegment_LIST table_columns; GetTableColumns(&table_columns); // Within each column, mark the range table regions occupy based on the // table columns detected. table_regions are moved to a grid later which // takes the ownership ColSegment_LIST table_regions; GetTableRegions(&table_columns, &table_regions); if (textord_tablefind_show_mark) { ScrollView* table_win = MakeWindow(1200, 300, "Table Columns and Regions"); DisplayColSegments(table_win, &table_columns, ScrollView::DARK_TURQUOISE); DisplayColSegments(table_win, &table_regions, ScrollView::YELLOW); } // Merge table regions across columns for tables spanning multiple // columns MoveColSegmentsToGrid(&table_regions, &table_grid_); GridMergeTableRegions(); // Adjust table boundaries by including nearby horizontal lines and left // out column headers AdjustTableBoundaries(); GridMergeTableRegions(); if (textord_tablefind_recognize_tables) { // Remove false alarms consiting of a single column DeleteSingleColumnTables(); if (textord_show_tables) { ScrollView* table_win = MakeWindow(1200, 300, "Detected Table Locations"); DisplayColPartitions(table_win, &clean_part_grid_, ScrollView::BLUE); DisplayColSegments(table_win, &table_columns, ScrollView::KHAKI); table_grid_.DisplayBoxes(table_win); } // Find table grid structure and reject tables that are malformed. RecognizeTables(); GridMergeTableRegions(); RecognizeTables(); if (textord_show_tables) { ScrollView* table_win = MakeWindow(1400, 600, "Recognized Tables"); DisplayColPartitions(table_win, &clean_part_grid_, ScrollView::BLUE, ScrollView::BLUE); table_grid_.DisplayBoxes(table_win); } } else { // Remove false alarms consiting of a single column // TODO(nbeato): verify this is a NOP after structured table rejection. // Right now it isn't. If the recognize function is doing what it is // supposed to do, this function is obsolete. DeleteSingleColumnTables(); if (textord_show_tables) { ScrollView* table_win = MakeWindow(1500, 300, "Detected Tables"); DisplayColPartitions(table_win, &clean_part_grid_, ScrollView::BLUE, ScrollView::BLUE); table_grid_.DisplayBoxes(table_win); } } if (textord_dump_table_images) WriteToPix(reskew); // Merge all colpartitions in table regions to make them a single // colpartition and revert types of isolated table cells not // assigned to any table to their original types. MakeTableBlocks(grid, all_columns, width_cb); }
void tesseract::TableFinder::MakeTableBlocks | ( | ColPartitionGrid * | grid, |
ColPartitionSet ** | columns, | ||
WidthCallback * | width_cb | ||
) | [protected] |
Definition at line 2070 of file tablefind.cpp.
{ // Since we have table blocks already, remove table tags from all // colpartitions GridSearch<ColPartition, ColPartition_CLIST, ColPartition_C_IT> gsearch(grid); gsearch.StartFullSearch(); ColPartition* part = NULL; while ((part = gsearch.NextFullSearch()) != NULL) { if (part->type() == PT_TABLE) { part->clear_table_type(); } } // Now make a single colpartition out of each table block and remove // all colpartitions contained within a table GridSearch<ColSegment, ColSegment_CLIST, ColSegment_C_IT> table_search(&table_grid_); table_search.StartFullSearch(); ColSegment* table; while ((table = table_search.NextFullSearch()) != NULL) { TBOX table_box = table->bounding_box(); // Start a rect search on table_box GridSearch<ColPartition, ColPartition_CLIST, ColPartition_C_IT> rectsearch(grid); rectsearch.StartRectSearch(table_box); ColPartition* part; ColPartition* table_partition = NULL; while ((part = rectsearch.NextRectSearch()) != NULL) { // Do not consider image partitions if (!part->IsTextType()) continue; TBOX part_box = part->bounding_box(); // Include partition in the table if more than half of it // is covered by the table if (part_box.overlap_fraction(table_box) > kMinOverlapWithTable) { rectsearch.RemoveBBox(); if (table_partition) { table_partition->Absorb(part, width_cb); } else { table_partition = part; } } } // Insert table colpartition back to part_grid_ if (table_partition) { // To match the columns used when transforming to blocks, the new table // partition must have its first and last column set at the grid y that // corresponds to its bottom. const TBOX& table_box = table_partition->bounding_box(); int grid_x, grid_y; grid->GridCoords(table_box.left(), table_box.bottom(), &grid_x, &grid_y); table_partition->SetPartitionType(resolution_, all_columns[grid_y]); table_partition->set_table_type(); table_partition->set_blob_type(BRT_TEXT); table_partition->set_flow(BTFT_CHAIN); table_partition->SetBlobTypes(); grid->InsertBBox(true, true, table_partition); } } }
ScrollView * tesseract::TableFinder::MakeWindow | ( | int | x, |
int | y, | ||
const char * | window_name | ||
) | [protected] |
Definition at line 518 of file tablefind.cpp.
{ return clean_part_grid_.MakeWindow(x, y, window_name); }
void tesseract::TableFinder::MarkPartitionsUsingLocalInformation | ( | ) | [protected] |
Definition at line 827 of file tablefind.cpp.
{ // Iterate the ColPartitions in the grid. GridSearch<ColPartition, ColPartition_CLIST, ColPartition_C_IT> gsearch(&clean_part_grid_); gsearch.StartFullSearch(); ColPartition* part = NULL; while ((part = gsearch.NextFullSearch()) != NULL) { if (!part->IsTextType()) // Only consider text partitions continue; // Only consider partitions in dominant font size or smaller if (part->median_size() > kMaxTableCellXheight * global_median_xheight_) continue; // Mark partitions with a large gap, or no significant gap as // table partitions. // Comments: It produces several false alarms at: // - last line of a paragraph (fixed) // - single word section headings // - page headers and footers // - numbered equations // - line drawing regions // TODO(faisal): detect and fix above-mentioned cases if (HasWideOrNoInterWordGap(part) || HasLeaderAdjacent(*part)) { part->set_table_type(); } } }
void tesseract::TableFinder::MarkTablePartitions | ( | ) | [protected] |
Definition at line 789 of file tablefind.cpp.
{ MarkPartitionsUsingLocalInformation(); if (textord_tablefind_show_mark) { ScrollView* table_win = MakeWindow(300, 300, "Initial Table Partitions"); DisplayColPartitions(table_win, &clean_part_grid_, ScrollView::BLUE); DisplayColPartitions(table_win, &leader_and_ruling_grid_, ScrollView::AQUAMARINE); } FilterFalseAlarms(); if (textord_tablefind_show_mark) { ScrollView* table_win = MakeWindow(600, 300, "Filtered Table Partitions"); DisplayColPartitions(table_win, &clean_part_grid_, ScrollView::BLUE); DisplayColPartitions(table_win, &leader_and_ruling_grid_, ScrollView::AQUAMARINE); } SmoothTablePartitionRuns(); if (textord_tablefind_show_mark) { ScrollView* table_win = MakeWindow(900, 300, "Smoothed Table Partitions"); DisplayColPartitions(table_win, &clean_part_grid_, ScrollView::BLUE); DisplayColPartitions(table_win, &leader_and_ruling_grid_, ScrollView::AQUAMARINE); } FilterFalseAlarms(); if (textord_tablefind_show_mark || textord_show_tables) { ScrollView* table_win = MakeWindow(900, 300, "Final Table Partitions"); DisplayColPartitions(table_win, &clean_part_grid_, ScrollView::BLUE); DisplayColPartitions(table_win, &leader_and_ruling_grid_, ScrollView::AQUAMARINE); } }
void tesseract::TableFinder::MoveColSegmentsToGrid | ( | ColSegment_LIST * | segments, |
ColSegmentGrid * | col_seg_grid | ||
) | [protected] |
Definition at line 1176 of file tablefind.cpp.
{ ColSegment_IT it(segments); for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) { ColSegment* seg = it.extract(); col_seg_grid->InsertBBox(true, true, seg); } }
void tesseract::TableFinder::RecognizeTables | ( | ) | [protected] |
Definition at line 1816 of file tablefind.cpp.
{ ScrollView* table_win = NULL; if (textord_show_tables) { table_win = MakeWindow(0, 0, "Table Structure"); DisplayColPartitions(table_win, &fragmented_text_grid_, ScrollView::BLUE, ScrollView::LIGHT_BLUE); // table_grid_.DisplayBoxes(table_win); } 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()); // Loop over all of the tables and try to fit them. // Store the good tables here. ColSegment_CLIST good_tables; ColSegment_C_IT good_it(&good_tables); ColSegmentGridSearch gsearch(&table_grid_); gsearch.StartFullSearch(); ColSegment* found_table = NULL; while ((found_table = gsearch.NextFullSearch()) != NULL) { gsearch.RemoveBBox(); // The goal is to make the tables persistent in a list. // When that happens, this will move into the search loop. const TBOX& found_box = found_table->bounding_box(); StructuredTable* table_structure = recognizer.RecognizeTable(found_box); // Process a table. Good tables are inserted into the grid again later on // We can't change boxes in the grid while it is running a search. if (table_structure != NULL) { if (textord_show_tables) { table_structure->Display(table_win, ScrollView::LIME_GREEN); } found_table->set_bounding_box(table_structure->bounding_box()); delete table_structure; good_it.add_after_then_move(found_table); } else { delete found_table; } } // TODO(nbeato): MERGE!! There is awesome info now available for merging. // At this point, the grid is empty. We can safely insert the good tables // back into grid. for (good_it.mark_cycle_pt(); !good_it.cycled_list(); good_it.forward()) table_grid_.InsertBBox(true, true, good_it.extract()); }
void tesseract::TableFinder::set_global_median_blob_width | ( | int | width | ) | [protected] |
Definition at line 759 of file tablefind.cpp.
{ global_median_blob_width_ = width; }
void tesseract::TableFinder::set_global_median_ledding | ( | int | ledding | ) | [protected] |
Definition at line 762 of file tablefind.cpp.
{ global_median_ledding_ = ledding; }
void tesseract::TableFinder::set_global_median_xheight | ( | int | xheight | ) | [protected] |
Definition at line 756 of file tablefind.cpp.
{ global_median_xheight_ = xheight; }
void tesseract::TableFinder::set_left_to_right_language | ( | bool | order | ) |
Definition at line 186 of file tablefind.cpp.
{ left_to_right_language_ = order; }
void tesseract::TableFinder::set_resolution | ( | int | resolution | ) | [inline] |
Definition at line 138 of file tablefind.h.
{ resolution_ = resolution; }
void tesseract::TableFinder::SetColumnsType | ( | ColSegment_LIST * | col_segments | ) | [protected] |
Definition at line 1143 of file tablefind.cpp.
{ ColSegment_IT it(column_blocks); for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) { ColSegment* seg = it.data(); TBOX box = seg->bounding_box(); int num_table_cells = 0; int num_text_cells = 0; GridSearch<ColPartition, ColPartition_CLIST, ColPartition_C_IT> rsearch(&clean_part_grid_); rsearch.SetUniqueMode(true); rsearch.StartRectSearch(box); ColPartition* part = NULL; while ((part = rsearch.NextRectSearch()) != NULL) { if (part->type() == PT_TABLE) { num_table_cells++; } else if (part->type() == PT_FLOWING_TEXT) { num_text_cells++; } } // If a column block has no text or table partition in it, it is not needed // for table detection. if (!num_table_cells && !num_text_cells) { delete it.extract(); } else { seg->set_num_table_cells(num_table_cells); seg->set_num_text_cells(num_text_cells); // set column type based on the ratio of table to text cells seg->set_type(); } } }
void tesseract::TableFinder::SetGlobalSpacings | ( | ColPartitionGrid * | grid | ) | [protected] |
Definition at line 709 of file tablefind.cpp.
{ STATS xheight_stats(0, kMaxVerticalSpacing + 1); STATS width_stats(0, kMaxBlobWidth + 1); STATS ledding_stats(0, kMaxVerticalSpacing + 1); // Iterate the ColPartitions in the grid. ColPartitionGridSearch gsearch(grid); gsearch.SetUniqueMode(true); gsearch.StartFullSearch(); ColPartition* part = NULL; while ((part = gsearch.NextFullSearch()) != NULL) { // TODO(nbeato): HACK HACK HACK! medians are equal to partition length. // ComputeLimits needs to get called somewhere outside of TableFinder // to make sure the partitions are properly initialized. // When this is called, SmoothPartitionPartners dies in an assert after // table find runs. Alternative solution. // part->ComputeLimits(); if (part->IsTextType()) { // xheight_stats.add(part->median_size(), part->boxes_count()); // width_stats.add(part->median_width(), part->boxes_count()); // This loop can be removed when above issues are fixed. // Replace it with the 2 lines commented out above. BLOBNBOX_C_IT it(part->boxes()); for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) { xheight_stats.add(it.data()->bounding_box().height(), 1); width_stats.add(it.data()->bounding_box().width(), 1); } ledding_stats.add(part->space_above(), 1); ledding_stats.add(part->space_below(), 1); } } // Set estimates based on median of statistics obtained set_global_median_xheight(static_cast<int>(xheight_stats.median() + 0.5)); set_global_median_blob_width(static_cast<int>(width_stats.median() + 0.5)); set_global_median_ledding(static_cast<int>(ledding_stats.median() + 0.5)); #ifndef GRAPHICS_DISABLED if (textord_tablefind_show_stats) { const char* kWindowName = "X-height (R), X-width (G), and ledding (B)"; ScrollView* stats_win = MakeWindow(500, 10, kWindowName); xheight_stats.plot(stats_win, 10, 200, 2, 15, ScrollView::RED); width_stats.plot(stats_win, 10, 200, 2, 15, ScrollView::GREEN); ledding_stats.plot(stats_win, 10, 200, 2, 15, ScrollView::BLUE); } #endif // GRAPHICS_DISABLED }
void tesseract::TableFinder::SetPartitionSpacings | ( | ColPartitionGrid * | grid, |
ColPartitionSet ** | all_columns | ||
) | [static, protected] |
Definition at line 586 of file tablefind.cpp.
{ // Iterate the ColPartitions in the grid. ColPartitionGridSearch gsearch(grid); gsearch.StartFullSearch(); ColPartition* part = NULL; while ((part = gsearch.NextFullSearch()) != NULL) { ColPartitionSet* columns = all_columns[gsearch.GridY()]; TBOX box = part->bounding_box(); int y = part->MidY(); ColPartition* left_column = columns->ColumnContaining(box.left(), y); ColPartition* right_column = columns->ColumnContaining(box.right(), y); // set distance from left column as space to the left if (left_column) { int left_space = MAX(0, box.left() - left_column->LeftAtY(y)); part->set_space_to_left(left_space); } // set distance from right column as space to the right if (right_column) { int right_space = MAX(0, right_column->RightAtY(y) - box.right()); part->set_space_to_right(right_space); } // Look for images that may be closer. // NOTE: used to be part_grid_, might cause issues now ColPartitionGridSearch hsearch(grid); hsearch.StartSideSearch(box.left(), box.bottom(), box.top()); ColPartition* neighbor = NULL; while ((neighbor = hsearch.NextSideSearch(true)) != NULL) { if (neighbor->type() == PT_PULLOUT_IMAGE || neighbor->type() == PT_FLOWING_IMAGE || neighbor->type() == PT_HEADING_IMAGE) { int right = neighbor->bounding_box().right(); if (right < box.left()) { int space = MIN(box.left() - right, part->space_to_left()); part->set_space_to_left(space); } } } hsearch.StartSideSearch(box.left(), box.bottom(), box.top()); neighbor = NULL; while ((neighbor = hsearch.NextSideSearch(false)) != NULL) { if (neighbor->type() == PT_PULLOUT_IMAGE || neighbor->type() == PT_FLOWING_IMAGE || neighbor->type() == PT_HEADING_IMAGE) { int left = neighbor->bounding_box().left(); if (left > box.right()) { int space = MIN(left - box.right(), part->space_to_right()); part->set_space_to_right(space); } } } ColPartition* upper_part = part->SingletonPartner(true); if (upper_part) { int space = MAX(0, upper_part->bounding_box().bottom() - part->bounding_box().bottom()); part->set_space_above(space); } else { // TODO(nbeato): What constitutes a good value? // 0 is the default value when not set, explicitly noting it needs to // be something else. part->set_space_above(MAX_INT32); } ColPartition* lower_part = part->SingletonPartner(false); if (lower_part) { int space = MAX(0, part->bounding_box().bottom() - lower_part->bounding_box().bottom()); part->set_space_below(space); } else { // TODO(nbeato): What constitutes a good value? // 0 is the default value when not set, explicitly noting it needs to // be something else. part->set_space_below(MAX_INT32); } } }
void tesseract::TableFinder::SetVerticalSpacing | ( | ColPartition * | part | ) | [protected] |
Definition at line 666 of file tablefind.cpp.
{ TBOX box = part->bounding_box(); int top_range = MIN(box.top() + kMaxVerticalSpacing, tright().y()); int bottom_range = MAX(box.bottom() - kMaxVerticalSpacing, bleft().y()); box.set_top(top_range); box.set_bottom(bottom_range); TBOX part_box = part->bounding_box(); // Start a rect search GridSearch<ColPartition, ColPartition_CLIST, ColPartition_C_IT> rectsearch(&clean_part_grid_); rectsearch.StartRectSearch(box); ColPartition* neighbor; int min_space_above = kMaxVerticalSpacing; int min_space_below = kMaxVerticalSpacing; ColPartition* above_neighbor = NULL; ColPartition* below_neighbor = NULL; while ((neighbor = rectsearch.NextRectSearch()) != NULL) { if (neighbor == part) continue; TBOX neighbor_box = neighbor->bounding_box(); if (neighbor_box.major_x_overlap(part_box)) { int gap = abs(part->median_bottom() - neighbor->median_bottom()); // If neighbor is below current partition if (neighbor_box.top() < part_box.bottom() && gap < min_space_below) { min_space_below = gap; below_neighbor = neighbor; } // If neighbor is above current partition else if (part_box.top() < neighbor_box.bottom() && gap < min_space_above) { min_space_above = gap; above_neighbor = neighbor; } } } part->set_space_above(min_space_above); part->set_space_below(min_space_below); part->set_nearest_neighbor_above(above_neighbor); part->set_nearest_neighbor_below(below_neighbor); }
void tesseract::TableFinder::SmoothTablePartitionRuns | ( | ) | [protected] |
Definition at line 1108 of file tablefind.cpp.
{ // Iterate the ColPartitions in the grid. ColPartitionGridSearch gsearch(&clean_part_grid_); gsearch.StartFullSearch(); ColPartition* part = NULL; while ((part = gsearch.NextFullSearch()) != NULL) { if (part->type() >= PT_TABLE || part->type() == PT_UNKNOWN) continue; // Consider only text partitions ColPartition* upper_part = part->nearest_neighbor_above(); ColPartition* lower_part = part->nearest_neighbor_below(); if (!upper_part || !lower_part) continue; if (upper_part->type() == PT_TABLE && lower_part->type() == PT_TABLE) part->set_table_type(); } // Pass 2, do the opposite. If both the upper and lower neighbors // exist and are not tables, this probably shouldn't be a table. gsearch.StartFullSearch(); part = NULL; while ((part = gsearch.NextFullSearch()) != NULL) { if (part->type() != PT_TABLE) continue; // Consider only text partitions ColPartition* upper_part = part->nearest_neighbor_above(); ColPartition* lower_part = part->nearest_neighbor_below(); // table can't be by itself if ((upper_part && upper_part->type() != PT_TABLE) && (lower_part && lower_part->type() != PT_TABLE)) { part->clear_table_type(); } } }
void tesseract::TableFinder::SplitAndInsertFragmentedTextPartition | ( | ColPartition * | part | ) | [protected] |
Definition at line 436 of file tablefind.cpp.
{ ASSERT_HOST(part != NULL); // Bye bye empty partitions! if (part->boxes()->empty()) { delete part; return; } // The AllowBlob function prevents this. ASSERT_HOST(part->median_width() > 0); const double kThreshold = part->median_width() * kSplitPartitionSize; ColPartition* right_part = part; bool found_split = true; while (found_split) { found_split = false; BLOBNBOX_C_IT box_it(right_part->boxes()); // Blobs are sorted left side first. If blobs overlap, // the previous blob may have a "more right" right side. // Account for this by always keeping the largest "right" // so far. int previous_right = MIN_INT32; // Look for the next split in the partition. for (box_it.mark_cycle_pt(); !box_it.cycled_list(); box_it.forward()) { const TBOX& box = box_it.data()->bounding_box(); if (previous_right != MIN_INT32 && box.left() - previous_right > kThreshold) { // We have a split position. Split the partition in two pieces. // Insert the left piece in the grid and keep processing the right. int mid_x = (box.left() + previous_right) / 2; ColPartition* left_part = right_part; right_part = left_part->SplitAt(mid_x); InsertFragmentedTextPartition(left_part); found_split = true; break; } // The right side of the previous blobs. previous_right = MAX(previous_right, box.right()); } } // When a split is not found, the right part is minimized // as much as possible, so process it. InsertFragmentedTextPartition(right_part); }
const ICOORD & tesseract::TableFinder::tright | ( | ) | const [protected] |
Definition at line 390 of file tablefind.cpp.
{ return clean_part_grid_.tright(); }
void tesseract::TableFinder::WriteToPix | ( | const FCOORD & | reskew | ) | [protected] |
Definition at line 1997 of file tablefind.cpp.
{ // Input file must be named test1.tif PIX* pix = pixRead("test1.tif"); if (!pix) { tprintf("Input file test1.tif not found.\n"); return; } int img_height = pixGetHeight(pix); int img_width = pixGetWidth(pix); // Maximum number of text or table partitions int num_boxes = 10; BOXA* text_box_array = boxaCreate(num_boxes); BOXA* table_box_array = boxaCreate(num_boxes); GridSearch<ColPartition, ColPartition_CLIST, ColPartition_C_IT> gsearch(&clean_part_grid_); gsearch.StartFullSearch(); ColPartition* part; // load colpartitions into text_box_array and table_box_array while ((part = gsearch.NextFullSearch()) != NULL) { TBOX box = part->bounding_box(); box.rotate_large(reskew); BOX* lept_box = boxCreate(box.left(), img_height - box.top(), box.right() - box.left(), box.top() - box.bottom()); if (part->type() == PT_TABLE) boxaAddBox(table_box_array, lept_box, L_INSERT); else boxaAddBox(text_box_array, lept_box, L_INSERT); } // draw colpartitions on the output image PIX* out = pixDrawBoxa(pix, text_box_array, 3, 0xff000000); out = pixDrawBoxa(out, table_box_array, 3, 0x0000ff00); BOXA* table_array = boxaCreate(num_boxes); // text file containing detected table bounding boxes FILE* fptr = fopen("tess-table.txt", "wb"); GridSearch<ColSegment, ColSegment_CLIST, ColSegment_C_IT> table_search(&table_grid_); table_search.StartFullSearch(); ColSegment* table; // load table boxes to table_array and write them to text file as well while ((table = table_search.NextFullSearch()) != NULL) { TBOX box = table->bounding_box(); box.rotate_large(reskew); // Since deskewing introduces negative coordinates, reskewing // might not completely recover from that since both steps enlarge // the actual box. Hence a box that undergoes deskewing/reskewing // may go out of image boundaries. Crop a table box if needed to // contain it inside the image dimensions. box = box.intersection(TBOX(0, 0, img_width - 1, img_height - 1)); BOX* lept_box = boxCreate(box.left(), img_height - box.top(), box.right() - box.left(), box.top() - box.bottom()); boxaAddBox(table_array, lept_box, L_INSERT); fprintf(fptr, "%d %d %d %d TABLE\n", box.left(), img_height - box.top(), box.right(), img_height - box.bottom()); } fclose(fptr); // paint table boxes on the debug image out = pixDrawBoxa(out, table_array, 5, 0x7fff0000); pixWrite("out.png", out, IFF_PNG); // memory cleanup boxaDestroy(&text_box_array); boxaDestroy(&table_box_array); boxaDestroy(&table_array); pixDestroy(&pix); pixDestroy(&out); }
Definition at line 418 of file tablefind.h.
ColSegmentGrid tesseract::TableFinder::col_seg_grid_ [protected] |
Definition at line 426 of file tablefind.h.
Definition at line 424 of file tablefind.h.
int tesseract::TableFinder::global_median_blob_width_ [protected] |
Definition at line 412 of file tablefind.h.
int tesseract::TableFinder::global_median_ledding_ [protected] |
Definition at line 414 of file tablefind.h.
int tesseract::TableFinder::global_median_xheight_ [protected] |
Definition at line 410 of file tablefind.h.
Definition at line 420 of file tablefind.h.
bool tesseract::TableFinder::left_to_right_language_ [protected] |
Definition at line 430 of file tablefind.h.
int tesseract::TableFinder::resolution_ [protected] |
Definition at line 408 of file tablefind.h.
ColSegmentGrid tesseract::TableFinder::table_grid_ [protected] |
Definition at line 428 of file tablefind.h.