tesseract  4.00.00dev
colfind.cpp
Go to the documentation of this file.
1 // File: colfind.cpp
3 // Description: Class to hold BLOBNBOXs in a grid for fast access
4 // to neighbours.
5 // Author: Ray Smith
6 // Created: Wed Jun 06 17:22:01 PDT 2007
7 //
8 // (C) Copyright 2007, Google Inc.
9 // Licensed under the Apache License, Version 2.0 (the "License");
10 // you may not use this file except in compliance with the License.
11 // You may obtain a copy of the License at
12 // http://www.apache.org/licenses/LICENSE-2.0
13 // Unless required by applicable law or agreed to in writing, software
14 // distributed under the License is distributed on an "AS IS" BASIS,
15 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 // See the License for the specific language governing permissions and
17 // limitations under the License.
18 //
20 
21 #ifdef _MSC_VER
22 #pragma warning(disable:4244) // Conversion warnings
23 #endif
24 
25 // Include automatically generated configuration file if running autoconf.
26 #ifdef HAVE_CONFIG_H
27 #include "config_auto.h"
28 #endif
29 
30 #include "colfind.h"
31 
32 #include "ccnontextdetect.h"
33 #include "colpartition.h"
34 #include "colpartitionset.h"
35 #include "equationdetectbase.h"
36 #include "linefind.h"
37 #include "normalis.h"
38 #include "strokewidth.h"
39 #include "blobbox.h"
40 #include "scrollview.h"
41 #include "tablefind.h"
42 #include "params.h"
43 #include "workingpartset.h"
44 
45 namespace tesseract {
46 
47 // When assigning columns, the max number of misfit grid rows/ColPartitionSets
48 // that can be ignored.
50 // Max fraction of mean_column_gap_ for the gap between two partitions within a
51 // column to allow them to merge.
52 const double kHorizontalGapMergeFraction = 0.5;
53 // Minimum gutter width as a fraction of gridsize
54 const double kMinGutterWidthGrid = 0.5;
55 // Max multiple of a partition's median size as a distance threshold for
56 // adding noise blobs.
57 const double kMaxDistToPartSizeRatio = 1.5;
58 
60  false, "Show partition bounds");
62  false, "Show blobs rejected as noise");
64  "Show partition bounds, waiting if >1");
65 BOOL_VAR(textord_tabfind_show_columns, false, "Show column bounds");
66 BOOL_VAR(textord_tabfind_show_blocks, false, "Show final block bounds");
67 BOOL_VAR(textord_tabfind_find_tables, true, "run table detection");
68 
69 ScrollView* ColumnFinder::blocks_win_ = NULL;
70 
71 // Gridsize is an estimate of the text size in the image. A suitable value
72 // is in TO_BLOCK::line_size after find_components has been used to make
73 // the blobs.
74 // bleft and tright are the bounds of the image (or rectangle) being processed.
75 // vlines is a (possibly empty) list of TabVector and vertical_x and y are
76 // the sum logical vertical vector produced by LineFinder::FindVerticalLines.
78  const ICOORD& bleft, const ICOORD& tright,
79  int resolution, bool cjk_script,
80  double aligned_gap_fraction,
81  TabVector_LIST* vlines, TabVector_LIST* hlines,
82  int vertical_x, int vertical_y)
83  : TabFind(gridsize, bleft, tright, vlines, vertical_x, vertical_y,
84  resolution),
85  cjk_script_(cjk_script),
86  min_gutter_width_(static_cast<int>(kMinGutterWidthGrid * gridsize)),
87  mean_column_gap_(tright.x() - bleft.x()),
88  tabfind_aligned_gap_fraction_(aligned_gap_fraction),
89  reskew_(1.0f, 0.0f), rotation_(1.0f, 0.0f), rerotate_(1.0f, 0.0f),
90  best_columns_(NULL), stroke_width_(NULL),
91  part_grid_(gridsize, bleft, tright), nontext_map_(NULL),
92  projection_(resolution),
93  denorm_(NULL), input_blobs_win_(NULL), equation_detect_(NULL) {
94  TabVector_IT h_it(&horizontal_lines_);
95  h_it.add_list_after(hlines);
96 }
97 
99  column_sets_.delete_data_pointers();
100  if (best_columns_ != NULL) {
101  delete [] best_columns_;
102  }
103  if (stroke_width_ != NULL)
104  delete stroke_width_;
105  delete input_blobs_win_;
106  pixDestroy(&nontext_map_);
107  while (denorm_ != NULL) {
108  DENORM* dead_denorm = denorm_;
109  denorm_ = const_cast<DENORM*>(denorm_->predecessor());
110  delete dead_denorm;
111  }
112 
113  // The ColPartitions are destroyed automatically, but any boxes in
114  // the noise_parts_ list are owned and need to be deleted explicitly.
115  ColPartition_IT part_it(&noise_parts_);
116  for (part_it.mark_cycle_pt(); !part_it.cycled_list(); part_it.forward()) {
117  ColPartition* part = part_it.data();
118  part->DeleteBoxes();
119  }
120  // Likewise any boxes in the good_parts_ list need to be deleted.
121  // These are just the image parts. Text parts have already given their
122  // boxes on to the TO_BLOCK, and have empty lists.
123  part_it.set_to_list(&good_parts_);
124  for (part_it.mark_cycle_pt(); !part_it.cycled_list(); part_it.forward()) {
125  ColPartition* part = part_it.data();
126  part->DeleteBoxes();
127  }
128  // Also, any blobs on the image_bblobs_ list need to have their cblobs
129  // deleted. This only happens if there has been an early return from
130  // FindColumns, as in a normal return, the blobs go into the grid and
131  // end up in noise_parts_, good_parts_ or the output blocks.
132  BLOBNBOX_IT bb_it(&image_bblobs_);
133  for (bb_it.mark_cycle_pt(); !bb_it.cycled_list(); bb_it.forward()) {
134  BLOBNBOX* bblob = bb_it.data();
135  delete bblob->cblob();
136  }
137 }
138 
139 // Performs initial processing on the blobs in the input_block:
140 // Setup the part_grid, stroke_width_, nontext_map.
141 // Obvious noise blobs are filtered out and used to mark the nontext_map_.
142 // Initial stroke-width analysis is used to get local text alignment
143 // direction, so the textline projection_ map can be setup.
144 // On return, IsVerticallyAlignedText may be called (now optionally) to
145 // determine the gross textline alignment of the page.
147  Pix* photo_mask_pix,
148  TO_BLOCK* input_block) {
149  part_grid_.Init(gridsize(), bleft(), tright());
150  if (stroke_width_ != NULL)
151  delete stroke_width_;
152  stroke_width_ = new StrokeWidth(gridsize(), bleft(), tright());
153  min_gutter_width_ = static_cast<int>(kMinGutterWidthGrid * gridsize());
154  input_block->ReSetAndReFilterBlobs();
155  #ifndef GRAPHICS_DISABLED
157  input_blobs_win_ = MakeWindow(0, 0, "Filtered Input Blobs");
158  input_block->plot_graded_blobs(input_blobs_win_);
159  }
160  #endif // GRAPHICS_DISABLED
161  SetBlockRuleEdges(input_block);
162  pixDestroy(&nontext_map_);
163  // Run a preliminary strokewidth neighbour detection on the medium blobs.
164  stroke_width_->SetNeighboursOnMediumBlobs(input_block);
165  CCNonTextDetect nontext_detect(gridsize(), bleft(), tright());
166  // Remove obvious noise and make the initial non-text map.
167  nontext_map_ = nontext_detect.ComputeNonTextMask(textord_debug_tabfind,
168  photo_mask_pix, input_block);
169  stroke_width_->FindTextlineDirectionAndFixBrokenCJK(pageseg_mode, cjk_script_,
170  input_block);
171  // Clear the strokewidth grid ready for rotation or leader finding.
172  stroke_width_->Clear();
173 }
174 
175 // Tests for vertical alignment of text (returning true if so), and generates
176 // a list of blobs of moderate aspect ratio, in the most frequent writing
177 // direction (in osd_blobs) for orientation and script detection to test
178 // the character orientation.
179 // block is the single block for the whole page or rectangle to be OCRed.
180 // Note that the vertical alignment may be due to text whose writing direction
181 // is vertical, like say Japanese, or due to text whose writing direction is
182 // horizontal but whose text appears vertically aligned because the image is
183 // not the right way up.
184 bool ColumnFinder::IsVerticallyAlignedText(double find_vertical_text_ratio,
185  TO_BLOCK* block,
186  BLOBNBOX_CLIST* osd_blobs) {
187  return stroke_width_->TestVerticalTextDirection(find_vertical_text_ratio,
188  block, osd_blobs);
189 }
190 
191 // Rotates the blobs and the TabVectors so that the gross writing direction
192 // (text lines) are horizontal and lines are read down the page.
193 // Applied rotation stored in rotation_.
194 // A second rotation is calculated for application during recognition to
195 // make the rotated blobs upright for recognition.
196 // Subsequent rotation stored in text_rotation_.
197 //
198 // Arguments:
199 // vertical_text_lines true if the text lines are vertical.
200 // recognition_rotation [0..3] is the number of anti-clockwise 90 degree
201 // rotations from osd required for the text to be upright and readable.
203  bool vertical_text_lines,
204  int recognition_rotation) {
205  const FCOORD anticlockwise90(0.0f, 1.0f);
206  const FCOORD clockwise90(0.0f, -1.0f);
207  const FCOORD rotation180(-1.0f, 0.0f);
208  const FCOORD norotation(1.0f, 0.0f);
209 
210  text_rotation_ = norotation;
211  // Rotate the page to make the text upright, as implied by
212  // recognition_rotation.
213  rotation_ = norotation;
214  if (recognition_rotation == 1) {
215  rotation_ = anticlockwise90;
216  } else if (recognition_rotation == 2) {
217  rotation_ = rotation180;
218  } else if (recognition_rotation == 3) {
219  rotation_ = clockwise90;
220  }
221  // We infer text writing direction to be vertical if there are several
222  // vertical text lines detected, and horizontal if not. But if the page
223  // orientation was determined to be 90 or 270 degrees, the true writing
224  // direction is the opposite of what we inferred.
225  if (recognition_rotation & 1) {
226  vertical_text_lines = !vertical_text_lines;
227  }
228  // If we still believe the writing direction is vertical, we use the
229  // convention of rotating the page ccw 90 degrees to make the text lines
230  // horizontal, and mark the blobs for rotation cw 90 degrees for
231  // classification so that the text order is correct after recognition.
232  if (vertical_text_lines) {
233  rotation_.rotate(anticlockwise90);
234  text_rotation_.rotate(clockwise90);
235  }
236  // Set rerotate_ to the inverse of rotation_.
237  rerotate_ = FCOORD(rotation_.x(), -rotation_.y());
238  if (rotation_.x() != 1.0f || rotation_.y() != 0.0f) {
239  // Rotate all the blobs and tab vectors.
240  RotateBlobList(rotation_, &block->large_blobs);
241  RotateBlobList(rotation_, &block->blobs);
242  RotateBlobList(rotation_, &block->small_blobs);
243  RotateBlobList(rotation_, &block->noise_blobs);
244  TabFind::ResetForVerticalText(rotation_, rerotate_, &horizontal_lines_,
245  &min_gutter_width_);
246  part_grid_.Init(gridsize(), bleft(), tright());
247  // Reset all blobs to initial state and filter by size.
248  // Since they have rotated, the list they belong on could have changed.
249  block->ReSetAndReFilterBlobs();
250  SetBlockRuleEdges(block);
251  stroke_width_->CorrectForRotation(rerotate_, &part_grid_);
252  }
253  if (textord_debug_tabfind) {
254  tprintf("Vertical=%d, orientation=%d, final rotation=(%f, %f)+(%f,%f)\n",
255  vertical_text_lines, recognition_rotation,
256  rotation_.x(), rotation_.y(),
257  text_rotation_.x(), text_rotation_.y());
258  }
259  // Setup the denormalization.
260  ASSERT_HOST(denorm_ == NULL);
261  denorm_ = new DENORM;
262  denorm_->SetupNormalization(NULL, &rotation_, NULL,
263  0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f);
264 }
265 
266 // Finds blocks of text, image, rule line, table etc, returning them in the
267 // blocks and to_blocks
268 // (Each TO_BLOCK points to the basic BLOCK and adds more information.)
269 // Image blocks are generated by a combination of photo_mask_pix (which may
270 // NOT be NULL) and the rejected text found during preliminary textline
271 // finding.
272 // The input_block is the result of a call to find_components, and contains
273 // the blobs found in the image or rectangle to be OCRed. These blobs will be
274 // removed and placed in the output blocks, while unused ones will be deleted.
275 // If single_column is true, the input is treated as single column, but
276 // it is still divided into blocks of equal line spacing/text size.
277 // scaled_color is scaled down by scaled_factor from the input color image,
278 // and may be NULL if the input was not color.
279 // grey_pix is optional, but if present must match the photo_mask_pix in size,
280 // and must be a *real* grey image instead of binary_pix * 255.
281 // thresholds_pix is expected to be present iff grey_pix is present and
282 // can be an integer factor reduction of the grey_pix. It represents the
283 // thresholds that were used to create the binary_pix from the grey_pix.
284 // If diacritic_blobs is non-null, then diacritics/noise blobs, that would
285 // confuse layout anaylsis by causing textline overlap, are placed there,
286 // with the expectation that they will be reassigned to words later and
287 // noise/diacriticness determined via classification.
288 // Returns -1 if the user hits the 'd' key in the blocks window while running
289 // in debug mode, which requests a retry with more debug info.
290 int ColumnFinder::FindBlocks(PageSegMode pageseg_mode, Pix* scaled_color,
291  int scaled_factor, TO_BLOCK* input_block,
292  Pix* photo_mask_pix, Pix* thresholds_pix,
293  Pix* grey_pix, DebugPixa* pixa_debug,
294  BLOCK_LIST* blocks, BLOBNBOX_LIST* diacritic_blobs,
295  TO_BLOCK_LIST* to_blocks) {
296  pixOr(photo_mask_pix, photo_mask_pix, nontext_map_);
297  stroke_width_->FindLeaderPartitions(input_block, &part_grid_);
298  stroke_width_->RemoveLineResidue(&big_parts_);
299  FindInitialTabVectors(NULL, min_gutter_width_, tabfind_aligned_gap_fraction_,
300  input_block);
301  SetBlockRuleEdges(input_block);
302  stroke_width_->GradeBlobsIntoPartitions(
303  pageseg_mode, rerotate_, input_block, nontext_map_, denorm_, cjk_script_,
304  &projection_, diacritic_blobs, &part_grid_, &big_parts_);
305  if (!PSM_SPARSE(pageseg_mode)) {
306  ImageFind::FindImagePartitions(photo_mask_pix, rotation_, rerotate_,
307  input_block, this, pixa_debug, &part_grid_,
308  &big_parts_);
309  ImageFind::TransferImagePartsToImageMask(rerotate_, &part_grid_,
310  photo_mask_pix);
311  ImageFind::FindImagePartitions(photo_mask_pix, rotation_, rerotate_,
312  input_block, this, pixa_debug, &part_grid_,
313  &big_parts_);
314  }
315  part_grid_.ReTypeBlobs(&image_bblobs_);
316  TidyBlobs(input_block);
317  Reset();
318  // TODO(rays) need to properly handle big_parts_.
319  ColPartition_IT p_it(&big_parts_);
320  for (p_it.mark_cycle_pt(); !p_it.cycled_list(); p_it.forward())
321  p_it.data()->DisownBoxesNoAssert();
322  big_parts_.clear();
323  delete stroke_width_;
324  stroke_width_ = NULL;
325  // Compute the edge offsets whether or not there is a grey_pix. It is done
326  // here as the c_blobs haven't been touched by rotation or anything yet,
327  // so no denorm is required, yet the text has been separated from image, so
328  // no time is wasted running it on image blobs.
329  input_block->ComputeEdgeOffsets(thresholds_pix, grey_pix);
330 
331  // A note about handling right-to-left scripts (Hebrew/Arabic):
332  // The columns must be reversed and come out in right-to-left instead of
333  // the normal left-to-right order. Because the left-to-right ordering
334  // is implicit in many data structures, it is simpler to fool the algorithms
335  // into thinking they are dealing with left-to-right text.
336  // To do this, we reflect the needed data in the y-axis and then reflect
337  // the blocks back after they have been created. This is a temporary
338  // arrangement that is confined to this function only, so the reflection
339  // is completely invisible in the output blocks.
340  // The only objects reflected are:
341  // The vertical separator lines that have already been found;
342  // The bounding boxes of all BLOBNBOXES on all lists on the input_block
343  // plus the image_bblobs. The outlines are not touched, since they are
344  // not looked at.
345  bool input_is_rtl = input_block->block->right_to_left();
346  if (input_is_rtl) {
347  // Reflect the vertical separator lines (member of TabFind).
348  ReflectInYAxis();
349  // Reflect the blob boxes.
350  ReflectForRtl(input_block, &image_bblobs_);
351  part_grid_.ReflectInYAxis();
352  }
353 
354  if (!PSM_SPARSE(pageseg_mode)) {
355  if (!PSM_COL_FIND_ENABLED(pageseg_mode)) {
356  // No tab stops needed. Just the grid that FindTabVectors makes.
357  DontFindTabVectors(&image_bblobs_, input_block, &deskew_, &reskew_);
358  } else {
359  SetBlockRuleEdges(input_block);
360  // Find the tab stops, estimate skew, and deskew the tabs, blobs and
361  // part_grid_.
362  FindTabVectors(&horizontal_lines_, &image_bblobs_, input_block,
363  min_gutter_width_, tabfind_aligned_gap_fraction_,
364  &part_grid_, &deskew_, &reskew_);
365  // Add the deskew to the denorm_.
366  DENORM* new_denorm = new DENORM;
367  new_denorm->SetupNormalization(NULL, &deskew_, denorm_,
368  0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f);
369  denorm_ = new_denorm;
370  }
371  SetBlockRuleEdges(input_block);
372  part_grid_.SetTabStops(this);
373 
374  // Make the column_sets_.
375  if (!MakeColumns(false)) {
376  tprintf("Empty page!!\n");
377  part_grid_.DeleteParts();
378  return 0; // This is an empty page.
379  }
380 
381  // Refill the grid using rectangular spreading, and get the benefit
382  // of the completed tab vectors marking the rule edges of each blob.
383  Clear();
384  #ifndef GRAPHICS_DISABLED
386  ScrollView* rej_win = MakeWindow(500, 300, "Rejected blobs");
387  input_block->plot_graded_blobs(rej_win);
388  }
389  #endif // GRAPHICS_DISABLED
390  InsertBlobsToGrid(false, false, &image_bblobs_, this);
391  InsertBlobsToGrid(true, true, &input_block->blobs, this);
392 
393  part_grid_.GridFindMargins(best_columns_);
394  // Split and merge the partitions by looking at local neighbours.
395  GridSplitPartitions();
396  // Resolve unknown partitions by adding to an existing partition, fixing
397  // the type, or declaring them noise.
398  part_grid_.GridFindMargins(best_columns_);
399  GridMergePartitions();
400  // Insert any unused noise blobs that are close enough to an appropriate
401  // partition.
402  InsertRemainingNoise(input_block);
403  // Add horizontal line separators as partitions.
404  GridInsertHLinePartitions();
405  GridInsertVLinePartitions();
406  // Recompute margins based on a local neighbourhood search.
407  part_grid_.GridFindMargins(best_columns_);
408  SetPartitionTypes();
409  }
411  ScrollView* part_win = MakeWindow(100, 300, "InitialPartitions");
412  part_grid_.DisplayBoxes(part_win);
413  DisplayTabVectors(part_win);
414  }
415 
416  if (!PSM_SPARSE(pageseg_mode)) {
417  if (equation_detect_) {
418  equation_detect_->FindEquationParts(&part_grid_, best_columns_);
419  }
421  TableFinder table_finder;
422  table_finder.Init(gridsize(), bleft(), tright());
423  table_finder.set_resolution(resolution_);
424  table_finder.set_left_to_right_language(
425  !input_block->block->right_to_left());
426  // Copy cleaned partitions from part_grid_ to clean_part_grid_ and
427  // insert dot-like noise into period_grid_
428  table_finder.InsertCleanPartitions(&part_grid_, input_block);
429  // Get Table Regions
430  table_finder.LocateTables(&part_grid_, best_columns_, WidthCB(), reskew_);
431  }
432  GridRemoveUnderlinePartitions();
433  part_grid_.DeleteUnknownParts(input_block);
434 
435  // Build the partitions into chains that belong in the same block and
436  // refine into one-to-one links, then smooth the types within each chain.
437  part_grid_.FindPartitionPartners();
438  part_grid_.FindFigureCaptions();
439  part_grid_.RefinePartitionPartners(true);
440  SmoothPartnerRuns();
441 
442  #ifndef GRAPHICS_DISABLED
444  ScrollView* window = MakeWindow(400, 300, "Partitions");
445  if (window != NULL) {
446  part_grid_.DisplayBoxes(window);
448  DisplayTabVectors(window);
449  if (window != NULL && textord_tabfind_show_partitions > 1) {
450  delete window->AwaitEvent(SVET_DESTROY);
451  }
452  }
453  }
454  #endif // GRAPHICS_DISABLED
455  part_grid_.AssertNoDuplicates();
456  }
457  // Ownership of the ColPartitions moves from part_sets_ to part_grid_ here,
458  // and ownership of the BLOBNBOXes moves to the ColPartitions.
459  // (They were previously owned by the block or the image_bblobs list.)
460  ReleaseBlobsAndCleanupUnused(input_block);
461  // Ownership of the ColPartitions moves from part_grid_ to good_parts_ and
462  // noise_parts_ here. In text blocks, ownership of the BLOBNBOXes moves
463  // from the ColPartitions to the output TO_BLOCK. In non-text, the
464  // BLOBNBOXes stay with the ColPartitions and get deleted in the destructor.
465  if (PSM_SPARSE(pageseg_mode))
466  part_grid_.ExtractPartitionsAsBlocks(blocks, to_blocks);
467  else
468  TransformToBlocks(blocks, to_blocks);
469  if (textord_debug_tabfind) {
470  tprintf("Found %d blocks, %d to_blocks\n",
471  blocks->length(), to_blocks->length());
472  }
473 
474  DisplayBlocks(blocks);
475  RotateAndReskewBlocks(input_is_rtl, to_blocks);
476  int result = 0;
477  #ifndef GRAPHICS_DISABLED
478  if (blocks_win_ != NULL) {
479  bool waiting = false;
480  do {
481  waiting = false;
482  SVEvent* event = blocks_win_->AwaitEvent(SVET_ANY);
483  if (event->type == SVET_INPUT && event->parameter != NULL) {
484  if (*event->parameter == 'd')
485  result = -1;
486  else
487  blocks->clear();
488  } else if (event->type == SVET_DESTROY) {
489  blocks_win_ = NULL;
490  } else {
491  waiting = true;
492  }
493  delete event;
494  } while (waiting);
495  }
496  #endif // GRAPHICS_DISABLED
497  return result;
498 }
499 
500 // Get the rotation required to deskew, and its inverse rotation.
502  *reskew = reskew_;
503  *deskew = reskew_;
504  deskew->set_y(-deskew->y());
505 }
506 
508  equation_detect_ = detect;
509 }
510 
512 
513 // Displays the blob and block bounding boxes in a window called Blocks.
514 void ColumnFinder::DisplayBlocks(BLOCK_LIST* blocks) {
515 #ifndef GRAPHICS_DISABLED
517  if (blocks_win_ == NULL)
518  blocks_win_ = MakeWindow(700, 300, "Blocks");
519  else
520  blocks_win_->Clear();
521  DisplayBoxes(blocks_win_);
522  BLOCK_IT block_it(blocks);
523  int serial = 1;
524  for (block_it.mark_cycle_pt(); !block_it.cycled_list();
525  block_it.forward()) {
526  BLOCK* block = block_it.data();
527  block->plot(blocks_win_, serial++,
530  }
531  blocks_win_->Update();
532  }
533 #endif
534 }
535 
536 // Displays the column edges at each grid y coordinate defined by
537 // best_columns_.
538 void ColumnFinder::DisplayColumnBounds(PartSetVector* sets) {
539 #ifndef GRAPHICS_DISABLED
540  ScrollView* col_win = MakeWindow(50, 300, "Columns");
541  DisplayBoxes(col_win);
543  for (int i = 0; i < gridheight_; ++i) {
544  ColPartitionSet* columns = best_columns_[i];
545  if (columns != NULL)
546  columns->DisplayColumnEdges(i * gridsize_, (i + 1) * gridsize_, col_win);
547  }
548 #endif
549 }
550 
551 // Sets up column_sets_ (the determined column layout at each horizontal
552 // slice). Returns false if the page is empty.
553 bool ColumnFinder::MakeColumns(bool single_column) {
554  // The part_sets_ are a temporary structure used during column creation,
555  // and is a vector of ColPartitionSets, representing ColPartitions found
556  // at horizontal slices through the page.
557  PartSetVector part_sets;
558  if (!single_column) {
559  if (!part_grid_.MakeColPartSets(&part_sets))
560  return false; // Empty page.
561  ASSERT_HOST(part_grid_.gridheight() == gridheight_);
562  // Try using only the good parts first.
563  bool good_only = true;
564  do {
565  for (int i = 0; i < gridheight_; ++i) {
566  ColPartitionSet* line_set = part_sets.get(i);
567  if (line_set != NULL && line_set->LegalColumnCandidate()) {
568  ColPartitionSet* column_candidate = line_set->Copy(good_only);
569  if (column_candidate != NULL)
570  column_candidate->AddToColumnSetsIfUnique(&column_sets_, WidthCB());
571  }
572  }
573  good_only = !good_only;
574  } while (column_sets_.empty() && !good_only);
576  PrintColumnCandidates("Column candidates");
577  // Improve the column candidates against themselves.
578  ImproveColumnCandidates(&column_sets_, &column_sets_);
580  PrintColumnCandidates("Improved columns");
581  // Improve the column candidates using the part_sets_.
582  ImproveColumnCandidates(&part_sets, &column_sets_);
583  }
584  ColPartitionSet* single_column_set =
585  part_grid_.MakeSingleColumnSet(WidthCB());
586  if (single_column_set != NULL) {
587  // Always add the single column set as a backup even if not in
588  // single column mode.
589  single_column_set->AddToColumnSetsIfUnique(&column_sets_, WidthCB());
590  }
592  PrintColumnCandidates("Final Columns");
593  bool has_columns = !column_sets_.empty();
594  if (has_columns) {
595  // Divide the page into sections of uniform column layout.
596  bool any_multi_column = AssignColumns(part_sets);
598  DisplayColumnBounds(&part_sets);
599  }
600  ComputeMeanColumnGap(any_multi_column);
601  }
602  for (int i = 0; i < part_sets.size(); ++i) {
603  ColPartitionSet* line_set = part_sets.get(i);
604  if (line_set != NULL) {
605  line_set->RelinquishParts();
606  delete line_set;
607  }
608  }
609  return has_columns;
610 }
611 
612 // Attempt to improve the column_candidates by expanding the columns
613 // and adding new partitions from the partition sets in src_sets.
614 // Src_sets may be equal to column_candidates, in which case it will
615 // use them as a source to improve themselves.
616 void ColumnFinder::ImproveColumnCandidates(PartSetVector* src_sets,
617  PartSetVector* column_sets) {
618  PartSetVector temp_cols;
619  temp_cols.move(column_sets);
620  if (src_sets == column_sets)
621  src_sets = &temp_cols;
622  int set_size = temp_cols.size();
623  // Try using only the good parts first.
624  bool good_only = true;
625  do {
626  for (int i = 0; i < set_size; ++i) {
627  ColPartitionSet* column_candidate = temp_cols.get(i);
628  ASSERT_HOST(column_candidate != NULL);
629  ColPartitionSet* improved = column_candidate->Copy(good_only);
630  if (improved != NULL) {
631  improved->ImproveColumnCandidate(WidthCB(), src_sets);
632  improved->AddToColumnSetsIfUnique(column_sets, WidthCB());
633  }
634  }
635  good_only = !good_only;
636  } while (column_sets->empty() && !good_only);
637  if (column_sets->empty())
638  column_sets->move(&temp_cols);
639  else
640  temp_cols.delete_data_pointers();
641 }
642 
643 // Prints debug information on the column candidates.
644 void ColumnFinder::PrintColumnCandidates(const char* title) {
645  int set_size = column_sets_.size();
646  tprintf("Found %d %s:\n", set_size, title);
647  if (textord_debug_tabfind >= 3) {
648  for (int i = 0; i < set_size; ++i) {
649  ColPartitionSet* column_set = column_sets_.get(i);
650  column_set->Print();
651  }
652  }
653 }
654 
655 // Finds the optimal set of columns that cover the entire image with as
656 // few changes in column partition as possible.
657 // NOTE: this could be thought of as an optimization problem, but a simple
658 // greedy algorithm is used instead. The algorithm repeatedly finds the modal
659 // compatible column in an unassigned region and uses that with the extra
660 // tweak of extending the modal region over small breaks in compatibility.
661 // Where modal regions overlap, the boundary is chosen so as to minimize
662 // the cost in terms of ColPartitions not fitting an approved column.
663 // Returns true if any part of the page is multi-column.
664 bool ColumnFinder::AssignColumns(const PartSetVector& part_sets) {
665  int set_count = part_sets.size();
666  ASSERT_HOST(set_count == gridheight());
667  // Allocate and init the best_columns_.
668  best_columns_ = new ColPartitionSet*[set_count];
669  for (int y = 0; y < set_count; ++y)
670  best_columns_[y] = NULL;
671  int column_count = column_sets_.size();
672  // column_set_costs[part_sets_ index][column_sets_ index] is
673  // < MAX_INT32 if the partition set is compatible with the column set,
674  // in which case its value is the cost for that set used in deciding
675  // which competing set to assign.
676  // any_columns_possible[part_sets_ index] is true if any of
677  // possible_column_sets[part_sets_ index][*] is < MAX_INT32.
678  // assigned_costs[part_sets_ index] is set to the column_set_costs
679  // of the assigned column_sets_ index or MAX_INT32 if none is set.
680  // On return the best_columns_ member is set.
681  bool* any_columns_possible = new bool[set_count];
682  int* assigned_costs = new int[set_count];
683  int** column_set_costs = new int*[set_count];
684  // Set possible column_sets to indicate whether each set is compatible
685  // with each column.
686  for (int part_i = 0; part_i < set_count; ++part_i) {
687  ColPartitionSet* line_set = part_sets.get(part_i);
688  bool debug = line_set != NULL &&
689  WithinTestRegion(2, line_set->bounding_box().left(),
690  line_set->bounding_box().bottom());
691  column_set_costs[part_i] = new int[column_count];
692  any_columns_possible[part_i] = false;
693  assigned_costs[part_i] = MAX_INT32;
694  for (int col_i = 0; col_i < column_count; ++col_i) {
695  if (line_set != NULL &&
696  column_sets_.get(col_i)->CompatibleColumns(debug, line_set,
697  WidthCB())) {
698  column_set_costs[part_i][col_i] =
699  column_sets_.get(col_i)->UnmatchedWidth(line_set);
700  any_columns_possible[part_i] = true;
701  } else {
702  column_set_costs[part_i][col_i] = MAX_INT32;
703  if (debug)
704  tprintf("Set id %d did not match at y=%d, lineset =%p\n",
705  col_i, part_i, line_set);
706  }
707  }
708  }
709  bool any_multi_column = false;
710  // Assign a column set to each vertical grid position.
711  // While there is an unassigned range, find its mode.
712  int start, end;
713  while (BiggestUnassignedRange(set_count, any_columns_possible,
714  &start, &end)) {
715  if (textord_debug_tabfind >= 2)
716  tprintf("Biggest unassigned range = %d- %d\n", start, end);
717  // Find the modal column_set_id in the range.
718  int column_set_id = RangeModalColumnSet(column_set_costs,
719  assigned_costs, start, end);
720  if (textord_debug_tabfind >= 2) {
721  tprintf("Range modal column id = %d\n", column_set_id);
722  column_sets_.get(column_set_id)->Print();
723  }
724  // Now find the longest run of the column_set_id in the range.
725  ShrinkRangeToLongestRun(column_set_costs, assigned_costs,
726  any_columns_possible,
727  column_set_id, &start, &end);
728  if (textord_debug_tabfind >= 2)
729  tprintf("Shrunk range = %d- %d\n", start, end);
730  // Extend the start and end past the longest run, while there are
731  // only small gaps in compatibility that can be overcome by larger
732  // regions of compatibility beyond.
733  ExtendRangePastSmallGaps(column_set_costs, assigned_costs,
734  any_columns_possible,
735  column_set_id, -1, -1, &start);
736  --end;
737  ExtendRangePastSmallGaps(column_set_costs, assigned_costs,
738  any_columns_possible,
739  column_set_id, 1, set_count, &end);
740  ++end;
742  tprintf("Column id %d applies to range = %d - %d\n",
743  column_set_id, start, end);
744  // Assign the column to the range, which now may overlap with other ranges.
745  AssignColumnToRange(column_set_id, start, end, column_set_costs,
746  assigned_costs);
747  if (column_sets_.get(column_set_id)->GoodColumnCount() > 1)
748  any_multi_column = true;
749  }
750  // If anything remains unassigned, the whole lot is unassigned, so
751  // arbitrarily assign id 0.
752  if (best_columns_[0] == NULL) {
753  AssignColumnToRange(0, 0, gridheight_, column_set_costs, assigned_costs);
754  }
755  // Free memory.
756  for (int i = 0; i < set_count; ++i) {
757  delete [] column_set_costs[i];
758  }
759  delete [] assigned_costs;
760  delete [] any_columns_possible;
761  delete [] column_set_costs;
762  return any_multi_column;
763 }
764 
765 // Finds the biggest range in part_sets_ that has no assigned column, but
766 // column assignment is possible.
767 bool ColumnFinder::BiggestUnassignedRange(int set_count,
768  const bool* any_columns_possible,
769  int* best_start, int* best_end) {
770  int best_range_size = 0;
771  *best_start = set_count;
772  *best_end = set_count;
773  int end = set_count;
774  for (int start = 0; start < gridheight_; start = end) {
775  // Find the first unassigned index in start.
776  while (start < set_count) {
777  if (best_columns_[start] == NULL && any_columns_possible[start])
778  break;
779  ++start;
780  }
781  // Find the first past the end and count the good ones in between.
782  int range_size = 1; // Number of non-null, but unassigned line sets.
783  end = start + 1;
784  while (end < set_count) {
785  if (best_columns_[end] != NULL)
786  break;
787  if (any_columns_possible[end])
788  ++range_size;
789  ++end;
790  }
791  if (start < set_count && range_size > best_range_size) {
792  best_range_size = range_size;
793  *best_start = start;
794  *best_end = end;
795  }
796  }
797  return *best_start < *best_end;
798 }
799 
800 // Finds the modal compatible column_set_ index within the given range.
801 int ColumnFinder::RangeModalColumnSet(int** column_set_costs,
802  const int* assigned_costs,
803  int start, int end) {
804  int column_count = column_sets_.size();
805  STATS column_stats(0, column_count);
806  for (int part_i = start; part_i < end; ++part_i) {
807  for (int col_j = 0; col_j < column_count; ++col_j) {
808  if (column_set_costs[part_i][col_j] < assigned_costs[part_i])
809  column_stats.add(col_j, 1);
810  }
811  }
812  ASSERT_HOST(column_stats.get_total() > 0);
813  return column_stats.mode();
814 }
815 
816 // Given that there are many column_set_id compatible columns in the range,
817 // shrinks the range to the longest contiguous run of compatibility, allowing
818 // gaps where no columns are possible, but not where competing columns are
819 // possible.
820 void ColumnFinder::ShrinkRangeToLongestRun(int** column_set_costs,
821  const int* assigned_costs,
822  const bool* any_columns_possible,
823  int column_set_id,
824  int* best_start, int* best_end) {
825  // orig_start and orig_end are the maximum range we will look at.
826  int orig_start = *best_start;
827  int orig_end = *best_end;
828  int best_range_size = 0;
829  *best_start = orig_end;
830  *best_end = orig_end;
831  int end = orig_end;
832  for (int start = orig_start; start < orig_end; start = end) {
833  // Find the first possible
834  while (start < orig_end) {
835  if (column_set_costs[start][column_set_id] < assigned_costs[start] ||
836  !any_columns_possible[start])
837  break;
838  ++start;
839  }
840  // Find the first past the end.
841  end = start + 1;
842  while (end < orig_end) {
843  if (column_set_costs[end][column_set_id] >= assigned_costs[start] &&
844  any_columns_possible[end])
845  break;
846  ++end;
847  }
848  if (start < orig_end && end - start > best_range_size) {
849  best_range_size = end - start;
850  *best_start = start;
851  *best_end = end;
852  }
853  }
854 }
855 
856 // Moves start in the direction of step, up to, but not including end while
857 // the only incompatible regions are no more than kMaxIncompatibleColumnCount
858 // in size, and the compatible regions beyond are bigger.
859 void ColumnFinder::ExtendRangePastSmallGaps(int** column_set_costs,
860  const int* assigned_costs,
861  const bool* any_columns_possible,
862  int column_set_id,
863  int step, int end, int* start) {
864  if (textord_debug_tabfind > 2)
865  tprintf("Starting expansion at %d, step=%d, limit=%d\n",
866  *start, step, end);
867  if (*start == end)
868  return; // Cannot be expanded.
869 
870  int barrier_size = 0;
871  int good_size = 0;
872  do {
873  // Find the size of the incompatible barrier.
874  barrier_size = 0;
875  int i;
876  for (i = *start + step; i != end; i += step) {
877  if (column_set_costs[i][column_set_id] < assigned_costs[i])
878  break; // We are back on.
879  // Locations where none are possible don't count.
880  if (any_columns_possible[i])
881  ++barrier_size;
882  }
883  if (textord_debug_tabfind > 2)
884  tprintf("At %d, Barrier size=%d\n", i, barrier_size);
885  if (barrier_size > kMaxIncompatibleColumnCount)
886  return; // Barrier too big.
887  if (i == end) {
888  // We can't go any further, but the barrier was small, so go to the end.
889  *start = i - step;
890  return;
891  }
892  // Now find the size of the good region on the other side.
893  good_size = 1;
894  for (i += step; i != end; i += step) {
895  if (column_set_costs[i][column_set_id] < assigned_costs[i])
896  ++good_size;
897  else if (any_columns_possible[i])
898  break;
899  }
900  if (textord_debug_tabfind > 2)
901  tprintf("At %d, good size = %d\n", i, good_size);
902  // If we had enough good ones we can extend the start and keep looking.
903  if (good_size >= barrier_size)
904  *start = i - step;
905  } while (good_size >= barrier_size);
906 }
907 
908 // Assigns the given column_set_id to the given range.
909 void ColumnFinder::AssignColumnToRange(int column_set_id, int start, int end,
910  int** column_set_costs,
911  int* assigned_costs) {
912  ColPartitionSet* column_set = column_sets_.get(column_set_id);
913  for (int i = start; i < end; ++i) {
914  assigned_costs[i] = column_set_costs[i][column_set_id];
915  best_columns_[i] = column_set;
916  }
917 }
918 
919 // Computes the mean_column_gap_.
920 void ColumnFinder::ComputeMeanColumnGap(bool any_multi_column) {
921  int total_gap = 0;
922  int total_width = 0;
923  int gap_samples = 0;
924  int width_samples = 0;
925  for (int i = 0; i < gridheight_; ++i) {
926  ASSERT_HOST(best_columns_[i] != NULL);
927  best_columns_[i]->AccumulateColumnWidthsAndGaps(&total_width,
928  &width_samples,
929  &total_gap,
930  &gap_samples);
931  }
932  mean_column_gap_ = any_multi_column && gap_samples > 0
933  ? total_gap / gap_samples : total_width / width_samples;
934 }
935 
938 
939 // Helper to delete all the deletable blobs on the list. Owned blobs are
940 // extracted from the list, but not deleted, leaving them owned by the owner().
941 static void ReleaseAllBlobsAndDeleteUnused(BLOBNBOX_LIST* blobs) {
942  for (BLOBNBOX_IT blob_it(blobs); !blob_it.empty(); blob_it.forward()) {
943  BLOBNBOX* blob = blob_it.extract();
944  if (blob->owner() == NULL) {
945  delete blob->cblob();
946  delete blob;
947  }
948  }
949 }
950 
951 // Hoovers up all un-owned blobs and deletes them.
952 // The rest get released from the block so the ColPartitions can pass
953 // ownership to the output blocks.
954 void ColumnFinder::ReleaseBlobsAndCleanupUnused(TO_BLOCK* block) {
955  ReleaseAllBlobsAndDeleteUnused(&block->blobs);
956  ReleaseAllBlobsAndDeleteUnused(&block->small_blobs);
957  ReleaseAllBlobsAndDeleteUnused(&block->noise_blobs);
958  ReleaseAllBlobsAndDeleteUnused(&block->large_blobs);
959  ReleaseAllBlobsAndDeleteUnused(&image_bblobs_);
960 }
961 
962 // Splits partitions that cross columns where they have nothing in the gap.
963 void ColumnFinder::GridSplitPartitions() {
964  // Iterate the ColPartitions in the grid.
966  gsearch(&part_grid_);
967  gsearch.StartFullSearch();
968  ColPartition* dont_repeat = NULL;
969  ColPartition* part;
970  while ((part = gsearch.NextFullSearch()) != NULL) {
971  if (part->blob_type() < BRT_UNKNOWN || part == dont_repeat)
972  continue; // Only applies to text partitions.
973  ColPartitionSet* column_set = best_columns_[gsearch.GridY()];
974  int first_col = -1;
975  int last_col = -1;
976  // Find which columns the partition spans.
977  part->ColumnRange(resolution_, column_set, &first_col, &last_col);
978  if (first_col > 0)
979  --first_col;
980  // Convert output column indices to physical column indices.
981  first_col /= 2;
982  last_col /= 2;
983  // We will only consider cases where a partition spans two columns,
984  // since a heading that spans more columns than that is most likely
985  // genuine.
986  if (last_col != first_col + 1)
987  continue;
988  // Set up a rectangle search x-bounded by the column gap and y by the part.
989  int y = part->MidY();
990  TBOX margin_box = part->bounding_box();
991  bool debug = AlignedBlob::WithinTestRegion(2, margin_box.left(),
992  margin_box.bottom());
993  if (debug) {
994  tprintf("Considering partition for GridSplit:");
995  part->Print();
996  }
997  ColPartition* column = column_set->GetColumnByIndex(first_col);
998  if (column == NULL)
999  continue;
1000  margin_box.set_left(column->RightAtY(y) + 2);
1001  column = column_set->GetColumnByIndex(last_col);
1002  if (column == NULL)
1003  continue;
1004  margin_box.set_right(column->LeftAtY(y) - 2);
1005  // TODO(rays) Decide whether to keep rectangular filling or not in the
1006  // main grid and therefore whether we need a fancier search here.
1007  // Now run the rect search on the main blob grid.
1009  if (debug) {
1010  tprintf("Searching box (%d,%d)->(%d,%d)\n",
1011  margin_box.left(), margin_box.bottom(),
1012  margin_box.right(), margin_box.top());
1013  part->Print();
1014  }
1015  rectsearch.StartRectSearch(margin_box);
1016  BLOBNBOX* bbox;
1017  while ((bbox = rectsearch.NextRectSearch()) != NULL) {
1018  if (bbox->bounding_box().overlap(margin_box))
1019  break;
1020  }
1021  if (bbox == NULL) {
1022  // There seems to be nothing in the hole, so split the partition.
1023  gsearch.RemoveBBox();
1024  int x_middle = (margin_box.left() + margin_box.right()) / 2;
1025  if (debug) {
1026  tprintf("Splitting part at %d:", x_middle);
1027  part->Print();
1028  }
1029  ColPartition* split_part = part->SplitAt(x_middle);
1030  if (split_part != NULL) {
1031  if (debug) {
1032  tprintf("Split result:");
1033  part->Print();
1034  split_part->Print();
1035  }
1036  part_grid_.InsertBBox(true, true, split_part);
1037  } else {
1038  // Split had no effect
1039  if (debug)
1040  tprintf("Split had no effect\n");
1041  dont_repeat = part;
1042  }
1043  part_grid_.InsertBBox(true, true, part);
1044  gsearch.RepositionIterator();
1045  } else if (debug) {
1046  tprintf("Part cannot be split: blob (%d,%d)->(%d,%d) in column gap\n",
1047  bbox->bounding_box().left(), bbox->bounding_box().bottom(),
1048  bbox->bounding_box().right(), bbox->bounding_box().top());
1049  }
1050  }
1051 }
1052 
1053 // Merges partitions where there is vertical overlap, within a single column,
1054 // and the horizontal gap is small enough.
1055 void ColumnFinder::GridMergePartitions() {
1056  // Iterate the ColPartitions in the grid.
1058  gsearch(&part_grid_);
1059  gsearch.StartFullSearch();
1060  ColPartition* part;
1061  while ((part = gsearch.NextFullSearch()) != NULL) {
1062  if (part->IsUnMergeableType())
1063  continue;
1064  // Set up a rectangle search x-bounded by the column and y by the part.
1065  ColPartitionSet* columns = best_columns_[gsearch.GridY()];
1066  TBOX box = part->bounding_box();
1067  bool debug = AlignedBlob::WithinTestRegion(1, box.left(), box.bottom());
1068  if (debug) {
1069  tprintf("Considering part for merge at:");
1070  part->Print();
1071  }
1072  int y = part->MidY();
1073  ColPartition* left_column = columns->ColumnContaining(box.left(), y);
1074  ColPartition* right_column = columns->ColumnContaining(box.right(), y);
1075  if (left_column == NULL || right_column != left_column) {
1076  if (debug)
1077  tprintf("In different columns\n");
1078  continue;
1079  }
1080  box.set_left(left_column->LeftAtY(y));
1081  box.set_right(right_column->RightAtY(y));
1082  // Now run the rect search.
1083  bool modified_box = false;
1085  rsearch(&part_grid_);
1086  rsearch.SetUniqueMode(true);
1087  rsearch.StartRectSearch(box);
1088  ColPartition* neighbour;
1089 
1090  while ((neighbour = rsearch.NextRectSearch()) != NULL) {
1091  if (neighbour == part || neighbour->IsUnMergeableType())
1092  continue;
1093  const TBOX& neighbour_box = neighbour->bounding_box();
1094  if (debug) {
1095  tprintf("Considering merge with neighbour at:");
1096  neighbour->Print();
1097  }
1098  if (neighbour_box.right() < box.left() ||
1099  neighbour_box.left() > box.right())
1100  continue; // Not within the same column.
1101  if (part->VSignificantCoreOverlap(*neighbour) &&
1102  part->TypesMatch(*neighbour)) {
1103  // There is vertical overlap and the gross types match, but only
1104  // merge if the horizontal gap is small enough, as one of the
1105  // partitions may be a figure caption within a column.
1106  // If there is only one column, then the mean_column_gap_ is large
1107  // enough to allow almost any merge, by being the mean column width.
1108  const TBOX& part_box = part->bounding_box();
1109  // Don't merge if there is something else in the way. Use the margin
1110  // to decide, and check both to allow a bit of overlap.
1111  if (neighbour_box.left() > part->right_margin() &&
1112  part_box.right() < neighbour->left_margin())
1113  continue; // Neighbour is too far to the right.
1114  if (neighbour_box.right() < part->left_margin() &&
1115  part_box.left() > neighbour->right_margin())
1116  continue; // Neighbour is too far to the left.
1117  int h_gap = MAX(part_box.left(), neighbour_box.left()) -
1118  MIN(part_box.right(), neighbour_box.right());
1119  if (h_gap < mean_column_gap_ * kHorizontalGapMergeFraction ||
1120  part_box.width() < mean_column_gap_ ||
1121  neighbour_box.width() < mean_column_gap_) {
1122  if (debug) {
1123  tprintf("Running grid-based merge between:\n");
1124  part->Print();
1125  neighbour->Print();
1126  }
1127  rsearch.RemoveBBox();
1128  if (!modified_box) {
1129  // We are going to modify part, so remove it and re-insert it after.
1130  gsearch.RemoveBBox();
1131  rsearch.RepositionIterator();
1132  modified_box = true;
1133  }
1134  part->Absorb(neighbour, WidthCB());
1135  } else if (debug) {
1136  tprintf("Neighbour failed hgap test\n");
1137  }
1138  } else if (debug) {
1139  tprintf("Neighbour failed overlap or typesmatch test\n");
1140  }
1141  }
1142  if (modified_box) {
1143  // We modified the box of part, so re-insert it into the grid.
1144  // This does no harm in the current cell, as it already exists there,
1145  // but it needs to exist in all the cells covered by its bounding box,
1146  // or it will never be found by a full search.
1147  // Because the box has changed, it has to be removed first, otherwise
1148  // add_sorted may fail to keep a single copy of the pointer.
1149  part_grid_.InsertBBox(true, true, part);
1150  gsearch.RepositionIterator();
1151  }
1152  }
1153 }
1154 
1155 // Inserts remaining noise blobs into the most applicable partition if any.
1156 // If there is no applicable partition, then the blobs are deleted.
1157 void ColumnFinder::InsertRemainingNoise(TO_BLOCK* block) {
1158  BLOBNBOX_IT blob_it(&block->noise_blobs);
1159  for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
1160  BLOBNBOX* blob = blob_it.data();
1161  if (blob->owner() != NULL) continue;
1162  TBOX search_box(blob->bounding_box());
1163  bool debug = WithinTestRegion(2, search_box.left(), search_box.bottom());
1164  search_box.pad(gridsize(), gridsize());
1165  // Setup a rectangle search to find the best partition to merge with.
1166  ColPartitionGridSearch rsearch(&part_grid_);
1167  rsearch.SetUniqueMode(true);
1168  rsearch.StartRectSearch(search_box);
1169  ColPartition* part;
1170  ColPartition* best_part = NULL;
1171  int best_distance = 0;
1172  while ((part = rsearch.NextRectSearch()) != NULL) {
1173  if (part->IsUnMergeableType())
1174  continue;
1175  int distance = projection_.DistanceOfBoxFromPartition(
1176  blob->bounding_box(), *part, denorm_, debug);
1177  if (best_part == NULL || distance < best_distance) {
1178  best_part = part;
1179  best_distance = distance;
1180  }
1181  }
1182  if (best_part != NULL &&
1183  best_distance < kMaxDistToPartSizeRatio * best_part->median_size()) {
1184  // Close enough to merge.
1185  if (debug) {
1186  tprintf("Adding noise blob with distance %d, thr=%g:box:",
1187  best_distance,
1188  kMaxDistToPartSizeRatio * best_part->median_size());
1189  blob->bounding_box().print();
1190  tprintf("To partition:");
1191  best_part->Print();
1192  }
1193  part_grid_.RemoveBBox(best_part);
1194  best_part->AddBox(blob);
1195  part_grid_.InsertBBox(true, true, best_part);
1196  blob->set_owner(best_part);
1197  blob->set_flow(best_part->flow());
1198  blob->set_region_type(best_part->blob_type());
1199  } else {
1200  // Mark the blob for deletion.
1201  blob->set_region_type(BRT_NOISE);
1202  }
1203  }
1204  // Delete the marked blobs, clearing neighbour references.
1205  block->DeleteUnownedNoise();
1206 }
1207 
1208 // Helper makes a box from a horizontal line.
1209 static TBOX BoxFromHLine(const TabVector* hline) {
1210  int top = MAX(hline->startpt().y(), hline->endpt().y());
1211  int bottom = MIN(hline->startpt().y(), hline->endpt().y());
1212  top += hline->mean_width();
1213  if (top == bottom) {
1214  if (bottom > 0)
1215  --bottom;
1216  else
1217  ++top;
1218  }
1219  return TBOX(hline->startpt().x(), bottom, hline->endpt().x(), top);
1220 }
1221 
1222 // Remove partitions that come from horizontal lines that look like
1223 // underlines, but are not part of a table.
1224 void ColumnFinder::GridRemoveUnderlinePartitions() {
1225  TabVector_IT hline_it(&horizontal_lines_);
1226  for (hline_it.mark_cycle_pt(); !hline_it.cycled_list(); hline_it.forward()) {
1227  TabVector* hline = hline_it.data();
1228  if (hline->intersects_other_lines())
1229  continue;
1230  TBOX line_box = BoxFromHLine(hline);
1231  TBOX search_box = line_box;
1232  search_box.pad(0, line_box.height());
1233  ColPartitionGridSearch part_search(&part_grid_);
1234  part_search.SetUniqueMode(true);
1235  part_search.StartRectSearch(search_box);
1236  ColPartition* covered;
1237  bool touched_table = false;
1238  bool touched_text = false;
1239  ColPartition* line_part = NULL;
1240  while ((covered = part_search.NextRectSearch()) != NULL) {
1241  if (covered->type() == PT_TABLE) {
1242  touched_table = true;
1243  break;
1244  } else if (covered->IsTextType()) {
1245  // TODO(rays) Add a list of underline sections to ColPartition.
1246  int text_bottom = covered->median_bottom();
1247  if (line_box.bottom() <= text_bottom && text_bottom <= search_box.top())
1248  touched_text = true;
1249  } else if (covered->blob_type() == BRT_HLINE &&
1250  line_box.contains(covered->bounding_box())) {
1251  line_part = covered;
1252  }
1253  }
1254  if (line_part != NULL && !touched_table && touched_text) {
1255  part_grid_.RemoveBBox(line_part);
1256  delete line_part;
1257  }
1258  }
1259 }
1260 
1261 // Add horizontal line separators as partitions.
1262 void ColumnFinder::GridInsertHLinePartitions() {
1263  TabVector_IT hline_it(&horizontal_lines_);
1264  for (hline_it.mark_cycle_pt(); !hline_it.cycled_list(); hline_it.forward()) {
1265  TabVector* hline = hline_it.data();
1266  TBOX line_box = BoxFromHLine(hline);
1269  line_box.left(), line_box.bottom(), line_box.right(), line_box.top());
1270  part->set_type(PT_HORZ_LINE);
1271  bool any_image = false;
1272  ColPartitionGridSearch part_search(&part_grid_);
1273  part_search.SetUniqueMode(true);
1274  part_search.StartRectSearch(line_box);
1275  ColPartition* covered;
1276  while ((covered = part_search.NextRectSearch()) != NULL) {
1277  if (covered->IsImageType()) {
1278  any_image = true;
1279  break;
1280  }
1281  }
1282  if (!any_image)
1283  part_grid_.InsertBBox(true, true, part);
1284  else
1285  delete part;
1286  }
1287 }
1288 
1289 // Add horizontal line separators as partitions.
1290 void ColumnFinder::GridInsertVLinePartitions() {
1291  TabVector_IT vline_it(dead_vectors());
1292  for (vline_it.mark_cycle_pt(); !vline_it.cycled_list(); vline_it.forward()) {
1293  TabVector* vline = vline_it.data();
1294  if (!vline->IsSeparator())
1295  continue;
1296  int left = MIN(vline->startpt().x(), vline->endpt().x());
1297  int right = MAX(vline->startpt().x(), vline->endpt().x());
1298  right += vline->mean_width();
1299  if (left == right) {
1300  if (left > 0)
1301  --left;
1302  else
1303  ++right;
1304  }
1307  left, vline->startpt().y(), right, vline->endpt().y());
1308  part->set_type(PT_VERT_LINE);
1309  bool any_image = false;
1310  ColPartitionGridSearch part_search(&part_grid_);
1311  part_search.SetUniqueMode(true);
1312  part_search.StartRectSearch(part->bounding_box());
1313  ColPartition* covered;
1314  while ((covered = part_search.NextRectSearch()) != NULL) {
1315  if (covered->IsImageType()) {
1316  any_image = true;
1317  break;
1318  }
1319  }
1320  if (!any_image)
1321  part_grid_.InsertBBox(true, true, part);
1322  else
1323  delete part;
1324  }
1325 }
1326 
1327 // For every ColPartition in the grid, sets its type based on position
1328 // in the columns.
1329 void ColumnFinder::SetPartitionTypes() {
1331  gsearch(&part_grid_);
1332  gsearch.StartFullSearch();
1333  ColPartition* part;
1334  while ((part = gsearch.NextFullSearch()) != NULL) {
1335  part->SetPartitionType(resolution_, best_columns_[gsearch.GridY()]);
1336  }
1337 }
1338 
1339 // Only images remain with multiple types in a run of partners.
1340 // Sets the type of all in the group to the maximum of the group.
1341 void ColumnFinder::SmoothPartnerRuns() {
1342  // Iterate the ColPartitions in the grid.
1344  gsearch(&part_grid_);
1345  gsearch.StartFullSearch();
1346  ColPartition* part;
1347  while ((part = gsearch.NextFullSearch()) != NULL) {
1348  ColPartition* partner = part->SingletonPartner(true);
1349  if (partner != NULL) {
1350  if (partner->SingletonPartner(false) != part) {
1351  tprintf("Ooops! Partition:(%d partners)",
1352  part->upper_partners()->length());
1353  part->Print();
1354  tprintf("has singleton partner:(%d partners",
1355  partner->lower_partners()->length());
1356  partner->Print();
1357  tprintf("but its singleton partner is:");
1358  if (partner->SingletonPartner(false) == NULL)
1359  tprintf("NULL\n");
1360  else
1361  partner->SingletonPartner(false)->Print();
1362  }
1363  ASSERT_HOST(partner->SingletonPartner(false) == part);
1364  } else if (part->SingletonPartner(false) != NULL) {
1365  ColPartitionSet* column_set = best_columns_[gsearch.GridY()];
1366  int column_count = column_set->ColumnCount();
1367  part->SmoothPartnerRun(column_count * 2 + 1);
1368  }
1369  }
1370 }
1371 
1372 // Helper functions for TransformToBlocks.
1373 // Add the part to the temp list in the correct order.
1374 void ColumnFinder::AddToTempPartList(ColPartition* part,
1375  ColPartition_CLIST* temp_list) {
1376  int mid_y = part->MidY();
1377  ColPartition_C_IT it(temp_list);
1378  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
1379  ColPartition* test_part = it.data();
1380  if (part->type() == PT_NOISE || test_part->type() == PT_NOISE)
1381  continue; // Noise stays in sequence.
1382  if (test_part == part->SingletonPartner(false))
1383  break; // Insert before its lower partner.
1384  int neighbour_bottom = test_part->median_bottom();
1385  int neighbour_top = test_part->median_top();
1386  int neighbour_y = (neighbour_bottom + neighbour_top) / 2;
1387  if (neighbour_y < mid_y)
1388  break; // part is above test_part so insert it.
1389  if (!part->HOverlaps(*test_part) && !part->WithinSameMargins(*test_part))
1390  continue; // Incompatibles stay in order
1391  }
1392  if (it.cycled_list()) {
1393  it.add_to_end(part);
1394  } else {
1395  it.add_before_stay_put(part);
1396  }
1397 }
1398 
1399 // Add everything from the temp list to the work_set assuming correct order.
1400 void ColumnFinder::EmptyTempPartList(ColPartition_CLIST* temp_list,
1401  WorkingPartSet_LIST* work_set) {
1402  ColPartition_C_IT it(temp_list);
1403  while (!it.empty()) {
1404  it.extract()->AddToWorkingSet(bleft_, tright_, resolution_,
1405  &good_parts_, work_set);
1406  it.forward();
1407  }
1408 }
1409 
1410 // Transform the grid of partitions to the output blocks.
1411 void ColumnFinder::TransformToBlocks(BLOCK_LIST* blocks,
1412  TO_BLOCK_LIST* to_blocks) {
1413  WorkingPartSet_LIST work_set;
1414  ColPartitionSet* column_set = NULL;
1415  ColPartition_IT noise_it(&noise_parts_);
1416  // The temp_part_list holds a list of parts at the same grid y coord
1417  // so they can be added in the correct order. This prevents thin objects
1418  // like horizontal lines going before the text lines above them.
1419  ColPartition_CLIST temp_part_list;
1420  // Iterate the ColPartitions in the grid. It starts at the top
1422  gsearch(&part_grid_);
1423  gsearch.StartFullSearch();
1424  int prev_grid_y = -1;
1425  ColPartition* part;
1426  while ((part = gsearch.NextFullSearch()) != NULL) {
1427  int grid_y = gsearch.GridY();
1428  if (grid_y != prev_grid_y) {
1429  EmptyTempPartList(&temp_part_list, &work_set);
1430  prev_grid_y = grid_y;
1431  }
1432  if (best_columns_[grid_y] != column_set) {
1433  column_set = best_columns_[grid_y];
1434  // Every line should have a non-null best column.
1435  ASSERT_HOST(column_set != NULL);
1437  &good_parts_, &work_set);
1439  tprintf("Changed column groups at grid index %d, y=%d\n",
1440  gsearch.GridY(), gsearch.GridY() * gridsize());
1441  }
1442  if (part->type() == PT_NOISE) {
1443  noise_it.add_to_end(part);
1444  } else {
1445  AddToTempPartList(part, &temp_part_list);
1446  }
1447  }
1448  EmptyTempPartList(&temp_part_list, &work_set);
1449  // Now finish all working sets and transfer ColPartitionSets to block_sets.
1450  WorkingPartSet_IT work_it(&work_set);
1451  while (!work_it.empty()) {
1452  WorkingPartSet* working_set = work_it.extract();
1454  &good_parts_, blocks, to_blocks);
1455  delete working_set;
1456  work_it.forward();
1457  }
1458 }
1459 
1460 // Helper reflects a list of blobs in the y-axis.
1461 // Only reflects the BLOBNBOX bounding box. Not the blobs or outlines below.
1462 static void ReflectBlobList(BLOBNBOX_LIST* bblobs) {
1463  BLOBNBOX_IT it(bblobs);
1464  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
1465  it.data()->reflect_box_in_y_axis();
1466  }
1467 }
1468 
1469 // Reflect the blob boxes (but not the outlines) in the y-axis so that
1470 // the blocks get created in the correct RTL order. Reflects the blobs
1471 // in the input_block and the bblobs list.
1472 // The reflection is undone in RotateAndReskewBlocks by
1473 // reflecting the blocks themselves, and then recomputing the blob bounding
1474 // boxes.
1475 void ColumnFinder::ReflectForRtl(TO_BLOCK* input_block, BLOBNBOX_LIST* bblobs) {
1476  ReflectBlobList(bblobs);
1477  ReflectBlobList(&input_block->blobs);
1478  ReflectBlobList(&input_block->small_blobs);
1479  ReflectBlobList(&input_block->noise_blobs);
1480  ReflectBlobList(&input_block->large_blobs);
1481  // Update the denorm with the reflection.
1482  DENORM* new_denorm = new DENORM;
1483  new_denorm->SetupNormalization(NULL, NULL, denorm_,
1484  0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.0f);
1485  denorm_ = new_denorm;
1486 }
1487 
1488 // Helper fixes up blobs and cblobs to match the desired rotation,
1489 // exploding multi-outline blobs back to single blobs and accumulating
1490 // the bounding box widths and heights.
1491 static void RotateAndExplodeBlobList(const FCOORD& blob_rotation,
1492  BLOBNBOX_LIST* bblobs,
1493  STATS* widths,
1494  STATS* heights) {
1495  BLOBNBOX_IT it(bblobs);
1496  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
1497  BLOBNBOX* blob = it.data();
1498  C_BLOB* cblob = blob->cblob();
1499  C_OUTLINE_LIST* outlines = cblob->out_list();
1500  C_OUTLINE_IT ol_it(outlines);
1501  if (!outlines->singleton()) {
1502  // This blob has multiple outlines from CJK repair.
1503  // Explode the blob back into individual outlines.
1504  for (;!ol_it.empty(); ol_it.forward()) {
1505  C_OUTLINE* outline = ol_it.extract();
1506  BLOBNBOX* new_blob = BLOBNBOX::RealBlob(outline);
1507  // This blob will be revisited later since we add_after_stay_put here.
1508  // This means it will get rotated and have its width/height added to
1509  // the stats below.
1510  it.add_after_stay_put(new_blob);
1511  }
1512  it.extract();
1513  delete cblob;
1514  delete blob;
1515  } else {
1516  if (blob_rotation.x() != 1.0f || blob_rotation.y() != 0.0f) {
1517  cblob->rotate(blob_rotation);
1518  }
1519  blob->compute_bounding_box();
1520  widths->add(blob->bounding_box().width(), 1);
1521  heights->add(blob->bounding_box().height(), 1);
1522  }
1523  }
1524 }
1525 
1526 // Undo the deskew that was done in FindTabVectors, as recognition is done
1527 // without correcting blobs or blob outlines for skew.
1528 // Reskew the completed blocks to put them back to the original rotated coords
1529 // that were created by CorrectOrientation.
1530 // If the input_is_rtl, then reflect the blocks in the y-axis to undo the
1531 // reflection that was done before FindTabVectors.
1532 // Blocks that were identified as vertical text (relative to the rotated
1533 // coordinates) are further rotated so the text lines are horizontal.
1534 // blob polygonal outlines are rotated to match the position of the blocks
1535 // that they are in, and their bounding boxes are recalculated to be accurate.
1536 // Record appropriate inverse transformations and required
1537 // classifier transformation in the blocks.
1538 void ColumnFinder::RotateAndReskewBlocks(bool input_is_rtl,
1539  TO_BLOCK_LIST* blocks) {
1540  if (input_is_rtl) {
1541  // The skew is backwards because of the reflection.
1542  FCOORD tmp = deskew_;
1543  deskew_ = reskew_;
1544  reskew_ = tmp;
1545  }
1546  TO_BLOCK_IT it(blocks);
1547  int block_index = 1;
1548  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
1549  TO_BLOCK* to_block = it.data();
1550  BLOCK* block = to_block->block;
1551  // Blocks are created on the deskewed blob outlines in TransformToBlocks()
1552  // so we need to reskew them back to page coordinates.
1553  if (input_is_rtl) {
1554  block->reflect_polygon_in_y_axis();
1555  }
1556  block->rotate(reskew_);
1557  // Copy the right_to_left flag to the created block.
1558  block->set_right_to_left(input_is_rtl);
1559  // Save the skew angle in the block for baseline computations.
1560  block->set_skew(reskew_);
1561  block->set_index(block_index++);
1562  FCOORD blob_rotation = ComputeBlockAndClassifyRotation(block);
1563  // Rotate all the blobs if needed and recompute the bounding boxes.
1564  // Compute the block median blob width and height as we go.
1565  STATS widths(0, block->bounding_box().width());
1566  STATS heights(0, block->bounding_box().height());
1567  RotateAndExplodeBlobList(blob_rotation, &to_block->blobs,
1568  &widths, &heights);
1569  TO_ROW_IT row_it(to_block->get_rows());
1570  for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
1571  TO_ROW* row = row_it.data();
1572  RotateAndExplodeBlobList(blob_rotation, row->blob_list(),
1573  &widths, &heights);
1574  }
1575  block->set_median_size(static_cast<int>(widths.median() + 0.5),
1576  static_cast<int>(heights.median() + 0.5));
1577  if (textord_debug_tabfind >= 2)
1578  tprintf("Block median size = (%d, %d)\n",
1579  block->median_size().x(), block->median_size().y());
1580  }
1581 }
1582 
1583 // Computes the rotations for the block (to make textlines horizontal) and
1584 // for the blobs (for classification) and sets the appropriate members
1585 // of the given block.
1586 // Returns the rotation that needs to be applied to the blobs to make
1587 // them sit in the rotated block.
1588 FCOORD ColumnFinder::ComputeBlockAndClassifyRotation(BLOCK* block) {
1589  // The text_rotation_ tells us the gross page text rotation that needs
1590  // to be applied for classification
1591  // TODO(rays) find block-level classify rotation by orientation detection.
1592  // In the mean time, assume that "up" for text printed in the minority
1593  // direction (PT_VERTICAL_TEXT) is perpendicular to the line of reading.
1594  // Accomplish this by zero-ing out the text rotation. This covers the
1595  // common cases of image credits in documents written in Latin scripts
1596  // and page headings for predominantly vertically written CJK books.
1597  FCOORD classify_rotation(text_rotation_);
1598  FCOORD block_rotation(1.0f, 0.0f);
1599  if (block->poly_block()->isA() == PT_VERTICAL_TEXT) {
1600  // Vertical text needs to be 90 degrees rotated relative to the rest.
1601  // If the rest has a 90 degree rotation already, use the inverse, making
1602  // the vertical text the original way up. Otherwise use 90 degrees
1603  // clockwise.
1604  if (rerotate_.x() == 0.0f)
1605  block_rotation = rerotate_;
1606  else
1607  block_rotation = FCOORD(0.0f, -1.0f);
1608  block->rotate(block_rotation);
1609  classify_rotation = FCOORD(1.0f, 0.0f);
1610  }
1611  block_rotation.rotate(rotation_);
1612  // block_rotation is now what we have done to the blocks. Now do the same
1613  // thing to the blobs, but save the inverse rotation in the block, as that
1614  // is what we need to DENORM back to the image coordinates.
1615  FCOORD blob_rotation(block_rotation);
1616  block_rotation.set_y(-block_rotation.y());
1617  block->set_re_rotation(block_rotation);
1618  block->set_classify_rotation(classify_rotation);
1619  if (textord_debug_tabfind) {
1620  tprintf("Blk %d, type %d rerotation(%.2f, %.2f), char(%.2f,%.2f), box:",
1621  block->index(), block->poly_block()->isA(),
1622  block->re_rotation().x(), block->re_rotation().y(),
1623  classify_rotation.x(), classify_rotation.y());
1624  block->bounding_box().print();
1625  }
1626  return blob_rotation;
1627 }
1628 
1629 } // namespace tesseract.
void ExtractCompletedBlocks(const ICOORD &bleft, const ICOORD &tright, int resolution, ColPartition_LIST *used_parts, BLOCK_LIST *blocks, TO_BLOCK_LIST *to_blocks)
ColPartition * ColumnContaining(int x, int y)
bool PSM_SPARSE(int pageseg_mode)
Definition: publictypes.h:188
bool textord_debug_printable
Definition: alignedblob.cpp:33
C_OUTLINE_LIST * out_list()
Definition: stepblob.h:64
void rotate(const FCOORD &rotation)
Definition: ocrblock.cpp:85
Definition: capi.h:95
void ColumnRange(int resolution, ColPartitionSet *columns, int *first_col, int *last_col)
bool textord_tabfind_show_columns
Definition: colfind.cpp:65
bool overlap(const TBOX &box) const
Definition: rect.h:345
bool MakeColPartSets(PartSetVector *part_sets)
int GridY() const
Definition: bbgrid.h:247
const TBOX & bounding_box() const
void AccumulateColumnWidthsAndGaps(int *total_width, int *width_samples, int *total_gap, int *gap_samples)
void DisplayBoxes(ScrollView *window)
Definition: bbgrid.h:617
Definition: points.h:189
void ReSetAndReFilterBlobs()
Definition: blobbox.cpp:1007
ColPartition_CLIST * upper_partners()
Definition: colpartition.h:196
Pix * ComputeNonTextMask(bool debug, Pix *photo_map, TO_BLOCK *blob_block)
inT32 get_total() const
Definition: statistc.h:86
const DENORM * predecessor() const
Definition: normalis.h:265
void set_right_to_left(bool value)
Definition: ocrblock.h:86
void SetBlockRuleEdges(TO_BLOCK *block)
Definition: tabfind.cpp:134
void InsertBlobsToGrid(bool h_spread, bool v_spread, BLOBNBOX_LIST *blobs, BBGrid< BLOBNBOX, BLOBNBOX_CLIST, BLOBNBOX_C_IT > *grid)
Definition: tabfind.cpp:92
int index() const
Definition: pdblock.h:67
ICOORD tright_
Definition: bbgrid.h:92
bool VSignificantCoreOverlap(const ColPartition &other) const
Definition: colpartition.h:387
static BLOBNBOX * RealBlob(C_OUTLINE *outline)
Definition: blobbox.h:143
void SmoothPartnerRun(int working_set_count)
void rotate(const FCOORD vec)
Definition: ipoints.h:471
void plot_graded_blobs(ScrollView *to_win)
Definition: blobbox.cpp:1067
void bounding_box(ICOORD &bottom_left, ICOORD &top_right) const
get box
Definition: pdblock.h:59
bool textord_tabfind_find_tables
Definition: colfind.cpp:67
BLOBNBOX_LIST large_blobs
Definition: blobbox.h:772
#define MAX_INT32
Definition: host.h:62
void LocateTables(ColPartitionGrid *grid, ColPartitionSet **columns, WidthCallback *width_cb, const FCOORD &reskew)
Definition: tablefind.cpp:263
int LeftAtY(int y) const
Definition: colpartition.h:340
void DeleteUnknownParts(TO_BLOCK *block)
ScrollView * DisplayTabVectors(ScrollView *tab_win)
Definition: tabfind.cpp:498
void Init(int gridsize, const ICOORD &bleft, const ICOORD &tright)
Definition: bbgrid.h:448
bool IsUnMergeableType() const
Definition: colpartition.h:443
ScrollView * MakeWindow(int x, int y, const char *window_name)
Definition: bbgrid.h:593
void SetEquationDetect(EquationDetectBase *detect)
Definition: colfind.cpp:507
void ExtractPartitionsAsBlocks(BLOCK_LIST *blocks, TO_BLOCK_LIST *to_blocks)
const ICOORD & bleft() const
Definition: bbgrid.h:73
ColPartition * GetColumnByIndex(int index)
inT16 x() const
access function
Definition: points.h:52
#define tprintf(...)
Definition: tprintf.h:31
bool intersects_other_lines() const
Definition: tabvector.h:179
void move(GenericVector< T > *from)
int gridsize() const
Definition: bbgrid.h:64
const ICOORD & median_size() const
Definition: ocrblock.h:156
void SetupNormalization(const BLOCK *block, const FCOORD *rotation, const DENORM *predecessor, float x_origin, float y_origin, float x_scale, float y_scale, float final_xshift, float final_yshift)
Definition: normalis.cpp:95
C_BLOB * cblob() const
Definition: blobbox.h:253
tesseract::ColPartition * owner() const
Definition: blobbox.h:337
ColPartition_CLIST * lower_partners()
Definition: colpartition.h:199
bool empty() const
Definition: genericvector.h:90
int gridheight() const
Definition: bbgrid.h:70
bool textord_tabfind_show_blocks
Definition: colfind.cpp:66
#define BOOL_VAR(name, val, comment)
Definition: params.h:279
void Clear()
Definition: bbgrid.h:459
Definition: capi.h:94
int size() const
Definition: genericvector.h:72
void AddBox(BLOBNBOX *box)
bool IsVerticallyAlignedText(double find_vertical_text_ratio, TO_BLOCK *block, BLOBNBOX_CLIST *osd_blobs)
Definition: colfind.cpp:184
bool textord_tabfind_show_initial_partitions
Definition: colfind.cpp:60
int RightAtY(int y) const
Definition: colpartition.h:344
void set_y(float yin)
rewrite function
Definition: points.h:220
ColPartitionSet * Copy(bool good_only)
void DontFindTabVectors(BLOBNBOX_LIST *image_blobs, TO_BLOCK *block, FCOORD *deskew, FCOORD *reskew)
Definition: tabfind.cpp:453
void AssertNoDuplicates()
Definition: bbgrid.h:642
void RepositionIterator()
Definition: bbgrid.h:896
void set_region_type(BlobRegionType new_type)
Definition: blobbox.h:271
virtual int FindEquationParts(ColPartitionGrid *part_grid, ColPartitionSet **best_columns)=0
#define ASSERT_HOST(x)
Definition: errcode.h:84
void ComputeEdgeOffsets(Pix *thresholds, Pix *grey)
Definition: blobbox.cpp:1051
inT16 left() const
Definition: rect.h:68
static void FindImagePartitions(Pix *image_pix, const FCOORD &rotation, const FCOORD &rerotation, TO_BLOCK *block, TabFind *tab_grid, DebugPixa *pixa_debug, ColPartitionGrid *part_grid, ColPartition_LIST *big_parts)
Definition: imagefind.cpp:1292
void Clear()
Definition: scrollview.cpp:595
void SetUniqueMode(bool mode)
Definition: bbgrid.h:255
void set_classify_rotation(const FCOORD &rotation)
Definition: ocrblock.h:147
void set_owner(tesseract::ColPartition *new_owner)
Definition: blobbox.h:340
const double kMinGutterWidthGrid
Definition: colfind.cpp:54
void GetDeskewVectors(FCOORD *deskew, FCOORD *reskew)
Definition: colfind.cpp:501
void SetupAndFilterNoise(PageSegMode pageseg_mode, Pix *photo_mask_pix, TO_BLOCK *input_block)
Definition: colfind.cpp:146
void SetPartitionType(int resolution, ColPartitionSet *columns)
const ICOORD & startpt() const
Definition: tabvector.h:146
void CorrectForRotation(const FCOORD &rerotation, ColPartitionGrid *part_grid)
void DisplayColumnEdges(int y_bottom, int y_top, ScrollView *win)
BBC * NextFullSearch()
Definition: bbgrid.h:679
bool IsImageType() const
Definition: colpartition.h:423
static void RotateBlobList(const FCOORD &rotation, BLOBNBOX_LIST *blobs)
Definition: tabfind.cpp:1257
ColPartition * SplitAt(int split_x)
BLOBNBOX_LIST small_blobs
Definition: blobbox.h:771
int textord_debug_tabfind
Definition: alignedblob.cpp:27
void set_type(PolyBlockType t)
Definition: colpartition.h:184
void ChangeWorkColumns(const ICOORD &bleft, const ICOORD &tright, int resolution, ColPartition_LIST *used_parts, WorkingPartSet_LIST *working_set)
ICOORD vertical_skew_
Definition: tabfind.h:367
void InsertBBox(bool h_spread, bool v_spread, BBC *bbox)
Definition: bbgrid.h:490
double median() const
Definition: statistc.cpp:239
int textord_tabfind_show_partitions
Definition: colfind.cpp:64
const TBOX & bounding_box() const
Definition: colpartition.h:109
static void Update()
Definition: scrollview.cpp:715
const int kMaxIncompatibleColumnCount
Definition: colfind.cpp:49
inT16 y() const
access_function
Definition: points.h:56
bool WithinSameMargins(const ColPartition &other) const
Definition: colpartition.h:395
const ICOORD & tright() const
Definition: bbgrid.h:76
BLOBNBOX_LIST blobs
Definition: blobbox.h:768
int DistanceOfBoxFromPartition(const TBOX &box, const ColPartition &part, const DENORM *denorm, bool debug) const
#define INT_VAR(name, val, comment)
Definition: params.h:276
void ImproveColumnCandidate(WidthCallback *cb, PartSetVector *src_sets)
void InsertCleanPartitions(ColPartitionGrid *grid, TO_BLOCK *block)
Definition: tablefind.cpp:197
void DeleteUnownedNoise()
Definition: blobbox.cpp:1033
const double kHorizontalGapMergeFraction
Definition: colfind.cpp:52
void pad(int xpad, int ypad)
Definition: rect.h:127
bool contains(const FCOORD pt) const
Definition: rect.h:323
PolyBlockType type() const
Definition: colpartition.h:181
void RefinePartitionPartners(bool get_desperate)
void set_re_rotation(const FCOORD &rotation)
Definition: ocrblock.h:141
T & get(int index) const
static bool WithinTestRegion(int detail_level, int x, int y)
bool HOverlaps(const ColPartition &other) const
Definition: colpartition.h:365
ColPartitionSet * MakeSingleColumnSet(WidthCallback *cb)
void add(inT32 value, inT32 count)
Definition: statistc.cpp:101
inT16 top() const
Definition: rect.h:54
#define MAX(x, y)
Definition: ndminx.h:24
void set_index(int value)
Definition: pdblock.h:68
const ICOORD & endpt() const
Definition: tabvector.h:149
void set_flow(BlobTextFlowType value)
Definition: blobbox.h:283
int mean_width() const
Definition: tabvector.h:161
inT32 mode() const
Definition: statistc.cpp:115
virtual ~ColumnFinder()
Definition: colfind.cpp:98
Definition: rect.h:30
#define MIN(x, y)
Definition: ndminx.h:28
POLY_BLOCK * poly_block() const
Definition: pdblock.h:55
void TidyBlobs(TO_BLOCK *block)
Definition: tabfind.cpp:466
void ResetForVerticalText(const FCOORD &rotate, const FCOORD &rerotate, TabVector_LIST *horizontal_lines, int *min_gutter_width)
Definition: tabfind.cpp:1301
void reflect_polygon_in_y_axis()
Definition: ocrblock.cpp:108
void SetNeighboursOnMediumBlobs(TO_BLOCK *block)
ScrollView * FindInitialTabVectors(BLOBNBOX_LIST *image_blobs, int min_gutter_width, double tabfind_aligned_gap_fraction, TO_BLOCK *block)
Definition: tabfind.cpp:515
inT16 height() const
Definition: rect.h:104
void plot(ScrollView *window, inT32 serial, ScrollView::Color colour)
Definition: pdblock.cpp:177
BBC * NextRectSearch()
Definition: bbgrid.h:846
static void TransferImagePartsToImageMask(const FCOORD &rerotation, ColPartitionGrid *part_grid, Pix *image_mask)
Definition: imagefind.cpp:1239
void FindLeaderPartitions(TO_BLOCK *block, ColPartitionGrid *part_grid)
void Init(int grid_size, const ICOORD &bottom_left, const ICOORD &top_right)
Definition: tablefind.cpp:185
float y() const
Definition: points.h:212
typedef int(ZCALLBACK *close_file_func) OF((voidpf opaque
inT16 right() const
Definition: rect.h:75
bool PSM_COL_FIND_ENABLED(int pageseg_mode)
Definition: publictypes.h:185
ColPartition * SingletonPartner(bool upper)
inT16 width() const
Definition: rect.h:111
void RemoveBBox(BBC *bbox)
Definition: bbgrid.h:537
void set_right(int x)
Definition: rect.h:78
void set_left(int x)
Definition: rect.h:71
Definition: statistc.h:33
PolyBlockType isA() const
Definition: polyblk.h:48
void print() const
Definition: rect.h:270
void StartFullSearch()
Definition: bbgrid.h:669
FCOORD re_rotation() const
Definition: ocrblock.h:138
const double kMaxDistToPartSizeRatio
Definition: colfind.cpp:57
void Absorb(ColPartition *other, WidthCallback *cb)
ColumnFinder(int gridsize, const ICOORD &bleft, const ICOORD &tright, int resolution, bool cjk_script, double aligned_gap_fraction, TabVector_LIST *vlines, TabVector_LIST *hlines, int vertical_x, int vertical_y)
Definition: colfind.cpp:77
inT16 bottom() const
Definition: rect.h:61
void CorrectOrientation(TO_BLOCK *block, bool vertical_text_lines, int recognition_rotation)
Definition: colfind.cpp:202
bool FindTabVectors(TabVector_LIST *hlines, BLOBNBOX_LIST *image_blobs, TO_BLOCK *block, int min_gutter_width, double tabfind_aligned_gap_fraction, ColPartitionGrid *part_grid, FCOORD *deskew, FCOORD *reskew)
Definition: tabfind.cpp:423
BLOCK * block
Definition: blobbox.h:773
void rotate(const FCOORD &rotation)
Definition: stepblob.cpp:387
bool textord_tabfind_show_reject_blobs
Definition: colfind.cpp:62
void StartRectSearch(const TBOX &rect)
Definition: bbgrid.h:834
void set_left_to_right_language(bool order)
Definition: tablefind.cpp:181
bool right_to_left() const
Definition: ocrblock.h:83
BLOBNBOX_LIST * blob_list()
Definition: blobbox.h:595
void FindTextlineDirectionAndFixBrokenCJK(PageSegMode pageseg_mode, bool cjk_merge, TO_BLOCK *input_block)
TO_ROW_LIST * get_rows()
Definition: blobbox.h:700
BLOBNBOX_LIST noise_blobs
Definition: blobbox.h:770
bool TestVerticalTextDirection(double find_vertical_text_ratio, TO_BLOCK *block, BLOBNBOX_CLIST *osd_blobs)
void delete_data_pointers()
bool IsSeparator() const
Definition: tabvector.h:221
TabVector_LIST * dead_vectors()
Definition: tabfind.h:176
WidthCallback * WidthCB()
Definition: tabfind.h:158
float x() const
Definition: points.h:209
const TBOX & bounding_box() const
Definition: blobbox.h:215
void set_resolution(int resolution)
Definition: tablefind.h:138
void ReTypeBlobs(BLOBNBOX_LIST *im_blobs)
void SetTabStops(TabFind *tabgrid)
Definition: ocrblock.h:30
void compute_bounding_box()
Definition: blobbox.h:225
void set_median_size(int x, int y)
Definition: ocrblock.h:159
BlobTextFlowType flow() const
Definition: colpartition.h:154
BlobRegionType blob_type() const
Definition: colpartition.h:148
void Pen(Color color)
Definition: scrollview.cpp:726
bool TypesMatch(const ColPartition &other) const
Definition: colpartition.h:403
int FindBlocks(PageSegMode pageseg_mode, Pix *scaled_color, int scaled_factor, TO_BLOCK *block, Pix *photo_mask_pix, Pix *thresholds_pix, Pix *grey_pix, DebugPixa *pixa_debug, BLOCK_LIST *blocks, BLOBNBOX_LIST *diacritic_blobs, TO_BLOCK_LIST *to_blocks)
Definition: colfind.cpp:290
static ColPartition * MakeLinePartition(BlobRegionType blob_type, const ICOORD &vertical, int left, int bottom, int right, int top)
void GradeBlobsIntoPartitions(PageSegMode pageseg_mode, const FCOORD &rerotation, TO_BLOCK *block, Pix *nontext_pix, const DENORM *denorm, bool cjk_script, TextlineProjection *projection, BLOBNBOX_LIST *diacritic_blobs, ColPartitionGrid *part_grid, ColPartition_LIST *big_parts)
integer coordinate
Definition: points.h:30
void GridFindMargins(ColPartitionSet **best_columns)
SVEvent * AwaitEvent(SVEventType type)
Definition: scrollview.cpp:449
void set_skew(const FCOORD &skew)
Definition: ocrblock.h:153
void RemoveLineResidue(ColPartition_LIST *big_part_list)
void AddToColumnSetsIfUnique(PartSetVector *column_sets, WidthCallback *cb)