|
tesseract 3.04.01
|
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.