tesseract 3.04.01

textord/tabfind.cpp

Go to the documentation of this file.
00001 
00002 // File:        TabFind.cpp
00003 // Description: Subclass of BBGrid to find vertically aligned blobs.
00004 // Author:      Ray Smith
00005 // Created:     Fri Mar 21 15:03:01 PST 2008
00006 //
00007 // (C) Copyright 2008, Google Inc.
00008 // Licensed under the Apache License, Version 2.0 (the "License");
00009 // you may not use this file except in compliance with the License.
00010 // You may obtain a copy of the License at
00011 // http://www.apache.org/licenses/LICENSE-2.0
00012 // Unless required by applicable law or agreed to in writing, software
00013 // distributed under the License is distributed on an "AS IS" BASIS,
00014 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00015 // See the License for the specific language governing permissions and
00016 // limitations under the License.
00017 //
00019 
00020 #ifdef HAVE_CONFIG_H
00021 #include "config_auto.h"
00022 #endif
00023 
00024 #include "tabfind.h"
00025 #include "alignedblob.h"
00026 #include "blobbox.h"
00027 #include "colpartitiongrid.h"
00028 #include "detlinefit.h"
00029 #include "linefind.h"
00030 #include "ndminx.h"
00031 
00032 namespace tesseract {
00033 
00034 // Multiple of box size to search for initial gaps.
00035 const int kTabRadiusFactor = 5;
00036 // Min and Max multiple of height to search vertically when extrapolating.
00037 const int kMinVerticalSearch = 3;
00038 const int kMaxVerticalSearch = 12;
00039 const int kMaxRaggedSearch = 25;
00040 // Minimum number of lines in a column width to make it interesting.
00041 const int kMinLinesInColumn = 10;
00042 // Minimum width of a column to be interesting.
00043 const int kMinColumnWidth = 200;
00044 // Minimum fraction of total column lines for a column to be interesting.
00045 const double kMinFractionalLinesInColumn = 0.125;
00046 // Fraction of height used as alignment tolerance for aligned tabs.
00047 const double kAlignedFraction = 0.03125;
00048 // Minimum gutter width in absolute inch (multiplied by resolution)
00049 const double kMinGutterWidthAbsolute = 0.02;
00050 // Maximum gutter width (in absolute inch) that we care about
00051 const double kMaxGutterWidthAbsolute = 2.00;
00052 // Multiplier of gridsize for min gutter width of TT_MAYBE_RAGGED blobs.
00053 const int kRaggedGutterMultiple = 5;
00054 // Min aspect ratio of tall objects to be considered a separator line.
00055 // (These will be ignored in searching the gutter for obstructions.)
00056 const double kLineFragmentAspectRatio = 10.0;
00057 // Multiplier of new y positions in running average for skew estimation.
00058 const double kSmoothFactor = 0.25;
00059 // Min coverage for a good baseline between vectors
00060 const double kMinBaselineCoverage = 0.5;
00061 // Minimum overlap fraction when scanning text lines for column widths.
00062 const double kCharVerticalOverlapFraction = 0.375;
00063 // Maximum horizontal gap allowed when scanning for column widths
00064 const double kMaxHorizontalGap = 3.0;
00065 // Maximum upper quartile error allowed on a baseline fit as a fraction
00066 // of height.
00067 const double kMaxBaselineError = 0.4375;
00068 // Min number of points to accept after evaluation.
00069 const int kMinEvaluatedTabs = 3;
00070 // Minimum aspect ratio of a textline to make a good textline blob with a
00071 // single blob.
00072 const int kMaxTextLineBlobRatio = 5;
00073 // Minimum aspect ratio of a textline to make a good textline blob with
00074 // multiple blobs. Target ratio varies according to number of blobs.
00075 const int kMinTextLineBlobRatio = 3;
00076 // Fraction of box area covered by image to make a blob image.
00077 const double kMinImageArea = 0.5;
00078 // Up to 30 degrees is allowed for rotations of diacritic blobs.
00079 // Keep this value slightly larger than kCosSmallAngle in blobbox.cpp
00080 // so that the assert there never fails.
00081 const double kCosMaxSkewAngle = 0.866025;
00082 
00083 BOOL_VAR(textord_tabfind_show_initialtabs, false, "Show tab candidates");
00084 BOOL_VAR(textord_tabfind_show_finaltabs, false, "Show tab vectors");
00085 
00086 TabFind::TabFind(int gridsize, const ICOORD& bleft, const ICOORD& tright,
00087                  TabVector_LIST* vlines, int vertical_x, int vertical_y,
00088                  int resolution)
00089   : AlignedBlob(gridsize, bleft, tright),
00090     resolution_(resolution),
00091     image_origin_(0, tright.y() - 1) {
00092   width_cb_ = NULL;
00093   v_it_.set_to_list(&vectors_);
00094   v_it_.add_list_after(vlines);
00095   SetVerticalSkewAndParellelize(vertical_x, vertical_y);
00096   width_cb_ = NewPermanentTessCallback(this, &TabFind::CommonWidth);
00097 }
00098 
00099 TabFind::~TabFind() {
00100   if (width_cb_ != NULL)
00101     delete width_cb_;
00102 }
00103 
00105 
00106 // Insert a list of blobs into the given grid (not necessarily this).
00107 // If take_ownership is true, then the blobs are removed from the source list.
00108 // See InsertBlob for the other arguments.
00109 // It would seem to make more sense to swap this and grid, but this way
00110 // around allows grid to not be derived from TabFind, eg a ColPartitionGrid,
00111 // while the grid that provides the tab stops(this) has to be derived from
00112 // TabFind.
00113 void TabFind::InsertBlobsToGrid(bool h_spread, bool v_spread,
00114                                 BLOBNBOX_LIST* blobs,
00115                                 BBGrid<BLOBNBOX, BLOBNBOX_CLIST,
00116                                        BLOBNBOX_C_IT>* grid) {
00117   BLOBNBOX_IT blob_it(blobs);
00118   int b_count = 0;
00119   int reject_count = 0;
00120   for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
00121     BLOBNBOX* blob = blob_it.data();
00122 //    if (InsertBlob(true, true, blob, grid)) {
00123     if (InsertBlob(h_spread, v_spread, blob, grid)) {
00124       ++b_count;
00125     } else {
00126       ++reject_count;
00127     }
00128   }
00129   if (textord_debug_tabfind) {
00130     tprintf("Inserted %d blobs into grid, %d rejected.\n",
00131             b_count, reject_count);
00132   }
00133 }
00134 
00135 // Insert a single blob into the given grid (not necessarily this).
00136 // If h_spread, then all cells covered horizontally by the box are
00137 // used, otherwise, just the bottom-left. Similarly for v_spread.
00138 // A side effect is that the left and right rule edges of the blob are
00139 // set according to the tab vectors in this (not grid).
00140 bool TabFind::InsertBlob(bool h_spread, bool v_spread, BLOBNBOX* blob,
00141                          BBGrid<BLOBNBOX, BLOBNBOX_CLIST,
00142                                 BLOBNBOX_C_IT>* grid) {
00143   TBOX box = blob->bounding_box();
00144   blob->set_left_rule(LeftEdgeForBox(box, false, false));
00145   blob->set_right_rule(RightEdgeForBox(box, false, false));
00146   blob->set_left_crossing_rule(LeftEdgeForBox(box, true, false));
00147   blob->set_right_crossing_rule(RightEdgeForBox(box, true, false));
00148   if (blob->joined_to_prev())
00149     return false;
00150   grid->InsertBBox(h_spread, v_spread, blob);
00151   return true;
00152 }
00153 
00154 // Calls SetBlobRuleEdges for all the blobs in the given block.
00155 void TabFind::SetBlockRuleEdges(TO_BLOCK* block) {
00156   SetBlobRuleEdges(&block->blobs);
00157   SetBlobRuleEdges(&block->small_blobs);
00158   SetBlobRuleEdges(&block->noise_blobs);
00159   SetBlobRuleEdges(&block->large_blobs);
00160 }
00161 
00162 // Sets the left and right rule and crossing_rules for the blobs in the given
00163 // list by fiding the next outermost tabvectors for each blob.
00164 void TabFind::SetBlobRuleEdges(BLOBNBOX_LIST* blobs) {
00165   BLOBNBOX_IT blob_it(blobs);
00166   for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
00167     BLOBNBOX* blob = blob_it.data();
00168     TBOX box = blob->bounding_box();
00169     blob->set_left_rule(LeftEdgeForBox(box, false, false));
00170     blob->set_right_rule(RightEdgeForBox(box, false, false));
00171     blob->set_left_crossing_rule(LeftEdgeForBox(box, true, false));
00172     blob->set_right_crossing_rule(RightEdgeForBox(box, true, false));
00173   }
00174 }
00175 
00176 // Returns the gutter width of the given TabVector between the given y limits.
00177 // Also returns x-shift to be added to the vector to clear any intersecting
00178 // blobs. The shift is deducted from the returned gutter.
00179 // If ignore_unmergeables is true, then blobs of UnMergeableType are
00180 // ignored as if they don't exist. (Used for text on image.)
00181 // max_gutter_width is used as the maximum width worth searching for in case
00182 // there is nothing near the TabVector.
00183 int TabFind::GutterWidth(int bottom_y, int top_y, const TabVector& v,
00184                          bool ignore_unmergeables, int max_gutter_width,
00185                          int* required_shift) {
00186   bool right_to_left = v.IsLeftTab();
00187   int bottom_x = v.XAtY(bottom_y);
00188   int top_x = v.XAtY(top_y);
00189   int start_x = right_to_left ? MAX(top_x, bottom_x) : MIN(top_x, bottom_x);
00190   BlobGridSearch sidesearch(this);
00191   sidesearch.StartSideSearch(start_x, bottom_y, top_y);
00192   int min_gap = max_gutter_width;
00193   *required_shift = 0;
00194   BLOBNBOX* blob = NULL;
00195   while ((blob = sidesearch.NextSideSearch(right_to_left)) != NULL) {
00196     const TBOX& box = blob->bounding_box();
00197     if (box.bottom() >= top_y || box.top() <= bottom_y)
00198       continue;  // Doesn't overlap enough.
00199     if (box.height() >= gridsize() * 2 &&
00200         box.height() > box.width() * kLineFragmentAspectRatio) {
00201       // Skip likely separator line residue.
00202       continue;
00203     }
00204     if (ignore_unmergeables && BLOBNBOX::UnMergeableType(blob->region_type()))
00205       continue;  // Skip non-text if required.
00206     int mid_y = (box.bottom() + box.top()) / 2;
00207     // We use the x at the mid-y so that the required_shift guarantees
00208     // to clear all the blobs on the tab-stop. If we use the min/max
00209     // of x at top/bottom of the blob, then exactness would be required,
00210     // which is not a good thing.
00211     int tab_x = v.XAtY(mid_y);
00212     int gap;
00213     if (right_to_left) {
00214       gap = tab_x - box.right();
00215       if (gap < 0 && box.left() - tab_x < *required_shift)
00216         *required_shift = box.left() - tab_x;
00217     } else {
00218       gap = box.left() - tab_x;
00219       if (gap < 0 && box.right() - tab_x > *required_shift)
00220         *required_shift = box.right() - tab_x;
00221     }
00222     if (gap > 0 && gap < min_gap)
00223       min_gap = gap;
00224   }
00225   // Result may be negative, in which case,  this is a really bad tabstop.
00226   return min_gap - abs(*required_shift);
00227 }
00228 
00229 // Find the gutter width and distance to inner neighbour for the given blob.
00230 void TabFind::GutterWidthAndNeighbourGap(int tab_x, int mean_height,
00231                                          int max_gutter, bool left,
00232                                          BLOBNBOX* bbox, int* gutter_width,
00233                                          int* neighbour_gap ) {
00234   const TBOX& box = bbox->bounding_box();
00235   // The gutter and internal sides of the box.
00236   int gutter_x = left ? box.left() : box.right();
00237   int internal_x = left ? box.right() : box.left();
00238   // On ragged edges, the gutter side of the box is away from the tabstop.
00239   int tab_gap = left ? gutter_x - tab_x : tab_x - gutter_x;
00240   *gutter_width = max_gutter;
00241   // If the box is away from the tabstop, we need to increase
00242   // the allowed gutter width.
00243   if (tab_gap > 0)
00244     *gutter_width += tab_gap;
00245   bool debug = WithinTestRegion(2, box.left(), box.bottom());
00246   if (debug)
00247     tprintf("Looking in gutter\n");
00248   // Find the nearest blob on the outside of the column.
00249   BLOBNBOX* gutter_bbox = AdjacentBlob(bbox, left,
00250                                        bbox->flow() == BTFT_TEXT_ON_IMAGE, 0.0,
00251                                        *gutter_width, box.top(), box.bottom());
00252   if (gutter_bbox != NULL) {
00253     TBOX gutter_box = gutter_bbox->bounding_box();
00254     *gutter_width = left ? tab_x - gutter_box.right()
00255                         : gutter_box.left() - tab_x;
00256   }
00257   if (*gutter_width >= max_gutter) {
00258     // If there is no box because a tab was in the way, get the tab coord.
00259     TBOX gutter_box(box);
00260     if (left) {
00261       gutter_box.set_left(tab_x - max_gutter - 1);
00262       gutter_box.set_right(tab_x - max_gutter);
00263       int tab_gutter = RightEdgeForBox(gutter_box, true, false);
00264       if (tab_gutter < tab_x - 1)
00265         *gutter_width = tab_x - tab_gutter;
00266     } else {
00267       gutter_box.set_left(tab_x + max_gutter);
00268       gutter_box.set_right(tab_x + max_gutter + 1);
00269       int tab_gutter = LeftEdgeForBox(gutter_box, true, false);
00270       if (tab_gutter > tab_x + 1)
00271         *gutter_width = tab_gutter - tab_x;
00272     }
00273   }
00274   if (*gutter_width > max_gutter)
00275     *gutter_width = max_gutter;
00276   // Now look for a neighbour on the inside.
00277   if (debug)
00278     tprintf("Looking for neighbour\n");
00279   BLOBNBOX* neighbour = AdjacentBlob(bbox, !left,
00280                                      bbox->flow() == BTFT_TEXT_ON_IMAGE, 0.0,
00281                                      *gutter_width, box.top(), box.bottom());
00282   int neighbour_edge = left ? RightEdgeForBox(box, true, false)
00283                             : LeftEdgeForBox(box, true, false);
00284   if (neighbour != NULL) {
00285     TBOX n_box = neighbour->bounding_box();
00286     if (debug) {
00287       tprintf("Found neighbour:");
00288       n_box.print();
00289     }
00290     if (left && n_box.left() < neighbour_edge)
00291       neighbour_edge = n_box.left();
00292     else if (!left && n_box.right() > neighbour_edge)
00293       neighbour_edge = n_box.right();
00294   }
00295   *neighbour_gap = left ? neighbour_edge - internal_x
00296                         : internal_x - neighbour_edge;
00297 }
00298 
00299 // Return the x-coord that corresponds to the right edge for the given
00300 // box. If there is a rule line to the right that vertically overlaps it,
00301 // then return the x-coord of the rule line, otherwise return the right
00302 // edge of the page. For details see RightTabForBox below.
00303 int TabFind::RightEdgeForBox(const TBOX& box, bool crossing, bool extended) {
00304   TabVector* v = RightTabForBox(box, crossing, extended);
00305   return v == NULL ? tright_.x() : v->XAtY((box.top() + box.bottom()) / 2);
00306 }
00307 // As RightEdgeForBox, but finds the left Edge instead.
00308 int TabFind::LeftEdgeForBox(const TBOX& box, bool crossing, bool extended) {
00309   TabVector* v = LeftTabForBox(box, crossing, extended);
00310   return v == NULL ? bleft_.x() : v->XAtY((box.top() + box.bottom()) / 2);
00311 }
00312 
00313 // This comment documents how this function works.
00314 // For its purpose and arguments, see the comment in tabfind.h.
00315 // TabVectors are stored sorted by perpendicular distance of middle from
00316 // the global mean vertical vector. Since the individual vectors can have
00317 // differing directions, their XAtY for a given y is not necessarily in the
00318 // right order. Therefore the search has to be run with a margin.
00319 // The middle of a vector that passes through (x,y) cannot be higher than
00320 // halfway from y to the top, or lower than halfway from y to the bottom
00321 // of the coordinate range; therefore, the search margin is the range of
00322 // sort keys between these halfway points. Any vector with a sort key greater
00323 // than the upper margin must be to the right of x at y, and likewise any
00324 // vector with a sort key less than the lower margin must pass to the left
00325 // of x at y.
00326 TabVector* TabFind::RightTabForBox(const TBOX& box, bool crossing,
00327                                    bool extended) {
00328   if (v_it_.empty())
00329     return NULL;
00330   int top_y = box.top();
00331   int bottom_y = box.bottom();
00332   int mid_y = (top_y + bottom_y) / 2;
00333   int right = crossing ? (box.left() + box.right()) / 2 : box.right();
00334   int min_key, max_key;
00335   SetupTabSearch(right, mid_y, &min_key, &max_key);
00336   // Position the iterator at the first TabVector with sort_key >= min_key.
00337   while (!v_it_.at_first() && v_it_.data()->sort_key() >= min_key)
00338     v_it_.backward();
00339   while (!v_it_.at_last() && v_it_.data()->sort_key() < min_key)
00340     v_it_.forward();
00341   // Find the leftmost tab vector that overlaps and has XAtY(mid_y) >= right.
00342   TabVector* best_v = NULL;
00343   int best_x = -1;
00344   int key_limit = -1;
00345   do {
00346     TabVector* v = v_it_.data();
00347     int x = v->XAtY(mid_y);
00348     if (x >= right &&
00349         (v->VOverlap(top_y, bottom_y) > 0 ||
00350          (extended && v->ExtendedOverlap(top_y, bottom_y) > 0))) {
00351       if (best_v == NULL || x < best_x) {
00352         best_v = v;
00353         best_x = x;
00354         // We can guarantee that no better vector can be found if the
00355         // sort key exceeds that of the best by max_key - min_key.
00356         key_limit = v->sort_key() + max_key - min_key;
00357       }
00358     }
00359     // Break when the search is done to avoid wrapping the iterator and
00360     // thereby potentially slowing the next search.
00361     if (v_it_.at_last() ||
00362         (best_v != NULL && v->sort_key() > key_limit))
00363       break;  // Prevent restarting list for next call.
00364     v_it_.forward();
00365   } while (!v_it_.at_first());
00366   return best_v;
00367 }
00368 
00369 // As RightTabForBox, but finds the left TabVector instead.
00370 TabVector* TabFind::LeftTabForBox(const TBOX& box, bool crossing,
00371                                   bool extended) {
00372   if (v_it_.empty())
00373     return NULL;
00374   int top_y = box.top();
00375   int bottom_y = box.bottom();
00376   int mid_y = (top_y + bottom_y) / 2;
00377   int left = crossing ? (box.left() + box.right()) / 2 : box.left();
00378   int min_key, max_key;
00379   SetupTabSearch(left, mid_y, &min_key, &max_key);
00380   // Position the iterator at the last TabVector with sort_key <= max_key.
00381   while (!v_it_.at_last() && v_it_.data()->sort_key() <= max_key)
00382     v_it_.forward();
00383   while (!v_it_.at_first() && v_it_.data()->sort_key() > max_key) {
00384     v_it_.backward();
00385   }
00386   // Find the rightmost tab vector that overlaps and has XAtY(mid_y) <= left.
00387   TabVector* best_v = NULL;
00388   int best_x = -1;
00389   int key_limit = -1;
00390   do {
00391     TabVector* v = v_it_.data();
00392     int x = v->XAtY(mid_y);
00393     if (x <= left &&
00394         (v->VOverlap(top_y, bottom_y) > 0 ||
00395          (extended && v->ExtendedOverlap(top_y, bottom_y) > 0))) {
00396       if (best_v == NULL || x > best_x) {
00397         best_v = v;
00398         best_x = x;
00399         // We can guarantee that no better vector can be found if the
00400         // sort key is less than that of the best by max_key - min_key.
00401         key_limit = v->sort_key() - (max_key - min_key);
00402       }
00403     }
00404     // Break when the search is done to avoid wrapping the iterator and
00405     // thereby potentially slowing the next search.
00406     if (v_it_.at_first() ||
00407         (best_v != NULL && v->sort_key() < key_limit))
00408       break;  // Prevent restarting list for next call.
00409     v_it_.backward();
00410   } while (!v_it_.at_last());
00411   return best_v;
00412 }
00413 
00414 // Return true if the given width is close to one of the common
00415 // widths in column_widths_.
00416 bool TabFind::CommonWidth(int width) {
00417   width /= kColumnWidthFactor;
00418   ICOORDELT_IT it(&column_widths_);
00419   for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
00420     ICOORDELT* w = it.data();
00421     if (w->x() - 1 <= width && width <= w->y() + 1)
00422       return true;
00423   }
00424   return false;
00425 }
00426 
00427 // Return true if the sizes are more than a
00428 // factor of 2 different.
00429 bool TabFind::DifferentSizes(int size1, int size2) {
00430   return size1 > size2 * 2 || size2 > size1 * 2;
00431 }
00432 
00433 // Return true if the sizes are more than a
00434 // factor of 5 different.
00435 bool TabFind::VeryDifferentSizes(int size1, int size2) {
00436   return size1 > size2 * 5 || size2 > size1 * 5;
00437 }
00438 
00440 
00441 // Top-level function to find TabVectors in an input page block.
00442 // Returns false if the detected skew angle is impossible.
00443 // Applies the detected skew angle to deskew the tabs, blobs and part_grid.
00444 bool TabFind::FindTabVectors(TabVector_LIST* hlines,
00445                              BLOBNBOX_LIST* image_blobs, TO_BLOCK* block,
00446                              int min_gutter_width,
00447                              double tabfind_aligned_gap_fraction,
00448                              ColPartitionGrid* part_grid,
00449                              FCOORD* deskew, FCOORD* reskew) {
00450   ScrollView* tab_win = FindInitialTabVectors(image_blobs, min_gutter_width,
00451                                               tabfind_aligned_gap_fraction,
00452                                               block);
00453   ComputeColumnWidths(tab_win, part_grid);
00454   TabVector::MergeSimilarTabVectors(vertical_skew_, &vectors_, this);
00455   SortVectors();
00456   CleanupTabs();
00457   if (!Deskew(hlines, image_blobs, block, deskew, reskew))
00458     return false;  // Skew angle is too large.
00459   part_grid->Deskew(*deskew);
00460   ApplyTabConstraints();
00461   #ifndef GRAPHICS_DISABLED
00462   if (textord_tabfind_show_finaltabs) {
00463     tab_win = MakeWindow(640, 50, "FinalTabs");
00464     if (textord_debug_images) {
00465       tab_win->Image(AlignedBlob::textord_debug_pix().string(),
00466                      image_origin_.x(), image_origin_.y());
00467     } else {
00468       DisplayBoxes(tab_win);
00469       DisplayTabs("FinalTabs", tab_win);
00470     }
00471     tab_win = DisplayTabVectors(tab_win);
00472   }
00473   #endif  // GRAPHICS_DISABLED
00474   return true;
00475 }
00476 
00477 // Top-level function to not find TabVectors in an input page block,
00478 // but setup for single column mode.
00479 void TabFind::DontFindTabVectors(BLOBNBOX_LIST* image_blobs, TO_BLOCK* block,
00480                                  FCOORD* deskew, FCOORD* reskew) {
00481   InsertBlobsToGrid(false, false, image_blobs, this);
00482   InsertBlobsToGrid(true, false, &block->blobs, this);
00483   deskew->set_x(1.0f);
00484   deskew->set_y(0.0f);
00485   reskew->set_x(1.0f);
00486   reskew->set_y(0.0f);
00487 }
00488 
00489 // Cleans up the lists of blobs in the block ready for use by TabFind.
00490 // Large blobs that look like text are moved to the main blobs list.
00491 // Main blobs that are superseded by the image blobs are deleted.
00492 void TabFind::TidyBlobs(TO_BLOCK* block) {
00493   BLOBNBOX_IT large_it = &block->large_blobs;
00494   BLOBNBOX_IT blob_it = &block->blobs;
00495   int b_count = 0;
00496   for (large_it.mark_cycle_pt(); !large_it.cycled_list(); large_it.forward()) {
00497     BLOBNBOX* large_blob = large_it.data();
00498     if (large_blob->owner() != NULL) {
00499       blob_it.add_to_end(large_it.extract());
00500       ++b_count;
00501     }
00502   }
00503   if (textord_debug_tabfind) {
00504     tprintf("Moved %d large blobs to normal list\n",
00505             b_count);
00506     #ifndef GRAPHICS_DISABLED
00507     ScrollView* rej_win = MakeWindow(500, 300, "Image blobs");
00508     block->plot_graded_blobs(rej_win);
00509     block->plot_noise_blobs(rej_win);
00510     rej_win->Update();
00511     #endif  // GRAPHICS_DISABLED
00512   }
00513   block->DeleteUnownedNoise();
00514 }
00515 
00516 // Helper function to setup search limits for *TabForBox.
00517 void TabFind::SetupTabSearch(int x, int y, int* min_key, int* max_key) {
00518   int key1 = TabVector::SortKey(vertical_skew_, x, (y + tright_.y()) / 2);
00519   int key2 = TabVector::SortKey(vertical_skew_, x, (y + bleft_.y()) / 2);
00520   *min_key = MIN(key1, key2);
00521   *max_key = MAX(key1, key2);
00522 }
00523 
00524 ScrollView* TabFind::DisplayTabVectors(ScrollView* tab_win) {
00525 #ifndef GRAPHICS_DISABLED
00526   // For every vector, display it.
00527   TabVector_IT it(&vectors_);
00528   for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
00529     TabVector* vector = it.data();
00530     vector->Display(tab_win);
00531   }
00532   tab_win->Update();
00533 #endif
00534   return tab_win;
00535 }
00536 
00537 // PRIVATE CODE.
00538 //
00539 // First part of FindTabVectors, which may be used twice if the text
00540 // is mostly of vertical alignment.
00541 ScrollView* TabFind::FindInitialTabVectors(BLOBNBOX_LIST* image_blobs,
00542                                            int min_gutter_width,
00543                                            double tabfind_aligned_gap_fraction,
00544                                            TO_BLOCK* block) {
00545   if (textord_tabfind_show_initialtabs) {
00546     ScrollView* line_win = MakeWindow(0, 0, "VerticalLines");
00547     line_win = DisplayTabVectors(line_win);
00548   }
00549   // Prepare the grid.
00550   if (image_blobs != NULL)
00551     InsertBlobsToGrid(true, false, image_blobs, this);
00552   InsertBlobsToGrid(true, false, &block->blobs, this);
00553   ScrollView* initial_win = FindTabBoxes(min_gutter_width,
00554                                          tabfind_aligned_gap_fraction);
00555   FindAllTabVectors(min_gutter_width);
00556 
00557   TabVector::MergeSimilarTabVectors(vertical_skew_, &vectors_, this);
00558   SortVectors();
00559   EvaluateTabs();
00560   if (textord_tabfind_show_initialtabs && initial_win != NULL)
00561     initial_win = DisplayTabVectors(initial_win);
00562   MarkVerticalText();
00563   return initial_win;
00564 }
00565 
00566 // Helper displays all the boxes in the given vector on the given window.
00567 static void DisplayBoxVector(const GenericVector<BLOBNBOX*>& boxes,
00568                              ScrollView* win) {
00569   #ifndef GRAPHICS_DISABLED
00570   for (int i = 0; i < boxes.size(); ++i) {
00571     TBOX box = boxes[i]->bounding_box();
00572     int left_x = box.left();
00573     int right_x = box.right();
00574     int top_y = box.top();
00575     int bottom_y = box.bottom();
00576     ScrollView::Color box_color = boxes[i]->BoxColor();
00577     win->Pen(box_color);
00578     win->Rectangle(left_x, bottom_y, right_x, top_y);
00579   }
00580   win->Update();
00581   #endif  // GRAPHICS_DISABLED
00582 }
00583 
00584 // For each box in the grid, decide whether it is a candidate tab-stop,
00585 // and if so add it to the left/right tab boxes.
00586 ScrollView* TabFind::FindTabBoxes(int min_gutter_width,
00587                                   double tabfind_aligned_gap_fraction) {
00588   left_tab_boxes_.clear();
00589   right_tab_boxes_.clear();
00590   // For every bbox in the grid, determine whether it uses a tab on an edge.
00591   GridSearch<BLOBNBOX, BLOBNBOX_CLIST, BLOBNBOX_C_IT> gsearch(this);
00592   gsearch.StartFullSearch();
00593   BLOBNBOX* bbox;
00594   while ((bbox = gsearch.NextFullSearch()) != NULL) {
00595     if (TestBoxForTabs(bbox, min_gutter_width, tabfind_aligned_gap_fraction)) {
00596       // If it is any kind of tab, insert it into the vectors.
00597       if (bbox->left_tab_type() != TT_NONE)
00598         left_tab_boxes_.push_back(bbox);
00599       if (bbox->right_tab_type() != TT_NONE)
00600         right_tab_boxes_.push_back(bbox);
00601     }
00602   }
00603   // Sort left tabs by left and right by right to see the outermost one first
00604   // on a ragged tab.
00605   left_tab_boxes_.sort(SortByBoxLeft<BLOBNBOX>);
00606   right_tab_boxes_.sort(SortRightToLeft<BLOBNBOX>);
00607   ScrollView* tab_win = NULL;
00608   #ifndef GRAPHICS_DISABLED
00609   if (textord_tabfind_show_initialtabs) {
00610     tab_win = MakeWindow(0, 100, "InitialTabs");
00611     tab_win->Pen(ScrollView::BLUE);
00612     tab_win->Brush(ScrollView::NONE);
00613     // Display the left and right tab boxes.
00614     DisplayBoxVector(left_tab_boxes_, tab_win);
00615     DisplayBoxVector(right_tab_boxes_, tab_win);
00616     tab_win = DisplayTabs("Tabs", tab_win);
00617   }
00618   #endif  // GRAPHICS_DISABLED
00619   return tab_win;
00620 }
00621 
00622 bool TabFind::TestBoxForTabs(BLOBNBOX* bbox, int min_gutter_width,
00623                              double tabfind_aligned_gap_fraction) {
00624   GridSearch<BLOBNBOX, BLOBNBOX_CLIST, BLOBNBOX_C_IT> radsearch(this);
00625   TBOX box = bbox->bounding_box();
00626   // If there are separator lines, get the column edges.
00627   int left_column_edge = bbox->left_rule();
00628   int right_column_edge = bbox->right_rule();
00629   // The edges of the bounding box of the blob being processed.
00630   int left_x = box.left();
00631   int right_x = box.right();
00632   int top_y = box.top();
00633   int bottom_y = box.bottom();
00634   int height = box.height();
00635   bool debug = WithinTestRegion(3, left_x, top_y);
00636   if (debug) {
00637     tprintf("Column edges for blob at (%d,%d)->(%d,%d) are [%d, %d]\n",
00638             left_x, top_y, right_x, bottom_y,
00639             left_column_edge, right_column_edge);
00640   }
00641   // Compute a search radius based on a multiple of the height.
00642   int radius = (height * kTabRadiusFactor + gridsize_ - 1) / gridsize_;
00643   radsearch.StartRadSearch((left_x + right_x)/2, (top_y + bottom_y)/2, radius);
00644   // In Vertical Page mode, once we have an estimate of the vertical line
00645   // spacing, the minimum amount of gutter space before a possible tab is
00646   // increased under the assumption that column partition is always larger
00647   // than line spacing.
00648   int min_spacing =
00649       static_cast<int>(height * tabfind_aligned_gap_fraction);
00650   if (min_gutter_width > min_spacing)
00651     min_spacing = min_gutter_width;
00652   int min_ragged_gutter = kRaggedGutterMultiple * gridsize();
00653   if (min_gutter_width > min_ragged_gutter)
00654     min_ragged_gutter = min_gutter_width;
00655   int target_right = left_x - min_spacing;
00656   int target_left = right_x + min_spacing;
00657   // We will be evaluating whether the left edge could be a left tab, and
00658   // whether the right edge could be a right tab.
00659   // A box can be a tab if its bool is_(left/right)_tab remains true, meaning
00660   // that no blobs have been found in the gutter during the radial search.
00661   // A box can also be a tab if there are objects in the gutter only above
00662   // or only below, and there are aligned objects on the opposite side, but
00663   // not too many unaligned objects. The maybe_(left/right)_tab_up counts
00664   // aligned objects above and negatively counts unaligned objects above,
00665   // and is set to -MAX_INT32 if a gutter object is found above.
00666   // The other 3 maybe ints work similarly for the other sides.
00667   // These conditions are very strict, to minimize false positives, and really
00668   // only aligned tabs and outermost ragged tab blobs will qualify, so we
00669   // also have maybe_ragged_left/right with less stringent rules.
00670   // A blob that is maybe_ragged_left/right will be further qualified later,
00671   // using the min_ragged_gutter.
00672   bool is_left_tab = true;
00673   bool is_right_tab = true;
00674   bool maybe_ragged_left = true;
00675   bool maybe_ragged_right = true;
00676   int maybe_left_tab_up = 0;
00677   int maybe_right_tab_up = 0;
00678   int maybe_left_tab_down = 0;
00679   int maybe_right_tab_down = 0;
00680   if (bbox->leader_on_left()) {
00681     is_left_tab = false;
00682     maybe_ragged_left = false;
00683     maybe_left_tab_up = -MAX_INT32;
00684     maybe_left_tab_down = -MAX_INT32;
00685   }
00686   if (bbox->leader_on_right()) {
00687     is_right_tab = false;
00688     maybe_ragged_right = false;
00689     maybe_right_tab_up = -MAX_INT32;
00690     maybe_right_tab_down = -MAX_INT32;
00691   }
00692   int alignment_tolerance = static_cast<int>(resolution_ * kAlignedFraction);
00693   BLOBNBOX* neighbour = NULL;
00694   while ((neighbour = radsearch.NextRadSearch()) != NULL) {
00695     if (neighbour == bbox)
00696       continue;
00697     TBOX nbox = neighbour->bounding_box();
00698     int n_left = nbox.left();
00699     int n_right = nbox.right();
00700     if (debug)
00701       tprintf("Neighbour at (%d,%d)->(%d,%d)\n",
00702               n_left, nbox.bottom(), n_right, nbox.top());
00703     // If the neighbouring blob is the wrong side of a separator line, then it
00704     // "doesn't exist" as far as we are concerned.
00705     if (n_right > right_column_edge || n_left < left_column_edge ||
00706         left_x < neighbour->left_rule() || right_x > neighbour->right_rule())
00707       continue;  // Separator line in the way.
00708     int n_mid_x = (n_left + n_right) / 2;
00709     int n_mid_y = (nbox.top() + nbox.bottom()) / 2;
00710     if (n_mid_x <= left_x && n_right >= target_right) {
00711       if (debug)
00712         tprintf("Not a left tab\n");
00713       is_left_tab = false;
00714       if (n_mid_y < top_y)
00715         maybe_left_tab_down = -MAX_INT32;
00716       if (n_mid_y > bottom_y)
00717         maybe_left_tab_up = -MAX_INT32;
00718     } else if (NearlyEqual(left_x, n_left, alignment_tolerance)) {
00719       if (debug)
00720         tprintf("Maybe a left tab\n");
00721       if (n_mid_y > top_y && maybe_left_tab_up > -MAX_INT32)
00722         ++maybe_left_tab_up;
00723       if (n_mid_y < bottom_y && maybe_left_tab_down > -MAX_INT32)
00724         ++maybe_left_tab_down;
00725     } else if (n_left < left_x && n_right >= left_x) {
00726       // Overlaps but not aligned so negative points on a maybe.
00727       if (debug)
00728         tprintf("Maybe Not a left tab\n");
00729       if (n_mid_y > top_y && maybe_left_tab_up > -MAX_INT32)
00730         --maybe_left_tab_up;
00731       if (n_mid_y < bottom_y && maybe_left_tab_down > -MAX_INT32)
00732         --maybe_left_tab_down;
00733     }
00734     if (n_left < left_x && nbox.y_overlap(box) && n_right >= target_right) {
00735       maybe_ragged_left = false;
00736       if (debug)
00737         tprintf("Not a ragged left\n");
00738     }
00739     if (n_mid_x >= right_x && n_left <= target_left) {
00740       if (debug)
00741         tprintf("Not a right tab\n");
00742       is_right_tab = false;
00743       if (n_mid_y < top_y)
00744         maybe_right_tab_down = -MAX_INT32;
00745       if (n_mid_y > bottom_y)
00746         maybe_right_tab_up = -MAX_INT32;
00747     } else if (NearlyEqual(right_x, n_right, alignment_tolerance)) {
00748       if (debug)
00749         tprintf("Maybe a right tab\n");
00750       if (n_mid_y > top_y && maybe_right_tab_up > -MAX_INT32)
00751         ++maybe_right_tab_up;
00752       if (n_mid_y < bottom_y && maybe_right_tab_down > -MAX_INT32)
00753         ++maybe_right_tab_down;
00754     } else if (n_right > right_x && n_left <= right_x) {
00755       // Overlaps but not aligned so negative points on a maybe.
00756       if (debug)
00757         tprintf("Maybe Not a right tab\n");
00758       if (n_mid_y > top_y && maybe_right_tab_up > -MAX_INT32)
00759         --maybe_right_tab_up;
00760       if (n_mid_y < bottom_y && maybe_right_tab_down > -MAX_INT32)
00761         --maybe_right_tab_down;
00762     }
00763     if (n_right > right_x && nbox.y_overlap(box) && n_left <= target_left) {
00764       maybe_ragged_right = false;
00765       if (debug)
00766         tprintf("Not a ragged right\n");
00767     }
00768     if (maybe_left_tab_down == -MAX_INT32 && maybe_left_tab_up == -MAX_INT32 &&
00769         maybe_right_tab_down == -MAX_INT32 && maybe_right_tab_up == -MAX_INT32)
00770       break;
00771   }
00772   if (is_left_tab || maybe_left_tab_up > 1 || maybe_left_tab_down > 1) {
00773     bbox->set_left_tab_type(TT_MAYBE_ALIGNED);
00774   } else if (maybe_ragged_left && ConfirmRaggedLeft(bbox, min_ragged_gutter)) {
00775     bbox->set_left_tab_type(TT_MAYBE_RAGGED);
00776   } else {
00777     bbox->set_left_tab_type(TT_NONE);
00778   }
00779   if (is_right_tab || maybe_right_tab_up > 1 || maybe_right_tab_down > 1) {
00780     bbox->set_right_tab_type(TT_MAYBE_ALIGNED);
00781   } else if (maybe_ragged_right &&
00782              ConfirmRaggedRight(bbox, min_ragged_gutter)) {
00783     bbox->set_right_tab_type(TT_MAYBE_RAGGED);
00784   } else {
00785     bbox->set_right_tab_type(TT_NONE);
00786   }
00787   if (debug) {
00788     tprintf("Left result = %s, Right result=%s\n",
00789             bbox->left_tab_type() == TT_MAYBE_ALIGNED ? "Aligned" :
00790             (bbox->left_tab_type() == TT_MAYBE_RAGGED ? "Ragged" : "None"),
00791             bbox->right_tab_type() == TT_MAYBE_ALIGNED ? "Aligned" :
00792             (bbox->right_tab_type() == TT_MAYBE_RAGGED ? "Ragged" : "None"));
00793   }
00794   return bbox->left_tab_type() != TT_NONE || bbox->right_tab_type() != TT_NONE;
00795 }
00796 
00797 // Returns true if there is nothing in the rectangle of width min_gutter to
00798 // the left of bbox.
00799 bool TabFind::ConfirmRaggedLeft(BLOBNBOX* bbox, int min_gutter) {
00800   TBOX search_box(bbox->bounding_box());
00801   search_box.set_right(search_box.left());
00802   search_box.set_left(search_box.left() - min_gutter);
00803   return NothingYOverlapsInBox(search_box, bbox->bounding_box());
00804 }
00805 
00806 // Returns true if there is nothing in the rectangle of width min_gutter to
00807 // the right of bbox.
00808 bool TabFind::ConfirmRaggedRight(BLOBNBOX* bbox, int min_gutter) {
00809   TBOX search_box(bbox->bounding_box());
00810   search_box.set_left(search_box.right());
00811   search_box.set_right(search_box.right() + min_gutter);
00812   return NothingYOverlapsInBox(search_box, bbox->bounding_box());
00813 }
00814 
00815 // Returns true if there is nothing in the given search_box that vertically
00816 // overlaps target_box other than target_box itself.
00817 bool TabFind::NothingYOverlapsInBox(const TBOX& search_box,
00818                                     const TBOX& target_box) {
00819   BlobGridSearch rsearch(this);
00820   rsearch.StartRectSearch(search_box);
00821   BLOBNBOX* blob;
00822   while ((blob = rsearch.NextRectSearch()) != NULL) {
00823     const TBOX& box = blob->bounding_box();
00824     if (box.y_overlap(target_box) && !(box == target_box))
00825       return false;
00826   }
00827   return true;
00828 }
00829 
00830 void TabFind::FindAllTabVectors(int min_gutter_width) {
00831   // A list of vectors that will be created in estimating the skew.
00832   TabVector_LIST dummy_vectors;
00833   // An estimate of the vertical direction, revised as more lines are added.
00834   int vertical_x = 0;
00835   int vertical_y = 1;
00836   // Find an estimate of the vertical direction by finding some tab vectors.
00837   // Slowly up the search size until we get some vectors.
00838   for (int search_size = kMinVerticalSearch; search_size < kMaxVerticalSearch;
00839        search_size += kMinVerticalSearch) {
00840     int vector_count = FindTabVectors(search_size, TA_LEFT_ALIGNED,
00841                                       min_gutter_width,
00842                                       &dummy_vectors,
00843                                       &vertical_x, &vertical_y);
00844     vector_count += FindTabVectors(search_size, TA_RIGHT_ALIGNED,
00845                                    min_gutter_width,
00846                                    &dummy_vectors,
00847                                    &vertical_x, &vertical_y);
00848     if (vector_count > 0)
00849       break;
00850   }
00851   // Get rid of the test vectors and reset the types of the tabs.
00852   dummy_vectors.clear();
00853   for (int i = 0; i < left_tab_boxes_.size(); ++i) {
00854     BLOBNBOX* bbox = left_tab_boxes_[i];
00855     if (bbox->left_tab_type() == TT_CONFIRMED)
00856       bbox->set_left_tab_type(TT_MAYBE_ALIGNED);
00857   }
00858   for (int i = 0; i < right_tab_boxes_.size(); ++i) {
00859     BLOBNBOX* bbox = right_tab_boxes_[i];
00860     if (bbox->right_tab_type() == TT_CONFIRMED)
00861       bbox->set_right_tab_type(TT_MAYBE_ALIGNED);
00862   }
00863   if (textord_debug_tabfind) {
00864     tprintf("Beginning real tab search with vertical = %d,%d...\n",
00865             vertical_x, vertical_y);
00866   }
00867   // Now do the real thing ,but keep the vectors in the dummy_vectors list
00868   // until they are all done, so we don't get the tab vectors confused with
00869   // the rule line vectors.
00870   FindTabVectors(kMaxVerticalSearch, TA_LEFT_ALIGNED, min_gutter_width,
00871                  &dummy_vectors, &vertical_x, &vertical_y);
00872   FindTabVectors(kMaxVerticalSearch, TA_RIGHT_ALIGNED, min_gutter_width,
00873                  &dummy_vectors, &vertical_x, &vertical_y);
00874   FindTabVectors(kMaxRaggedSearch, TA_LEFT_RAGGED, min_gutter_width,
00875                  &dummy_vectors, &vertical_x, &vertical_y);
00876   FindTabVectors(kMaxRaggedSearch, TA_RIGHT_RAGGED, min_gutter_width,
00877                  &dummy_vectors, &vertical_x, &vertical_y);
00878   // Now add the vectors to the vectors_ list.
00879   TabVector_IT v_it(&vectors_);
00880   v_it.add_list_after(&dummy_vectors);
00881   // Now use the summed (mean) vertical vector as the direction for everything.
00882   SetVerticalSkewAndParellelize(vertical_x, vertical_y);
00883 }
00884 
00885 // Helper for FindAllTabVectors finds the vectors of a particular type.
00886 int TabFind::FindTabVectors(int search_size_multiple, TabAlignment alignment,
00887                             int min_gutter_width, TabVector_LIST* vectors,
00888                             int* vertical_x, int* vertical_y) {
00889   TabVector_IT vector_it(vectors);
00890   int vector_count = 0;
00891   // Search the right or left tab boxes, looking for tab vectors.
00892   bool right = alignment == TA_RIGHT_ALIGNED || alignment == TA_RIGHT_RAGGED;
00893   const GenericVector<BLOBNBOX*>& boxes = right ? right_tab_boxes_
00894                                                 : left_tab_boxes_;
00895   for (int i = 0; i < boxes.size(); ++i) {
00896     BLOBNBOX* bbox = boxes[i];
00897     if ((!right && bbox->left_tab_type() == TT_MAYBE_ALIGNED) ||
00898         (right && bbox->right_tab_type() == TT_MAYBE_ALIGNED)) {
00899       TabVector* vector = FindTabVector(search_size_multiple, min_gutter_width,
00900                                         alignment,
00901                                         bbox, vertical_x, vertical_y);
00902       if (vector != NULL) {
00903         ++vector_count;
00904         vector_it.add_to_end(vector);
00905       }
00906     }
00907   }
00908   return vector_count;
00909 }
00910 
00911 // Finds a vector corresponding to a tabstop running through the
00912 // given box of the given alignment type.
00913 // search_size_multiple is a multiple of height used to control
00914 // the size of the search.
00915 // vertical_x and y are updated with an estimate of the real
00916 // vertical direction. (skew finding.)
00917 // Returns NULL if no decent tabstop can be found.
00918 TabVector* TabFind::FindTabVector(int search_size_multiple,
00919                                   int min_gutter_width,
00920                                   TabAlignment alignment,
00921                                   BLOBNBOX* bbox,
00922                                   int* vertical_x, int* vertical_y) {
00923   int height = MAX(bbox->bounding_box().height(), gridsize());
00924   AlignedBlobParams align_params(*vertical_x, *vertical_y,
00925                                  height,
00926                                  search_size_multiple, min_gutter_width,
00927                                  resolution_, alignment);
00928   // FindVerticalAlignment is in the parent (AlignedBlob) class.
00929   return FindVerticalAlignment(align_params, bbox, vertical_x, vertical_y);
00930 }
00931 
00932 // Set the vertical_skew_ member from the given vector and refit
00933 // all vectors parallel to the skew vector.
00934 void TabFind::SetVerticalSkewAndParellelize(int vertical_x, int vertical_y) {
00935   // Fit the vertical vector into an ICOORD, which is 16 bit.
00936   vertical_skew_.set_with_shrink(vertical_x, vertical_y);
00937   if (textord_debug_tabfind)
00938     tprintf("Vertical skew vector=(%d,%d)\n",
00939             vertical_skew_.x(), vertical_skew_.y());
00940   v_it_.set_to_list(&vectors_);
00941   for (v_it_.mark_cycle_pt(); !v_it_.cycled_list(); v_it_.forward()) {
00942     TabVector* v = v_it_.data();
00943     v->Fit(vertical_skew_, true);
00944   }
00945   // Now sort the vectors as their direction has potentially changed.
00946   SortVectors();
00947 }
00948 
00949 // Sort all the current vectors using the given vertical direction vector.
00950 void TabFind::SortVectors() {
00951   vectors_.sort(TabVector::SortVectorsByKey);
00952   v_it_.set_to_list(&vectors_);
00953 }
00954 
00955 // Evaluate all the current tab vectors.
00956 void TabFind::EvaluateTabs() {
00957   TabVector_IT rule_it(&vectors_);
00958   for (rule_it.mark_cycle_pt(); !rule_it.cycled_list(); rule_it.forward()) {
00959     TabVector* tab = rule_it.data();
00960     if (!tab->IsSeparator()) {
00961       tab->Evaluate(vertical_skew_, this);
00962       if (tab->BoxCount() < kMinEvaluatedTabs) {
00963         if (textord_debug_tabfind > 2)
00964           tab->Print("Too few boxes");
00965         delete rule_it.extract();
00966         v_it_.set_to_list(&vectors_);
00967       } else if (WithinTestRegion(3, tab->startpt().x(), tab->startpt().y())) {
00968         tab->Print("Evaluated tab");
00969       }
00970     }
00971   }
00972 }
00973 
00974 // Trace textlines from one side to the other of each tab vector, saving
00975 // the most frequent column widths found in a list so that a given width
00976 // can be tested for being a common width with a simple callback function.
00977 void TabFind::ComputeColumnWidths(ScrollView* tab_win,
00978                                   ColPartitionGrid* part_grid) {
00979   #ifndef GRAPHICS_DISABLED
00980   if (tab_win != NULL)
00981     tab_win->Pen(ScrollView::WHITE);
00982   #endif  // GRAPHICS_DISABLED
00983   // Accumulate column sections into a STATS
00984   int col_widths_size = (tright_.x() - bleft_.x()) / kColumnWidthFactor;
00985   STATS col_widths(0, col_widths_size + 1);
00986   ApplyPartitionsToColumnWidths(part_grid, &col_widths);
00987   #ifndef GRAPHICS_DISABLED
00988   if (tab_win != NULL) {
00989     tab_win->Update();
00990   }
00991   #endif  // GRAPHICS_DISABLED
00992   if (textord_debug_tabfind > 1)
00993     col_widths.print();
00994   // Now make a list of column widths.
00995   MakeColumnWidths(col_widths_size, &col_widths);
00996   // Turn the column width into a range.
00997   ApplyPartitionsToColumnWidths(part_grid, NULL);
00998 }
00999 
01000 // Finds column width and:
01001 //   if col_widths is not null (pass1):
01002 //     pair-up tab vectors with existing ColPartitions and accumulate widths.
01003 //   else (pass2):
01004 //     find the largest real partition width for each recorded column width,
01005 //     to be used as the minimum acceptable width.
01006 void TabFind::ApplyPartitionsToColumnWidths(ColPartitionGrid* part_grid,
01007                                             STATS* col_widths) {
01008   // For every ColPartition in the part_grid, add partners to the tabvectors
01009   // and accumulate the column widths.
01010   ColPartitionGridSearch gsearch(part_grid);
01011   gsearch.StartFullSearch();
01012   ColPartition* part;
01013   while ((part = gsearch.NextFullSearch()) != NULL) {
01014     BLOBNBOX_C_IT blob_it(part->boxes());
01015     if (blob_it.empty())
01016       continue;
01017     BLOBNBOX* left_blob = blob_it.data();
01018     blob_it.move_to_last();
01019     BLOBNBOX* right_blob = blob_it.data();
01020     TabVector* left_vector = LeftTabForBox(left_blob->bounding_box(),
01021                                            true, false);
01022     if (left_vector == NULL || left_vector->IsRightTab())
01023       continue;
01024     TabVector* right_vector = RightTabForBox(right_blob->bounding_box(),
01025                                              true, false);
01026     if (right_vector == NULL || right_vector->IsLeftTab())
01027       continue;
01028 
01029     int line_left = left_vector->XAtY(left_blob->bounding_box().bottom());
01030     int line_right = right_vector->XAtY(right_blob->bounding_box().bottom());
01031     // Add to STATS of measurements if the width is significant.
01032     int width = line_right - line_left;
01033     if (col_widths != NULL) {
01034       AddPartnerVector(left_blob, right_blob, left_vector, right_vector);
01035       if (width >= kMinColumnWidth)
01036         col_widths->add(width / kColumnWidthFactor, 1);
01037     } else {
01038       width /= kColumnWidthFactor;
01039       ICOORDELT_IT it(&column_widths_);
01040       for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
01041         ICOORDELT* w = it.data();
01042         if (NearlyEqual<int>(width, w->y(), 1)) {
01043           int true_width = part->bounding_box().width() / kColumnWidthFactor;
01044           if (true_width <= w->y() && true_width > w->x())
01045             w->set_x(true_width);
01046           break;
01047         }
01048       }
01049     }
01050   }
01051 }
01052 
01053 // Helper makes the list of common column widths in column_widths_ from the
01054 // input col_widths. Destroys the content of col_widths by repeatedly
01055 // finding the mode and erasing the peak.
01056 void TabFind::MakeColumnWidths(int col_widths_size, STATS* col_widths) {
01057   ICOORDELT_IT w_it(&column_widths_);
01058   int total_col_count = col_widths->get_total();
01059   while (col_widths->get_total() > 0) {
01060     int width = col_widths->mode();
01061     int col_count = col_widths->pile_count(width);
01062     col_widths->add(width, -col_count);
01063     // Get the entire peak.
01064     for (int left = width - 1; left > 0 &&
01065          col_widths->pile_count(left) > 0;
01066          --left) {
01067       int new_count = col_widths->pile_count(left);
01068       col_count += new_count;
01069       col_widths->add(left, -new_count);
01070     }
01071     for (int right = width + 1; right < col_widths_size &&
01072          col_widths->pile_count(right) > 0;
01073          ++right) {
01074       int new_count = col_widths->pile_count(right);
01075       col_count += new_count;
01076       col_widths->add(right, -new_count);
01077     }
01078     if (col_count > kMinLinesInColumn &&
01079         col_count > kMinFractionalLinesInColumn * total_col_count) {
01080       ICOORDELT* w = new ICOORDELT(0, width);
01081       w_it.add_after_then_move(w);
01082       if (textord_debug_tabfind)
01083         tprintf("Column of width %d has %d = %.2f%% lines\n",
01084               width * kColumnWidthFactor, col_count,
01085               100.0 * col_count / total_col_count);
01086     }
01087   }
01088 }
01089 
01090 // Mark blobs as being in a vertical text line where that is the case.
01091 // Returns true if the majority of the image is vertical text lines.
01092 void TabFind::MarkVerticalText() {
01093   if (textord_debug_tabfind)
01094     tprintf("Checking for vertical lines\n");
01095   BlobGridSearch gsearch(this);
01096   gsearch.StartFullSearch();
01097   BLOBNBOX* blob = NULL;
01098   while ((blob = gsearch.NextFullSearch()) != NULL) {
01099     if (blob->region_type() < BRT_UNKNOWN)
01100       continue;
01101     if (blob->UniquelyVertical()) {
01102       blob->set_region_type(BRT_VERT_TEXT);
01103     }
01104   }
01105 }
01106 
01107 int TabFind::FindMedianGutterWidth(TabVector_LIST *lines) {
01108   TabVector_IT it(lines);
01109   int prev_right = -1;
01110   int max_gap = static_cast<int>(kMaxGutterWidthAbsolute * resolution_);
01111   STATS gaps(0, max_gap);
01112   STATS heights(0, max_gap);
01113   for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
01114     TabVector* v = it.data();
01115     TabVector* partner = v->GetSinglePartner();
01116     if (!v->IsLeftTab() || v->IsSeparator() || !partner) continue;
01117     heights.add(partner->startpt().x() - v->startpt().x(), 1);
01118     if (prev_right > 0 && v->startpt().x() > prev_right) {
01119       gaps.add(v->startpt().x() - prev_right, 1);
01120     }
01121     prev_right = partner->startpt().x();
01122   }
01123   if (textord_debug_tabfind)
01124     tprintf("TabGutter total %d  median_gap %.2f  median_hgt %.2f\n",
01125             gaps.get_total(), gaps.median(), heights.median());
01126   if (gaps.get_total() < kMinLinesInColumn) return 0;
01127   return static_cast<int>(gaps.median());
01128 }
01129 
01130 // Find the next adjacent (looking to the left or right) blob on this text
01131 // line, with the constraint that it must vertically significantly overlap
01132 // the [top_y, bottom_y] range.
01133 // If ignore_images is true, then blobs with aligned_text() < 0 are treated
01134 // as if they do not exist.
01135 BLOBNBOX* TabFind::AdjacentBlob(const BLOBNBOX* bbox,
01136                                 bool look_left, bool ignore_images,
01137                                 double min_overlap_fraction,
01138                                 int gap_limit, int top_y, int bottom_y) {
01139   GridSearch<BLOBNBOX, BLOBNBOX_CLIST, BLOBNBOX_C_IT> sidesearch(this);
01140   const TBOX& box = bbox->bounding_box();
01141   int left = box.left();
01142   int right = box.right();
01143   int mid_x = (left + right) / 2;
01144   sidesearch.StartSideSearch(mid_x, bottom_y, top_y);
01145   int best_gap = 0;
01146   bool debug = WithinTestRegion(3, left, bottom_y);
01147   BLOBNBOX* result = NULL;
01148   BLOBNBOX* neighbour = NULL;
01149   while ((neighbour = sidesearch.NextSideSearch(look_left)) != NULL) {
01150     if (debug) {
01151       tprintf("Adjacent blob: considering box:");
01152       neighbour->bounding_box().print();
01153     }
01154     if (neighbour == bbox ||
01155         (ignore_images && neighbour->region_type() < BRT_UNKNOWN))
01156       continue;
01157     const TBOX& nbox = neighbour->bounding_box();
01158     int n_top_y = nbox.top();
01159     int n_bottom_y = nbox.bottom();
01160     int v_overlap = MIN(n_top_y, top_y) - MAX(n_bottom_y, bottom_y);
01161     int height = top_y - bottom_y;
01162     int n_height = n_top_y - n_bottom_y;
01163     if (v_overlap > min_overlap_fraction * MIN(height, n_height) &&
01164         (min_overlap_fraction == 0.0 || !DifferentSizes(height, n_height))) {
01165       int n_left = nbox.left();
01166       int n_right = nbox.right();
01167       int h_gap = MAX(n_left, left) - MIN(n_right, right);
01168       int n_mid_x = (n_left + n_right) / 2;
01169       if (look_left == (n_mid_x < mid_x) && n_mid_x != mid_x) {
01170         if (h_gap > gap_limit) {
01171           // Hit a big gap before next tab so don't return anything.
01172           if (debug)
01173             tprintf("Giving up due to big gap = %d vs %d\n",
01174                     h_gap, gap_limit);
01175           return result;
01176         }
01177         if (h_gap > 0 && (look_left ? neighbour->right_tab_type()
01178                           : neighbour->left_tab_type()) >= TT_CONFIRMED) {
01179           // Hit a tab facing the wrong way. Stop in case we are crossing
01180           // the column boundary.
01181           if (debug)
01182             tprintf("Collision with like tab of type %d at %d,%d\n",
01183                     look_left ? neighbour->right_tab_type()
01184                                   : neighbour->left_tab_type(),
01185                     n_left, nbox.bottom());
01186           return result;
01187         }
01188         // This is a good fit to the line. Continue with this
01189         // neighbour as the bbox if the best gap.
01190         if (result == NULL || h_gap < best_gap) {
01191           if (debug)
01192             tprintf("Good result\n");
01193           result = neighbour;
01194           best_gap = h_gap;
01195         } else {
01196           // The new one is worse, so we probably already have the best result.
01197           return result;
01198         }
01199       } else if (debug) {
01200         tprintf("Wrong way\n");
01201       }
01202     } else if (debug) {
01203       tprintf("Insufficient overlap\n");
01204     }
01205   }
01206   if (WithinTestRegion(3, left, box.top()))
01207     tprintf("Giving up due to end of search\n");
01208   return result;  // Hit the edge and found nothing.
01209 }
01210 
01211 // Add a bi-directional partner relationship between the left
01212 // and the right. If one (or both) of the vectors is a separator,
01213 // extend a nearby extendable vector or create a new one of the
01214 // correct type, using the given left or right blob as a guide.
01215 void TabFind::AddPartnerVector(BLOBNBOX* left_blob, BLOBNBOX* right_blob,
01216                                TabVector* left, TabVector* right) {
01217   const TBOX& left_box = left_blob->bounding_box();
01218   const TBOX& right_box = right_blob->bounding_box();
01219   if (left->IsSeparator()) {
01220     // Try to find a nearby left edge to extend.
01221     TabVector* v = LeftTabForBox(left_box, true, true);
01222     if (v != NULL && v != left && v->IsLeftTab() &&
01223         v->XAtY(left_box.top()) > left->XAtY(left_box.top())) {
01224       left = v;  // Found a good replacement.
01225       left->ExtendToBox(left_blob);
01226     } else {
01227       // Fake a vector.
01228       left = new TabVector(*left, TA_LEFT_RAGGED, vertical_skew_, left_blob);
01229       vectors_.add_sorted(TabVector::SortVectorsByKey, left);
01230       v_it_.move_to_first();
01231     }
01232   }
01233   if (right->IsSeparator()) {
01234     // Try to find a nearby left edge to extend.
01235     if (WithinTestRegion(3, right_box.right(), right_box.bottom())) {
01236       tprintf("Box edge (%d,%d-%d)",
01237               right_box.right(), right_box.bottom(), right_box.top());
01238       right->Print(" looking for improvement for");
01239     }
01240     TabVector* v = RightTabForBox(right_box, true, true);
01241     if (v != NULL && v != right && v->IsRightTab() &&
01242         v->XAtY(right_box.top()) < right->XAtY(right_box.top())) {
01243       right = v;  // Found a good replacement.
01244       right->ExtendToBox(right_blob);
01245       if (WithinTestRegion(3, right_box.right(), right_box.bottom())) {
01246         right->Print("Extended vector");
01247       }
01248     } else {
01249       // Fake a vector.
01250       right = new TabVector(*right, TA_RIGHT_RAGGED, vertical_skew_,
01251                             right_blob);
01252       vectors_.add_sorted(TabVector::SortVectorsByKey, right);
01253       v_it_.move_to_first();
01254       if (WithinTestRegion(3, right_box.right(), right_box.bottom())) {
01255         right->Print("Created new vector");
01256       }
01257     }
01258   }
01259   left->AddPartner(right);
01260   right->AddPartner(left);
01261 }
01262 
01263 // Remove separators and unused tabs from the main vectors_ list
01264 // to the dead_vectors_ list.
01265 void TabFind::CleanupTabs() {
01266   // TODO(rays) Before getting rid of separators and unused vectors, it
01267   // would be useful to try moving ragged vectors outwards to see if this
01268   // allows useful extension. Could be combined with checking ends of partners.
01269   TabVector_IT it(&vectors_);
01270   TabVector_IT dead_it(&dead_vectors_);
01271   for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
01272     TabVector* v = it.data();
01273     if (v->IsSeparator() || v->Partnerless()) {
01274       dead_it.add_after_then_move(it.extract());
01275       v_it_.set_to_list(&vectors_);
01276     } else {
01277       v->FitAndEvaluateIfNeeded(vertical_skew_, this);
01278     }
01279   }
01280 }
01281 
01282 // Apply the given rotation to the given list of blobs.
01283 void TabFind::RotateBlobList(const FCOORD& rotation, BLOBNBOX_LIST* blobs) {
01284   BLOBNBOX_IT it(blobs);
01285   for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
01286     it.data()->rotate_box(rotation);
01287   }
01288 }
01289 
01290 // Recreate the grid with deskewed BLOBNBOXes.
01291 // Returns false if the detected skew angle is impossible.
01292 bool TabFind::Deskew(TabVector_LIST* hlines, BLOBNBOX_LIST* image_blobs,
01293                      TO_BLOCK* block, FCOORD* deskew, FCOORD* reskew) {
01294   ComputeDeskewVectors(deskew, reskew);
01295   if (deskew->x() < kCosMaxSkewAngle)
01296     return false;
01297   RotateBlobList(*deskew, image_blobs);
01298   RotateBlobList(*deskew, &block->blobs);
01299   RotateBlobList(*deskew, &block->small_blobs);
01300   RotateBlobList(*deskew, &block->noise_blobs);
01301   if (textord_debug_images) {
01302     // Rotate the debug pix and arrange for it to be drawn at the correct
01303     // pixel offset.
01304     Pix* pix_grey = pixRead(AlignedBlob::textord_debug_pix().string());
01305     int width = pixGetWidth(pix_grey);
01306     int height = pixGetHeight(pix_grey);
01307     float angle = atan2(deskew->y(), deskew->x());
01308     // Positive angle is clockwise to pixRotate.
01309     Pix* pix_rot = pixRotate(pix_grey, -angle, L_ROTATE_AREA_MAP,
01310                              L_BRING_IN_WHITE, width, height);
01311     // The image must be translated by the rotation of its center, since it
01312     // has just been rotated about its center.
01313     ICOORD center_offset(width / 2, height / 2);
01314     ICOORD new_center_offset(center_offset);
01315     new_center_offset.rotate(*deskew);
01316     image_origin_ += new_center_offset - center_offset;
01317     // The image grew as it was rotated, so offset the (top/left) origin
01318     // by half the change in size. y is opposite to x because it is drawn
01319     // at ist top/left, not bottom/left.
01320     ICOORD corner_offset((width - pixGetWidth(pix_rot)) / 2,
01321                          (pixGetHeight(pix_rot) - height) / 2);
01322     image_origin_ += corner_offset;
01323     pixWrite(AlignedBlob::textord_debug_pix().string(), pix_rot, IFF_PNG);
01324     pixDestroy(&pix_grey);
01325     pixDestroy(&pix_rot);
01326   }
01327 
01328   // Rotate the horizontal vectors. The vertical vectors don't need
01329   // rotating as they can just be refitted.
01330   TabVector_IT h_it(hlines);
01331   for (h_it.mark_cycle_pt(); !h_it.cycled_list(); h_it.forward()) {
01332     TabVector* h = h_it.data();
01333     h->Rotate(*deskew);
01334   }
01335   TabVector_IT d_it(&dead_vectors_);
01336   for (d_it.mark_cycle_pt(); !d_it.cycled_list(); d_it.forward()) {
01337     TabVector* d = d_it.data();
01338     d->Rotate(*deskew);
01339   }
01340   SetVerticalSkewAndParellelize(0, 1);
01341   // Rebuild the grid to the new size.
01342   TBOX grid_box(bleft_, tright_);
01343   grid_box.rotate_large(*deskew);
01344   Init(gridsize(), grid_box.botleft(), grid_box.topright());
01345   InsertBlobsToGrid(false, false, image_blobs, this);
01346   InsertBlobsToGrid(true, false, &block->blobs, this);
01347   return true;
01348 }
01349 
01350 // Flip the vertical and horizontal lines and rotate the grid ready
01351 // for working on the rotated image.
01352 // This also makes parameter adjustments for FindInitialTabVectors().
01353 void TabFind::ResetForVerticalText(const FCOORD& rotate, const FCOORD& rerotate,
01354                                    TabVector_LIST* horizontal_lines,
01355                                    int* min_gutter_width) {
01356   // Rotate the horizontal and vertical vectors and swap them over.
01357   // Only the separators are kept and rotated; other tabs are used
01358   // to estimate the gutter width then thrown away.
01359   TabVector_LIST ex_verticals;
01360   TabVector_IT ex_v_it(&ex_verticals);
01361   TabVector_LIST vlines;
01362   TabVector_IT v_it(&vlines);
01363   while (!v_it_.empty()) {
01364     TabVector* v = v_it_.extract();
01365     if (v->IsSeparator()) {
01366       v->Rotate(rotate);
01367       ex_v_it.add_after_then_move(v);
01368     } else {
01369       v_it.add_after_then_move(v);
01370     }
01371     v_it_.forward();
01372   }
01373 
01374   // Adjust the min gutter width for better tabbox selection
01375   // in 2nd call to FindInitialTabVectors().
01376   int median_gutter = FindMedianGutterWidth(&vlines);
01377   if (median_gutter > *min_gutter_width)
01378     *min_gutter_width = median_gutter;
01379 
01380   TabVector_IT h_it(horizontal_lines);
01381   for (h_it.mark_cycle_pt(); !h_it.cycled_list(); h_it.forward()) {
01382     TabVector* h = h_it.data();
01383     h->Rotate(rotate);
01384   }
01385   v_it_.add_list_after(horizontal_lines);
01386   v_it_.move_to_first();
01387   h_it.set_to_list(horizontal_lines);
01388   h_it.add_list_after(&ex_verticals);
01389 
01390   // Rebuild the grid to the new size.
01391   TBOX grid_box(bleft(), tright());
01392   grid_box.rotate_large(rotate);
01393   Init(gridsize(), grid_box.botleft(), grid_box.topright());
01394 }
01395 
01396 // Clear the grid and get rid of the tab vectors, but not separators,
01397 // ready to start again.
01398 void TabFind::Reset() {
01399   v_it_.move_to_first();
01400   for (v_it_.mark_cycle_pt(); !v_it_.cycled_list(); v_it_.forward()) {
01401     if (!v_it_.data()->IsSeparator())
01402       delete v_it_.extract();
01403   }
01404   Clear();
01405 }
01406 
01407 // Reflect the separator tab vectors and the grids in the y-axis.
01408 // Can only be called after Reset!
01409 void TabFind::ReflectInYAxis() {
01410   TabVector_LIST temp_list;
01411   TabVector_IT temp_it(&temp_list);
01412   v_it_.move_to_first();
01413   // The TabVector list only contains vertical lines, but they need to be
01414   // reflected and the list needs to be reversed, so they are still in
01415   // sort_key order.
01416   while (!v_it_.empty()) {
01417     TabVector* v = v_it_.extract();
01418     v_it_.forward();
01419     v->ReflectInYAxis();
01420     temp_it.add_before_then_move(v);
01421   }
01422   v_it_.add_list_after(&temp_list);
01423   v_it_.move_to_first();
01424   // Reset this grid with reflected bounding boxes.
01425   TBOX grid_box(bleft(), tright());
01426   int tmp = grid_box.left();
01427   grid_box.set_left(-grid_box.right());
01428   grid_box.set_right(-tmp);
01429   Init(gridsize(), grid_box.botleft(), grid_box.topright());
01430 }
01431 
01432 // Compute the rotation required to deskew, and its inverse rotation.
01433 void TabFind::ComputeDeskewVectors(FCOORD* deskew, FCOORD* reskew) {
01434   double length = vertical_skew_ % vertical_skew_;
01435   length = sqrt(length);
01436   deskew->set_x(static_cast<float>(vertical_skew_.y() / length));
01437   deskew->set_y(static_cast<float>(vertical_skew_.x() / length));
01438   reskew->set_x(deskew->x());
01439   reskew->set_y(-deskew->y());
01440 }
01441 
01442 // Compute and apply constraints to the end positions of TabVectors so
01443 // that where possible partners end at the same y coordinate.
01444 void TabFind::ApplyTabConstraints() {
01445   TabVector_IT it(&vectors_);
01446   for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
01447     TabVector* v = it.data();
01448     v->SetupConstraints();
01449   }
01450   for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
01451     TabVector* v = it.data();
01452     // With the first and last partner, we want a common bottom and top,
01453     // respectively, and for each change of partner, we want a common
01454     // top of first with bottom of next.
01455     v->SetupPartnerConstraints();
01456   }
01457   // TODO(rays) The back-to-back pairs should really be done like the
01458   // front-to-front pairs, but there is no convenient way of producing the
01459   // list of partners like there is with the front-to-front.
01460   for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
01461     TabVector* v = it.data();
01462     if (!v->IsRightTab())
01463       continue;
01464     // For each back-to-back pair of vectors, try for common top and bottom.
01465     TabVector_IT partner_it(it);
01466     for (partner_it.forward(); !partner_it.at_first(); partner_it.forward()) {
01467       TabVector* partner = partner_it.data();
01468       if (!partner->IsLeftTab() || !v->VOverlap(*partner))
01469         continue;
01470       v->SetupPartnerConstraints(partner);
01471     }
01472   }
01473   // Now actually apply the constraints to get common start/end points.
01474   for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
01475     TabVector* v = it.data();
01476     if (!v->IsSeparator())
01477       v->ApplyConstraints();
01478   }
01479   // TODO(rays) Where constraint application fails, it would be good to try
01480   // checking the ends to see if they really should be moved.
01481 }
01482 
01483 }  // namespace tesseract.
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines