tesseract  4.0.0-1-g2a2b
colpartitiongrid.cpp
Go to the documentation of this file.
1 // File: colpartitiongrid.cpp
3 // Description: Class collecting code that acts on a BBGrid of ColPartitions.
4 // Author: Ray Smith
5 // Created: Mon Oct 05 08:42:01 PDT 2009
6 //
7 // (C) Copyright 2009, Google Inc.
8 // Licensed under the Apache License, Version 2.0 (the "License");
9 // you may not use this file except in compliance with the License.
10 // You may obtain a copy of the License at
11 // http://www.apache.org/licenses/LICENSE-2.0
12 // Unless required by applicable law or agreed to in writing, software
13 // distributed under the License is distributed on an "AS IS" BASIS,
14 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 // See the License for the specific language governing permissions and
16 // limitations under the License.
17 //
19 
20 #ifdef HAVE_CONFIG_H
21 #include "config_auto.h"
22 #endif
23 
24 #include "colpartitiongrid.h"
25 #include "colpartitionset.h"
26 #include "imagefind.h"
27 
28 #include <algorithm>
29 
30 namespace tesseract {
31 
32 BOOL_VAR(textord_tabfind_show_color_fit, false, "Show stroke widths");
33 
34 // Max pad factor used to search the neighbourhood of a partition to smooth
35 // partition types.
36 const int kMaxPadFactor = 6;
37 // Max multiple of size (min(height, width)) for the distance of the nearest
38 // neighbour for the change of type to be used.
40 // Maximum number of lines in a credible figure caption.
41 const int kMaxCaptionLines = 7;
42 // Min ratio between biggest and smallest gap to bound a caption.
43 const double kMinCaptionGapRatio = 2.0;
44 // Min ratio between biggest gap and mean line height to bound a caption.
45 const double kMinCaptionGapHeightRatio = 0.5;
46 // Min fraction of ColPartition height to be overlapping for margin purposes.
47 const double kMarginOverlapFraction = 0.25;
48 // Size ratio required to consider an unmerged overlapping partition to be big.
49 const double kBigPartSizeRatio = 1.75;
50 // Fraction of gridsize to allow arbitrary overlap between partitions.
52 // Max vertical distance of neighbouring ColPartition as a multiple of
53 // partition height for it to be a partner.
54 // TODO(rays) fix the problem that causes a larger number to not work well.
55 // The value needs to be larger as sparse text blocks in a page that gets
56 // marked as single column will not find adjacent lines as partners, and
57 // will merge horizontally distant, but aligned lines. See rep.4B3 p5.
58 // The value needs to be small because double-spaced legal docs written
59 // in a single column, but justified courier have widely spaced lines
60 // that need to get merged before they partner-up with the lines above
61 // and below. See legal.3B5 p13/17. Neither of these should depend on
62 // the value of kMaxPartitionSpacing to be successful, and ColPartition
63 // merging needs attention to fix this problem.
64 const double kMaxPartitionSpacing = 1.75;
65 // Margin by which text has to beat image or vice-versa to make a firm
66 // decision in GridSmoothNeighbour.
67 const int kSmoothDecisionMargin = 4;
68 
70  const ICOORD& bleft, const ICOORD& tright)
71  : BBGrid<ColPartition, ColPartition_CLIST, ColPartition_C_IT>(gridsize,
72  bleft, tright) {
73 }
74 
75 // Handles a click event in a display window.
76 void ColPartitionGrid::HandleClick(int x, int y) {
78  ColPartition_CLIST, ColPartition_C_IT>::HandleClick(x, y);
79  // Run a radial search for partitions that overlap.
80  ColPartitionGridSearch radsearch(this);
81  radsearch.SetUniqueMode(true);
82  radsearch.StartRadSearch(x, y, 1);
83  ColPartition* neighbour;
84  FCOORD click(x, y);
85  while ((neighbour = radsearch.NextRadSearch()) != nullptr) {
86  const TBOX& nbox = neighbour->bounding_box();
87  if (nbox.contains(click)) {
88  tprintf("Block box:");
89  neighbour->bounding_box().print();
90  neighbour->Print();
91  }
92  }
93 }
94 
95 // Merges ColPartitions in the grid that look like they belong in the same
96 // textline.
97 // For all partitions in the grid, calls the box_cb permanent callback
98 // to compute the search box, searches the box, and if a candidate is found,
99 // calls the confirm_cb to check any more rules. If the confirm_cb returns
100 // true, then the partitions are merged.
101 // Both callbacks are deleted before returning.
104  TessResultCallback2<bool, const ColPartition*,
105  const ColPartition*>* confirm_cb) {
106  // Iterate the ColPartitions in the grid.
107  ColPartitionGridSearch gsearch(this);
108  gsearch.StartFullSearch();
109  ColPartition* part;
110  while ((part = gsearch.NextFullSearch()) != nullptr) {
111  if (MergePart(box_cb, confirm_cb, part))
112  gsearch.RepositionIterator();
113  }
114  delete box_cb;
115  delete confirm_cb;
116 }
117 
118 // For the given partition, calls the box_cb permanent callback
119 // to compute the search box, searches the box, and if a candidate is found,
120 // calls the confirm_cb to check any more rules. If the confirm_cb returns
121 // true, then the partitions are merged.
122 // Returns true if the partition is consumed by one or more merges.
125  TessResultCallback2<bool, const ColPartition*,
126  const ColPartition*>* confirm_cb,
127  ColPartition* part) {
128  if (part->IsUnMergeableType())
129  return false;
130  bool any_done = false;
131  // Repeatedly merge part while we find a best merge candidate that works.
132  bool merge_done = false;
133  do {
134  merge_done = false;
135  TBOX box = part->bounding_box();
136  bool debug = AlignedBlob::WithinTestRegion(2, box.left(), box.bottom());
137  if (debug) {
138  tprintf("Merge candidate:");
139  box.print();
140  }
141  // Set up a rectangle search bounded by the part.
142  if (!box_cb->Run(part, &box))
143  continue;
144  // Create a list of merge candidates.
145  ColPartition_CLIST merge_candidates;
146  FindMergeCandidates(part, box, debug, &merge_candidates);
147  // Find the best merge candidate based on minimal overlap increase.
148  int overlap_increase;
149  ColPartition* neighbour = BestMergeCandidate(part, &merge_candidates, debug,
150  confirm_cb,
151  &overlap_increase);
152  if (neighbour != nullptr && overlap_increase <= 0) {
153  if (debug) {
154  tprintf("Merging:hoverlap=%d, voverlap=%d, OLI=%d\n",
155  part->HCoreOverlap(*neighbour), part->VCoreOverlap(*neighbour),
156  overlap_increase);
157  }
158  // Looks like a good candidate so merge it.
159  RemoveBBox(neighbour);
160  // We will modify the box of part, so remove it from the grid, merge
161  // it and then re-insert it into the grid.
162  RemoveBBox(part);
163  part->Absorb(neighbour, nullptr);
164  InsertBBox(true, true, part);
165  merge_done = true;
166  any_done = true;
167  } else if (neighbour != nullptr) {
168  if (debug) {
169  tprintf("Overlapped when merged with increase %d: ", overlap_increase);
170  neighbour->bounding_box().print();
171  }
172  } else if (debug) {
173  tprintf("No candidate neighbour returned\n");
174  }
175  } while (merge_done);
176  return any_done;
177 }
178 
179 // Returns true if the given part and merge candidate might believably
180 // be part of a single text line according to the default rules.
181 // In general we only want to merge partitions that look like they
182 // are on the same text line, ie their median limits overlap, but we have
183 // to make exceptions for diacritics and stray punctuation.
184 static bool OKMergeCandidate(const ColPartition* part,
185  const ColPartition* candidate,
186  bool debug) {
187  const TBOX& part_box = part->bounding_box();
188  if (candidate == part)
189  return false; // Ignore itself.
190  if (!part->TypesMatch(*candidate) || candidate->IsUnMergeableType())
191  return false; // Don't mix inappropriate types.
192 
193  const TBOX& c_box = candidate->bounding_box();
194  if (debug) {
195  tprintf("Examining merge candidate:");
196  c_box.print();
197  }
198  // Candidates must be within a reasonable distance.
199  if (candidate->IsVerticalType() || part->IsVerticalType()) {
200  int h_dist = -part->HCoreOverlap(*candidate);
201  if (h_dist >= std::max(part_box.width(), c_box.width()) / 2) {
202  if (debug)
203  tprintf("Too far away: h_dist = %d\n", h_dist);
204  return false;
205  }
206  } else {
207  // Coarse filter by vertical distance between partitions.
208  int v_dist = -part->VCoreOverlap(*candidate);
209  if (v_dist >= std::max(part_box.height(), c_box.height()) / 2) {
210  if (debug)
211  tprintf("Too far away: v_dist = %d\n", v_dist);
212  return false;
213  }
214  // Candidates must either overlap in median y,
215  // or part or candidate must be an acceptable diacritic.
216  if (!part->VSignificantCoreOverlap(*candidate) &&
217  !part->OKDiacriticMerge(*candidate, debug) &&
218  !candidate->OKDiacriticMerge(*part, debug)) {
219  if (debug)
220  tprintf("Candidate fails overlap and diacritic tests!\n");
221  return false;
222  }
223  }
224  return true;
225 }
226 
227 // Helper function to compute the increase in overlap of the parts list of
228 // Colpartitions with the combination of merge1 and merge2, compared to
229 // the overlap with them uncombined.
230 // An overlap is not counted if passes the OKMergeOverlap test with ok_overlap
231 // as the pixel overlap limit. merge1 and merge2 must both be non-nullptr.
232 static int IncreaseInOverlap(const ColPartition* merge1,
233  const ColPartition* merge2,
234  int ok_overlap,
235  ColPartition_CLIST* parts) {
236  ASSERT_HOST(merge1 != nullptr && merge2 != nullptr);
237  int total_area = 0;
238  ColPartition_C_IT it(parts);
239  TBOX merged_box(merge1->bounding_box());
240  merged_box += merge2->bounding_box();
241  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
242  ColPartition* part = it.data();
243  if (part == merge1 || part == merge2)
244  continue;
245  TBOX part_box = part->bounding_box();
246  // Compute the overlap of the merged box with part.
247  int overlap_area = part_box.intersection(merged_box).area();
248  if (overlap_area > 0 && !part->OKMergeOverlap(*merge1, *merge2,
249  ok_overlap, false)) {
250  total_area += overlap_area;
251  // Subtract the overlap of merge1 and merge2 individually.
252  overlap_area = part_box.intersection(merge1->bounding_box()).area();
253  if (overlap_area > 0)
254  total_area -= overlap_area;
255  TBOX intersection_box = part_box.intersection(merge2->bounding_box());
256  overlap_area = intersection_box.area();
257  if (overlap_area > 0) {
258  total_area -= overlap_area;
259  // Add back the 3-way area.
260  intersection_box &= merge1->bounding_box(); // In-place intersection.
261  overlap_area = intersection_box.area();
262  if (overlap_area > 0)
263  total_area += overlap_area;
264  }
265  }
266  }
267  return total_area;
268 }
269 
270 // Helper function to test that each partition in candidates is either a
271 // good diacritic merge with part or an OK merge candidate with all others
272 // in the candidates list.
273 // ASCII Art Scenario:
274 // We sometimes get text such as "join-this" where the - is actually a long
275 // dash culled from a standard set of extra characters that don't match the
276 // font of the text. This makes its strokewidth not match and forms a broken
277 // set of 3 partitions for "join", "-" and "this" and the dash may slightly
278 // overlap BOTH words.
279 // ------- -------
280 // | ==== |
281 // ------- -------
282 // The standard merge rule: "you can merge 2 partitions as long as there is
283 // no increase in overlap elsewhere" fails miserably here. Merge any pair
284 // of partitions and the combined box overlaps more with the third than
285 // before. To allow the merge, we need to consider whether it is safe to
286 // merge everything, without merging separate text lines. For that we need
287 // everything to be an OKMergeCandidate (which is supposed to prevent
288 // separate text lines merging), but this is hard for diacritics to satisfy,
289 // so an alternative to being OKMergeCandidate with everything is to be an
290 // OKDiacriticMerge with part as the base character.
291 static bool TestCompatibleCandidates(const ColPartition& part, bool debug,
292  ColPartition_CLIST* candidates) {
293  ColPartition_C_IT it(candidates);
294  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
295  ColPartition* candidate = it.data();
296  if (!candidate->OKDiacriticMerge(part, false)) {
297  ColPartition_C_IT it2(it);
298  for (it2.mark_cycle_pt(); !it2.cycled_list(); it2.forward()) {
299  ColPartition* candidate2 = it2.data();
300  if (candidate2 != candidate &&
301  !OKMergeCandidate(candidate, candidate2, false)) {
302  if (debug) {
303  tprintf("NC overlap failed:Candidate:");
304  candidate2->bounding_box().print();
305  tprintf("fails to be a good merge with:");
306  candidate->bounding_box().print();
307  }
308  return false;
309  }
310  }
311  }
312  }
313  return true;
314 }
315 
316 // Computes and returns the total overlap of all partitions in the grid.
317 // If overlap_grid is non-null, it is filled with a grid that holds empty
318 // partitions representing the union of all overlapped partitions.
320  int total_overlap = 0;
321  // Iterate the ColPartitions in the grid.
322  ColPartitionGridSearch gsearch(this);
323  gsearch.StartFullSearch();
324  ColPartition* part;
325  while ((part = gsearch.NextFullSearch()) != nullptr) {
326  ColPartition_CLIST neighbors;
327  const TBOX& part_box = part->bounding_box();
328  FindOverlappingPartitions(part_box, part, &neighbors);
329  ColPartition_C_IT n_it(&neighbors);
330  bool any_part_overlap = false;
331  for (n_it.mark_cycle_pt(); !n_it.cycled_list(); n_it.forward()) {
332  const TBOX& n_box = n_it.data()->bounding_box();
333  int overlap = n_box.intersection(part_box).area();
334  if (overlap > 0 && overlap_grid != nullptr) {
335  if (*overlap_grid == nullptr) {
336  *overlap_grid = new ColPartitionGrid(gridsize(), bleft(), tright());
337  }
338  (*overlap_grid)->InsertBBox(true, true, n_it.data()->ShallowCopy());
339  if (!any_part_overlap) {
340  (*overlap_grid)->InsertBBox(true, true, part->ShallowCopy());
341  }
342  }
343  any_part_overlap = true;
344  total_overlap += overlap;
345  }
346  }
347  return total_overlap;
348 }
349 
350 // Finds all the ColPartitions in the grid that overlap with the given
351 // box and returns them SortByBoxLeft(ed) and uniqued in the given list.
352 // Any partition equal to not_this (may be nullptr) is excluded.
354  const ColPartition* not_this,
355  ColPartition_CLIST* parts) {
356  ColPartitionGridSearch rsearch(this);
357  rsearch.StartRectSearch(box);
358  ColPartition* part;
359  while ((part = rsearch.NextRectSearch()) != nullptr) {
360  if (part != not_this)
361  parts->add_sorted(SortByBoxLeft<ColPartition>, true, part);
362  }
363 }
364 
365 // Finds and returns the best candidate ColPartition to merge with part,
366 // selected from the candidates list, based on the minimum increase in
367 // pairwise overlap among all the partitions overlapped by the combined box.
368 // If overlap_increase is not nullptr then it returns the increase in overlap
369 // that would result from the merge.
370 // confirm_cb is a permanent callback that (if non-null) will be used to
371 // confirm the validity of a proposed merge candidate before selecting it.
372 //
373 // ======HOW MERGING WORKS======
374 // The problem:
375 // We want to merge all the parts of a textline together, but avoid merging
376 // separate textlines. Diacritics, i dots, punctuation, and broken characters
377 // are examples of small bits that need merging with the main textline.
378 // Drop-caps and descenders in one line that touch ascenders in the one below
379 // are examples of cases where we don't want to merge.
380 //
381 // The solution:
382 // Merges that increase overlap among other partitions are generally bad.
383 // Those that don't increase overlap (much) and minimize the total area
384 // seem to be good.
385 //
386 // Ascii art example:
387 // The text:
388 // groggy descenders
389 // minimum ascenders
390 // The boxes: The === represents a small box near or overlapping the lower box.
391 // -----------------
392 // | |
393 // -----------------
394 // -===-------------
395 // | |
396 // -----------------
397 // In considering what to do with the small === box, we find the 2 larger
398 // boxes as neighbours and possible merge candidates, but merging with the
399 // upper box increases overlap with the lower box, whereas merging with the
400 // lower box does not increase overlap.
401 // If the small === box didn't overlap either to start with, total area
402 // would be minimized by merging with the nearer (lower) box.
403 //
404 // This is a simple example. In reality, we have to allow some increase
405 // in overlap, or tightly spaced text would end up in bits.
407  const ColPartition* part, ColPartition_CLIST* candidates, bool debug,
409  int* overlap_increase) {
410  if (overlap_increase != nullptr)
411  *overlap_increase = 0;
412  if (candidates->empty())
413  return nullptr;
414  int ok_overlap =
415  static_cast<int>(kTinyEnoughTextlineOverlapFraction * gridsize() + 0.5);
416  // The best neighbour to merge with is the one that causes least
417  // total pairwise overlap among all the neighbours.
418  // If more than one offers the same total overlap, choose the one
419  // with the least total area.
420  const TBOX& part_box = part->bounding_box();
421  ColPartition_C_IT it(candidates);
422  ColPartition* best_candidate = nullptr;
423  // Find the total combined box of all candidates and the original.
424  TBOX full_box(part_box);
425  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
426  ColPartition* candidate = it.data();
427  full_box += candidate->bounding_box();
428  }
429  // Keep valid neighbours in a list.
430  ColPartition_CLIST neighbours;
431  // Now run a rect search of the merged box for overlapping neighbours, as
432  // we need anything that might be overlapped by the merged box.
433  FindOverlappingPartitions(full_box, part, &neighbours);
434  if (debug) {
435  tprintf("Finding best merge candidate from %d, %d neighbours for box:",
436  candidates->length(), neighbours.length());
437  part_box.print();
438  }
439  // If the best increase in overlap is positive, then we also check the
440  // worst non-candidate overlap. This catches the case of multiple good
441  // candidates that overlap each other when merged. If the worst
442  // non-candidate overlap is better than the best overlap, then return
443  // the worst non-candidate overlap instead.
444  ColPartition_CLIST non_candidate_neighbours;
445  non_candidate_neighbours.set_subtract(SortByBoxLeft<ColPartition>, true,
446  &neighbours, candidates);
447  int worst_nc_increase = 0;
448  int best_increase = INT32_MAX;
449  int best_area = 0;
450  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
451  ColPartition* candidate = it.data();
452  if (confirm_cb != nullptr && !confirm_cb->Run(part, candidate)) {
453  if (debug) {
454  tprintf("Candidate not confirmed:");
455  candidate->bounding_box().print();
456  }
457  continue;
458  }
459  int increase = IncreaseInOverlap(part, candidate, ok_overlap, &neighbours);
460  const TBOX& cand_box = candidate->bounding_box();
461  if (best_candidate == nullptr || increase < best_increase) {
462  best_candidate = candidate;
463  best_increase = increase;
464  best_area = cand_box.bounding_union(part_box).area() - cand_box.area();
465  if (debug) {
466  tprintf("New best merge candidate has increase %d, area %d, over box:",
467  increase, best_area);
468  full_box.print();
469  candidate->Print();
470  }
471  } else if (increase == best_increase) {
472  int area = cand_box.bounding_union(part_box).area() - cand_box.area();
473  if (area < best_area) {
474  best_area = area;
475  best_candidate = candidate;
476  }
477  }
478  increase = IncreaseInOverlap(part, candidate, ok_overlap,
479  &non_candidate_neighbours);
480  if (increase > worst_nc_increase)
481  worst_nc_increase = increase;
482  }
483  if (best_increase > 0) {
484  // If the worst non-candidate increase is less than the best increase
485  // including the candidates, then all the candidates can merge together
486  // and the increase in outside overlap would be less, so use that result,
487  // but only if each candidate is either a good diacritic merge with part,
488  // or an ok merge candidate with all the others.
489  // See TestCompatibleCandidates for more explanation and a picture.
490  if (worst_nc_increase < best_increase &&
491  TestCompatibleCandidates(*part, debug, candidates)) {
492  best_increase = worst_nc_increase;
493  }
494  }
495  if (overlap_increase != nullptr)
496  *overlap_increase = best_increase;
497  return best_candidate;
498 }
499 
500 // Helper to remove the given box from the given partition, put it in its
501 // own partition, and add to the partition list.
502 static void RemoveBadBox(BLOBNBOX* box, ColPartition* part,
503  ColPartition_LIST* part_list) {
504  part->RemoveBox(box);
505  ColPartition::MakeBigPartition(box, part_list);
506 }
507 
508 
509 // Split partitions where it reduces overlap between their bounding boxes.
510 // ColPartitions are after all supposed to be a partitioning of the blobs
511 // AND of the space on the page!
512 // Blobs that cause overlaps get removed, put in individual partitions
513 // and added to the big_parts list. They are most likely characters on
514 // 2 textlines that touch, or something big like a dropcap.
516  ColPartition_LIST* big_parts) {
517  int ok_overlap =
518  static_cast<int>(kTinyEnoughTextlineOverlapFraction * gridsize() + 0.5);
519  // Iterate the ColPartitions in the grid.
520  ColPartitionGridSearch gsearch(this);
521  gsearch.StartFullSearch();
522  ColPartition* part;
523  while ((part = gsearch.NextFullSearch()) != nullptr) {
524  // Set up a rectangle search bounded by the part.
525  const TBOX& box = part->bounding_box();
526  ColPartitionGridSearch rsearch(this);
527  rsearch.SetUniqueMode(true);
528  rsearch.StartRectSearch(box);
529  int unresolved_overlaps = 0;
530 
531  ColPartition* neighbour;
532  while ((neighbour = rsearch.NextRectSearch()) != nullptr) {
533  if (neighbour == part)
534  continue;
535  const TBOX& neighbour_box = neighbour->bounding_box();
536  if (neighbour->OKMergeOverlap(*part, *part, ok_overlap, false) &&
537  part->OKMergeOverlap(*neighbour, *neighbour, ok_overlap, false))
538  continue; // The overlap is OK both ways.
539 
540  // If removal of the biggest box from either partition eliminates the
541  // overlap, and it is much bigger than the box left behind, then
542  // it is either a drop-cap, an inter-line join, or some junk that
543  // we don't want anyway, so put it in the big_parts list.
544  if (!part->IsSingleton()) {
545  BLOBNBOX* excluded = part->BiggestBox();
546  TBOX shrunken = part->BoundsWithoutBox(excluded);
547  if (!shrunken.overlap(neighbour_box) &&
548  excluded->bounding_box().height() >
549  kBigPartSizeRatio * shrunken.height()) {
550  // Removing the biggest box fixes the overlap, so do it!
551  gsearch.RemoveBBox();
552  RemoveBadBox(excluded, part, big_parts);
553  InsertBBox(true, true, part);
554  gsearch.RepositionIterator();
555  break;
556  }
557  } else if (box.contains(neighbour_box)) {
558  ++unresolved_overlaps;
559  continue; // No amount of splitting will fix it.
560  }
561  if (!neighbour->IsSingleton()) {
562  BLOBNBOX* excluded = neighbour->BiggestBox();
563  TBOX shrunken = neighbour->BoundsWithoutBox(excluded);
564  if (!shrunken.overlap(box) &&
565  excluded->bounding_box().height() >
566  kBigPartSizeRatio * shrunken.height()) {
567  // Removing the biggest box fixes the overlap, so do it!
568  rsearch.RemoveBBox();
569  RemoveBadBox(excluded, neighbour, big_parts);
570  InsertBBox(true, true, neighbour);
571  gsearch.RepositionIterator();
572  break;
573  }
574  }
575  int part_overlap_count = part->CountOverlappingBoxes(neighbour_box);
576  int neighbour_overlap_count = neighbour->CountOverlappingBoxes(box);
577  ColPartition* right_part = nullptr;
578  if (neighbour_overlap_count <= part_overlap_count ||
579  part->IsSingleton()) {
580  // Try to split the neighbour to reduce overlap.
581  BLOBNBOX* split_blob = neighbour->OverlapSplitBlob(box);
582  if (split_blob != nullptr) {
583  rsearch.RemoveBBox();
584  right_part = neighbour->SplitAtBlob(split_blob);
585  InsertBBox(true, true, neighbour);
586  ASSERT_HOST(right_part != nullptr);
587  }
588  } else {
589  // Try to split part to reduce overlap.
590  BLOBNBOX* split_blob = part->OverlapSplitBlob(neighbour_box);
591  if (split_blob != nullptr) {
592  gsearch.RemoveBBox();
593  right_part = part->SplitAtBlob(split_blob);
594  InsertBBox(true, true, part);
595  ASSERT_HOST(right_part != nullptr);
596  }
597  }
598  if (right_part != nullptr) {
599  InsertBBox(true, true, right_part);
600  gsearch.RepositionIterator();
601  rsearch.RepositionIterator();
602  break;
603  }
604  }
605  if (unresolved_overlaps > 2 && part->IsSingleton()) {
606  // This part is no good so just add to big_parts.
607  RemoveBBox(part);
608  ColPartition_IT big_it(big_parts);
609  part->set_block_owned(true);
610  big_it.add_to_end(part);
611  gsearch.RepositionIterator();
612  }
613  }
614 }
615 
616 // Filters partitions of source_type by looking at local neighbours.
617 // Where a majority of neighbours have a text type, the partitions are
618 // changed to text, where the neighbours have image type, they are changed
619 // to image, and partitions that have no definite neighbourhood type are
620 // left unchanged.
621 // im_box and rerotation are used to map blob coordinates onto the
622 // nontext_map, which is used to prevent the spread of text neighbourhoods
623 // into images.
624 // Returns true if anything was changed.
626  Pix* nontext_map,
627  const TBOX& im_box,
628  const FCOORD& rotation) {
629  // Iterate the ColPartitions in the grid.
630  ColPartitionGridSearch gsearch(this);
631  gsearch.StartFullSearch();
632  ColPartition* part;
633  bool any_changed = false;
634  while ((part = gsearch.NextFullSearch()) != nullptr) {
635  if (part->flow() != source_type || BLOBNBOX::IsLineType(part->blob_type()))
636  continue;
637  const TBOX& box = part->bounding_box();
638  bool debug = AlignedBlob::WithinTestRegion(2, box.left(), box.bottom());
639  if (SmoothRegionType(nontext_map, im_box, rotation, debug, part))
640  any_changed = true;
641  }
642  return any_changed;
643 }
644 
645 // Reflects the grid and its colpartitions in the y-axis, assuming that
646 // all blob boxes have already been done.
648  ColPartition_LIST parts;
649  ColPartition_IT part_it(&parts);
650  // Iterate the ColPartitions in the grid to extract them.
651  ColPartitionGridSearch gsearch(this);
652  gsearch.StartFullSearch();
653  ColPartition* part;
654  while ((part = gsearch.NextFullSearch()) != nullptr) {
655  part_it.add_after_then_move(part);
656  }
657  ICOORD bot_left(-tright().x(), bleft().y());
658  ICOORD top_right(-bleft().x(), tright().y());
659  // Reinitializing the grid with reflected coords also clears all the
660  // pointers, so parts will now own the ColPartitions. (Briefly).
661  Init(gridsize(), bot_left, top_right);
662  for (part_it.move_to_first(); !part_it.empty(); part_it.forward()) {
663  part = part_it.extract();
664  part->ReflectInYAxis();
665  InsertBBox(true, true, part);
666  }
667 }
668 
669 // Transforms the grid of partitions to the output blocks, putting each
670 // partition into a separate block. We don't really care about the order,
671 // as we just want to get as much text as possible without trying to organize
672 // it into proper blocks or columns.
673 // TODO(rays) some kind of sort function would be useful and probably better
674 // than the default here, which is to sort by order of the grid search.
676  TO_BLOCK_LIST* to_blocks) {
677  TO_BLOCK_IT to_block_it(to_blocks);
678  BLOCK_IT block_it(blocks);
679  // All partitions will be put on this list and deleted on return.
680  ColPartition_LIST parts;
681  ColPartition_IT part_it(&parts);
682  // Iterate the ColPartitions in the grid to extract them.
683  ColPartitionGridSearch gsearch(this);
684  gsearch.StartFullSearch();
685  ColPartition* part;
686  while ((part = gsearch.NextFullSearch()) != nullptr) {
687  part_it.add_after_then_move(part);
688  // The partition has to be at least vaguely like text.
689  BlobRegionType blob_type = part->blob_type();
690  if (BLOBNBOX::IsTextType(blob_type) ||
691  (blob_type == BRT_UNKNOWN && part->boxes_count() > 1)) {
692  PolyBlockType type = blob_type == BRT_VERT_TEXT ? PT_VERTICAL_TEXT
693  : PT_FLOWING_TEXT;
694  // Get metrics from the row that will be used for the block.
695  TBOX box = part->bounding_box();
696  int median_width = part->median_width();
697  int median_height = part->median_height();
698  // Turn the partition into a TO_ROW.
699  TO_ROW* row = part->MakeToRow();
700  if (row == nullptr) {
701  // This partition is dead.
702  part->DeleteBoxes();
703  continue;
704  }
705  BLOCK* block = new BLOCK("", true, 0, 0, box.left(), box.bottom(),
706  box.right(), box.top());
707  block->pdblk.set_poly_block(new POLY_BLOCK(box, type));
708  TO_BLOCK* to_block = new TO_BLOCK(block);
709  TO_ROW_IT row_it(to_block->get_rows());
710  row_it.add_after_then_move(row);
711  // We haven't differentially rotated vertical and horizontal text at
712  // this point, so use width or height as appropriate.
713  if (blob_type == BRT_VERT_TEXT) {
714  to_block->line_size = static_cast<float>(median_width);
715  to_block->line_spacing = static_cast<float>(box.width());
716  to_block->max_blob_size = static_cast<float>(box.width() + 1);
717  } else {
718  to_block->line_size = static_cast<float>(median_height);
719  to_block->line_spacing = static_cast<float>(box.height());
720  to_block->max_blob_size = static_cast<float>(box.height() + 1);
721  }
722  if (to_block->line_size == 0) to_block->line_size = 1;
723  block_it.add_to_end(block);
724  to_block_it.add_to_end(to_block);
725  } else {
726  // This partition is dead.
727  part->DeleteBoxes();
728  }
729  }
730  Clear();
731  // Now it is safe to delete the ColPartitions as parts goes out of scope.
732 }
733 
734 // Rotates the grid and its colpartitions by the given angle, assuming that
735 // all blob boxes have already been done.
736 void ColPartitionGrid::Deskew(const FCOORD& deskew) {
737  ColPartition_LIST parts;
738  ColPartition_IT part_it(&parts);
739  // Iterate the ColPartitions in the grid to extract them.
740  ColPartitionGridSearch gsearch(this);
741  gsearch.StartFullSearch();
742  ColPartition* part;
743  while ((part = gsearch.NextFullSearch()) != nullptr) {
744  part_it.add_after_then_move(part);
745  }
746  // Rebuild the grid to the new size.
747  TBOX grid_box(bleft_, tright_);
748  grid_box.rotate_large(deskew);
749  Init(gridsize(), grid_box.botleft(), grid_box.topright());
750  // Reinitializing the grid with rotated coords also clears all the
751  // pointers, so parts will now own the ColPartitions. (Briefly).
752  for (part_it.move_to_first(); !part_it.empty(); part_it.forward()) {
753  part = part_it.extract();
754  part->ComputeLimits();
755  InsertBBox(true, true, part);
756  }
757 }
758 
759 // Sets the left and right tabs of the partitions in the grid.
761  // Iterate the ColPartitions in the grid.
762  ColPartitionGridSearch gsearch(this);
763  gsearch.StartFullSearch();
764  ColPartition* part;
765  while ((part = gsearch.NextFullSearch()) != nullptr) {
766  const TBOX& part_box = part->bounding_box();
767  TabVector* left_line = tabgrid->LeftTabForBox(part_box, true, false);
768  // If the overlapping line is not a left tab, try for non-overlapping.
769  if (left_line != nullptr && !left_line->IsLeftTab())
770  left_line = tabgrid->LeftTabForBox(part_box, false, false);
771  if (left_line != nullptr && left_line->IsLeftTab())
772  part->SetLeftTab(left_line);
773  TabVector* right_line = tabgrid->RightTabForBox(part_box, true, false);
774  if (right_line != nullptr && !right_line->IsRightTab())
775  right_line = tabgrid->RightTabForBox(part_box, false, false);
776  if (right_line != nullptr && right_line->IsRightTab())
777  part->SetRightTab(right_line);
778  part->SetColumnGoodness(tabgrid->WidthCB());
779  }
780 }
781 
782 // Makes the ColPartSets and puts them in the PartSetVector ready
783 // for finding column bounds. Returns false if no partitions were found.
785  ColPartition_LIST* part_lists = new ColPartition_LIST[gridheight()];
786  part_sets->reserve(gridheight());
787  // Iterate the ColPartitions in the grid to get parts onto lists for the
788  // y bottom of each.
789  ColPartitionGridSearch gsearch(this);
790  gsearch.StartFullSearch();
791  ColPartition* part;
792  bool any_parts_found = false;
793  while ((part = gsearch.NextFullSearch()) != nullptr) {
794  BlobRegionType blob_type = part->blob_type();
795  if (blob_type != BRT_NOISE &&
796  (blob_type != BRT_UNKNOWN || !part->boxes()->singleton())) {
797  int grid_x, grid_y;
798  const TBOX& part_box = part->bounding_box();
799  GridCoords(part_box.left(), part_box.bottom(), &grid_x, &grid_y);
800  ColPartition_IT part_it(&part_lists[grid_y]);
801  part_it.add_to_end(part);
802  any_parts_found = true;
803  }
804  }
805  if (any_parts_found) {
806  for (int grid_y = 0; grid_y < gridheight(); ++grid_y) {
807  ColPartitionSet* line_set = nullptr;
808  if (!part_lists[grid_y].empty()) {
809  line_set = new ColPartitionSet(&part_lists[grid_y]);
810  }
811  part_sets->push_back(line_set);
812  }
813  }
814  delete [] part_lists;
815  return any_parts_found;
816 }
817 
818 // Makes a single ColPartitionSet consisting of a single ColPartition that
819 // represents the total horizontal extent of the significant content on the
820 // page. Used for the single column setting in place of automatic detection.
821 // Returns nullptr if the page is empty of significant content.
823  ColPartition* single_column_part = nullptr;
824  // Iterate the ColPartitions in the grid to get parts onto lists for the
825  // y bottom of each.
826  ColPartitionGridSearch gsearch(this);
827  gsearch.StartFullSearch();
828  ColPartition* part;
829  while ((part = gsearch.NextFullSearch()) != nullptr) {
830  BlobRegionType blob_type = part->blob_type();
831  if (blob_type != BRT_NOISE &&
832  (blob_type != BRT_UNKNOWN || !part->boxes()->singleton())) {
833  // Consider for single column.
834  BlobTextFlowType flow = part->flow();
835  if ((blob_type == BRT_TEXT &&
836  (flow == BTFT_STRONG_CHAIN || flow == BTFT_CHAIN ||
837  flow == BTFT_LEADER || flow == BTFT_TEXT_ON_IMAGE)) ||
838  blob_type == BRT_RECTIMAGE || blob_type == BRT_POLYIMAGE) {
839  if (single_column_part == nullptr) {
840  single_column_part = part->ShallowCopy();
841  single_column_part->set_blob_type(BRT_TEXT);
842  // Copy the tabs from itself to properly setup the margins.
843  single_column_part->CopyLeftTab(*single_column_part, false);
844  single_column_part->CopyRightTab(*single_column_part, false);
845  } else {
846  if (part->left_key() < single_column_part->left_key())
847  single_column_part->CopyLeftTab(*part, false);
848  if (part->right_key() > single_column_part->right_key())
849  single_column_part->CopyRightTab(*part, false);
850  }
851  }
852  }
853  }
854  if (single_column_part != nullptr) {
855  // Make a ColPartitionSet out of the single_column_part as a candidate
856  // for the single column case.
857  single_column_part->SetColumnGoodness(cb);
858  return new ColPartitionSet(single_column_part);
859  }
860  return nullptr;
861 }
862 
863 // Mark the BLOBNBOXes in each partition as being owned by that partition.
865  // Iterate the ColPartitions in the grid.
866  ColPartitionGridSearch gsearch(this);
867  gsearch.StartFullSearch();
868  ColPartition* part;
869  while ((part = gsearch.NextFullSearch()) != nullptr) {
870  part->ClaimBoxes();
871  }
872 }
873 
874 // Retypes all the blobs referenced by the partitions in the grid.
875 // Image blobs are found and returned in the im_blobs list, as they are not
876 // owned by the block.
877 void ColPartitionGrid::ReTypeBlobs(BLOBNBOX_LIST* im_blobs) {
878  BLOBNBOX_IT im_blob_it(im_blobs);
879  ColPartition_LIST dead_parts;
880  ColPartition_IT dead_part_it(&dead_parts);
881  // Iterate the ColPartitions in the grid.
882  ColPartitionGridSearch gsearch(this);
883  gsearch.StartFullSearch();
884  ColPartition* part;
885  while ((part = gsearch.NextFullSearch()) != nullptr) {
886  BlobRegionType blob_type = part->blob_type();
887  BlobTextFlowType flow = part->flow();
888  bool any_blobs_moved = false;
889  if (blob_type == BRT_POLYIMAGE || blob_type == BRT_RECTIMAGE) {
890  BLOBNBOX_C_IT blob_it(part->boxes());
891  for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
892  BLOBNBOX* blob = blob_it.data();
893  im_blob_it.add_after_then_move(blob);
894  }
895  } else if (blob_type != BRT_NOISE) {
896  // Make sure the blobs are marked with the correct type and flow.
897  BLOBNBOX_C_IT blob_it(part->boxes());
898  for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
899  BLOBNBOX* blob = blob_it.data();
900  if (blob->region_type() == BRT_NOISE) {
901  // TODO(rays) Deprecated. Change this section to an assert to verify
902  // and then delete.
903  ASSERT_HOST(blob->cblob()->area() != 0);
904  blob->set_owner(nullptr);
905  blob_it.extract();
906  any_blobs_moved = true;
907  } else {
908  blob->set_region_type(blob_type);
909  if (blob->flow() != BTFT_LEADER)
910  blob->set_flow(flow);
911  }
912  }
913  }
914  if (blob_type == BRT_NOISE || part->boxes()->empty()) {
915  BLOBNBOX_C_IT blob_it(part->boxes());
916  part->DisownBoxes();
917  dead_part_it.add_to_end(part);
918  gsearch.RemoveBBox();
919  for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
920  BLOBNBOX* blob = blob_it.data();
921  if (blob->cblob()->area() == 0) {
922  // Any blob with zero area is a fake image blob and should be deleted.
923  delete blob->cblob();
924  delete blob;
925  }
926  }
927  } else if (any_blobs_moved) {
928  gsearch.RemoveBBox();
929  part->ComputeLimits();
930  InsertBBox(true, true, part);
931  gsearch.RepositionIterator();
932  }
933  }
934 }
935 
936 // The boxes within the partitions have changed (by deskew) so recompute
937 // the bounds of all the partitions and reinsert them into the grid.
939  const ICOORD& bleft,
940  const ICOORD& tright,
941  const ICOORD& vertical) {
942  ColPartition_LIST saved_parts;
943  ColPartition_IT part_it(&saved_parts);
944  // Iterate the ColPartitions in the grid to get parts onto a list.
945  ColPartitionGridSearch gsearch(this);
946  gsearch.StartFullSearch();
947  ColPartition* part;
948  while ((part = gsearch.NextFullSearch()) != nullptr) {
949  part_it.add_to_end(part);
950  }
951  // Reinitialize grid to the new size.
953  // Recompute the bounds of the parts and put them back in the new grid.
954  for (part_it.move_to_first(); !part_it.empty(); part_it.forward()) {
955  part = part_it.extract();
956  part->set_vertical(vertical);
957  part->ComputeLimits();
958  InsertBBox(true, true, part);
959  }
960 }
961 
962 // Improves the margins of the ColPartitions in the grid by calling
963 // FindPartitionMargins on each.
964 // best_columns, which may be nullptr, is an array of pointers indicating the
965 // column set at each y-coordinate in the grid.
966 // best_columns is usually the best_columns_ member of ColumnFinder.
968  // Iterate the ColPartitions in the grid.
969  ColPartitionGridSearch gsearch(this);
970  gsearch.StartFullSearch();
971  ColPartition* part;
972  while ((part = gsearch.NextFullSearch()) != nullptr) {
973  // Set up a rectangle search x-bounded by the column and y by the part.
974  ColPartitionSet* columns = best_columns != nullptr
975  ? best_columns[gsearch.GridY()]
976  : nullptr;
977  FindPartitionMargins(columns, part);
978  const TBOX& box = part->bounding_box();
979  if (AlignedBlob::WithinTestRegion(2, box.left(), box.bottom())) {
980  tprintf("Computed margins for part:");
981  part->Print();
982  }
983  }
984 }
985 
986 // Improves the margins of the ColPartitions in the list by calling
987 // FindPartitionMargins on each.
988 // best_columns, which may be nullptr, is an array of pointers indicating the
989 // column set at each y-coordinate in the grid.
990 // best_columns is usually the best_columns_ member of ColumnFinder.
992  ColPartition_LIST* parts) {
993  ColPartition_IT part_it(parts);
994  for (part_it.mark_cycle_pt(); !part_it.cycled_list(); part_it.forward()) {
995  ColPartition* part = part_it.data();
996  ColPartitionSet* columns = nullptr;
997  if (best_columns != nullptr) {
998  const TBOX& part_box = part->bounding_box();
999  // Get the columns from the y grid coord.
1000  int grid_x, grid_y;
1001  GridCoords(part_box.left(), part_box.bottom(), &grid_x, &grid_y);
1002  columns = best_columns[grid_y];
1003  }
1004  FindPartitionMargins(columns, part);
1005  }
1006 }
1007 
1008 // Deletes all the partitions in the grid after disowning all the blobs.
1010  ColPartition_LIST dead_parts;
1011  ColPartition_IT dead_it(&dead_parts);
1012  ColPartitionGridSearch gsearch(this);
1013  gsearch.StartFullSearch();
1014  ColPartition* part;
1015  while ((part = gsearch.NextFullSearch()) != nullptr) {
1016  part->DisownBoxes();
1017  dead_it.add_to_end(part); // Parts will be deleted on return.
1018  }
1019  Clear();
1020 }
1021 
1022 // Deletes all the partitions in the grid that are of type BRT_UNKNOWN and
1023 // all the blobs in them.
1025  ColPartitionGridSearch gsearch(this);
1026  gsearch.StartFullSearch();
1027  ColPartition* part;
1028  while ((part = gsearch.NextFullSearch()) != nullptr) {
1029  if (part->blob_type() == BRT_UNKNOWN) {
1030  gsearch.RemoveBBox();
1031  // Once marked, the blobs will be swept up by DeleteUnownedNoise.
1032  part->set_flow(BTFT_NONTEXT);
1033  part->set_blob_type(BRT_NOISE);
1034  part->SetBlobTypes();
1035  part->DisownBoxes();
1036  delete part;
1037  }
1038  }
1039  block->DeleteUnownedNoise();
1040 }
1041 
1042 // Deletes all the partitions in the grid that are NOT of flow type BTFT_LEADER.
1044  ColPartitionGridSearch gsearch(this);
1045  gsearch.StartFullSearch();
1046  ColPartition* part;
1047  while ((part = gsearch.NextFullSearch()) != nullptr) {
1048  if (part->flow() != BTFT_LEADER) {
1049  gsearch.RemoveBBox();
1050  if (part->ReleaseNonLeaderBoxes()) {
1051  InsertBBox(true, true, part);
1052  gsearch.RepositionIterator();
1053  } else {
1054  delete part;
1055  }
1056  }
1057  }
1058 }
1059 
1060 // Finds and marks text partitions that represent figure captions.
1062  // For each image region find its best candidate text caption region,
1063  // if any and mark it as such.
1064  ColPartitionGridSearch gsearch(this);
1065  gsearch.StartFullSearch();
1066  ColPartition* part;
1067  while ((part = gsearch.NextFullSearch()) != nullptr) {
1068  if (part->IsImageType()) {
1069  const TBOX& part_box = part->bounding_box();
1070  bool debug = AlignedBlob::WithinTestRegion(2, part_box.left(),
1071  part_box.bottom());
1072  ColPartition* best_caption = nullptr;
1073  int best_dist = 0; // Distance to best_caption.
1074  int best_upper = 0; // Direction of best_caption.
1075  // Handle both lower and upper directions.
1076  for (int upper = 0; upper < 2; ++upper) {
1077  ColPartition_C_IT partner_it(upper ? part->upper_partners()
1078  : part->lower_partners());
1079  // If there are no image partners, then this direction is ok.
1080  for (partner_it.mark_cycle_pt(); !partner_it.cycled_list();
1081  partner_it.forward()) {
1082  ColPartition* partner = partner_it.data();
1083  if (partner->IsImageType()) {
1084  break;
1085  }
1086  }
1087  if (!partner_it.cycled_list()) continue;
1088  // Find the nearest totally overlapping text partner.
1089  for (partner_it.mark_cycle_pt(); !partner_it.cycled_list();
1090  partner_it.forward()) {
1091  ColPartition* partner = partner_it.data();
1092  if (!partner->IsTextType() || partner->type() == PT_TABLE) continue;
1093  const TBOX& partner_box = partner->bounding_box();
1094  if (debug) {
1095  tprintf("Finding figure captions for image part:");
1096  part_box.print();
1097  tprintf("Considering partner:");
1098  partner_box.print();
1099  }
1100  if (partner_box.left() >= part_box.left() &&
1101  partner_box.right() <= part_box.right()) {
1102  int dist = partner_box.y_gap(part_box);
1103  if (best_caption == nullptr || dist < best_dist) {
1104  best_dist = dist;
1105  best_caption = partner;
1106  best_upper = upper;
1107  }
1108  }
1109  }
1110  }
1111  if (best_caption != nullptr) {
1112  if (debug) {
1113  tprintf("Best caption candidate:");
1114  best_caption->bounding_box().print();
1115  }
1116  // We have a candidate caption. Qualify it as being separable from
1117  // any body text. We are looking for either a small number of lines
1118  // or a big gap that indicates a separation from the body text.
1119  int line_count = 0;
1120  int biggest_gap = 0;
1121  int smallest_gap = INT16_MAX;
1122  int total_height = 0;
1123  int mean_height = 0;
1124  ColPartition* end_partner = nullptr;
1125  ColPartition* next_partner = nullptr;
1126  for (ColPartition* partner = best_caption; partner != nullptr &&
1127  line_count <= kMaxCaptionLines;
1128  partner = next_partner) {
1129  if (!partner->IsTextType()) {
1130  end_partner = partner;
1131  break;
1132  }
1133  ++line_count;
1134  total_height += partner->bounding_box().height();
1135  next_partner = partner->SingletonPartner(best_upper);
1136  if (next_partner != nullptr) {
1137  int gap = partner->bounding_box().y_gap(
1138  next_partner->bounding_box());
1139  if (gap > biggest_gap) {
1140  biggest_gap = gap;
1141  end_partner = next_partner;
1142  mean_height = total_height / line_count;
1143  } else if (gap < smallest_gap) {
1144  smallest_gap = gap;
1145  }
1146  // If the gap looks big compared to the text size and the smallest
1147  // gap seen so far, then we can stop.
1148  if (biggest_gap > mean_height * kMinCaptionGapHeightRatio &&
1149  biggest_gap > smallest_gap * kMinCaptionGapRatio)
1150  break;
1151  }
1152  }
1153  if (debug) {
1154  tprintf("Line count=%d, biggest gap %d, smallest%d, mean height %d\n",
1155  line_count, biggest_gap, smallest_gap, mean_height);
1156  if (end_partner != nullptr) {
1157  tprintf("End partner:");
1158  end_partner->bounding_box().print();
1159  }
1160  }
1161  if (next_partner == nullptr && line_count <= kMaxCaptionLines)
1162  end_partner = nullptr; // No gap, but line count is small.
1163  if (line_count <= kMaxCaptionLines) {
1164  // This is a qualified caption. Mark the text as caption.
1165  for (ColPartition* partner = best_caption; partner != nullptr &&
1166  partner != end_partner;
1167  partner = next_partner) {
1168  partner->set_type(PT_CAPTION_TEXT);
1169  partner->SetBlobTypes();
1170  if (debug) {
1171  tprintf("Set caption type for partition:");
1172  partner->bounding_box().print();
1173  }
1174  next_partner = partner->SingletonPartner(best_upper);
1175  }
1176  }
1177  }
1178  }
1179  }
1180 }
1181 
1184 
1185 // For every ColPartition in the grid, finds its upper and lower neighbours.
1187  ColPartitionGridSearch gsearch(this);
1188  gsearch.StartFullSearch();
1189  ColPartition* part;
1190  while ((part = gsearch.NextFullSearch()) != nullptr) {
1191  if (part->IsVerticalType()) {
1192  FindVPartitionPartners(true, part);
1193  FindVPartitionPartners(false, part);
1194  } else {
1195  FindPartitionPartners(true, part);
1196  FindPartitionPartners(false, part);
1197  }
1198  }
1199 }
1200 
1201 // Finds the best partner in the given direction for the given partition.
1202 // Stores the result with AddPartner.
1204  if (part->type() == PT_NOISE)
1205  return; // Noise is not allowed to partner anything.
1206  const TBOX& box = part->bounding_box();
1207  int top = part->median_top();
1208  int bottom = part->median_bottom();
1209  int height = top - bottom;
1210  int mid_y = (bottom + top) / 2;
1211  ColPartitionGridSearch vsearch(this);
1212  // Search down for neighbour below
1213  vsearch.StartVerticalSearch(box.left(), box.right(), part->MidY());
1214  ColPartition* neighbour;
1215  ColPartition* best_neighbour = nullptr;
1216  int best_dist = INT32_MAX;
1217  while ((neighbour = vsearch.NextVerticalSearch(!upper)) != nullptr) {
1218  if (neighbour == part || neighbour->type() == PT_NOISE)
1219  continue; // Noise is not allowed to partner anything.
1220  int neighbour_bottom = neighbour->median_bottom();
1221  int neighbour_top = neighbour->median_top();
1222  int neighbour_y = (neighbour_bottom + neighbour_top) / 2;
1223  if (upper != (neighbour_y > mid_y))
1224  continue;
1225  if (!part->HOverlaps(*neighbour) && !part->WithinSameMargins(*neighbour))
1226  continue;
1227  if (!part->TypesMatch(*neighbour)) {
1228  if (best_neighbour == nullptr)
1229  best_neighbour = neighbour;
1230  continue;
1231  }
1232  int dist = upper ? neighbour_bottom - top : bottom - neighbour_top;
1233  if (dist <= kMaxPartitionSpacing * height) {
1234  if (dist < best_dist) {
1235  best_dist = dist;
1236  best_neighbour = neighbour;
1237  }
1238  } else {
1239  break;
1240  }
1241  }
1242  if (best_neighbour != nullptr)
1243  part->AddPartner(upper, best_neighbour);
1244 }
1245 
1246 // Finds the best partner in the given direction for the given partition.
1247 // Stores the result with AddPartner.
1249  ColPartition* part) {
1250  if (part->type() == PT_NOISE)
1251  return; // Noise is not allowed to partner anything.
1252  const TBOX& box = part->bounding_box();
1253  int left = part->median_left();
1254  int right = part->median_right();
1255  int width = right >= left ? right - left : -1;
1256  int mid_x = (left + right) / 2;
1257  ColPartitionGridSearch hsearch(this);
1258  // Search left for neighbour to_the_left
1259  hsearch.StartSideSearch(mid_x, box.bottom(), box.top());
1260  ColPartition* neighbour;
1261  ColPartition* best_neighbour = nullptr;
1262  int best_dist = INT32_MAX;
1263  while ((neighbour = hsearch.NextSideSearch(to_the_left)) != nullptr) {
1264  if (neighbour == part || neighbour->type() == PT_NOISE)
1265  continue; // Noise is not allowed to partner anything.
1266  int neighbour_left = neighbour->median_left();
1267  int neighbour_right = neighbour->median_right();
1268  int neighbour_x = (neighbour_left + neighbour_right) / 2;
1269  if (to_the_left != (neighbour_x < mid_x))
1270  continue;
1271  if (!part->VOverlaps(*neighbour))
1272  continue;
1273  if (!part->TypesMatch(*neighbour))
1274  continue; // Only match to other vertical text.
1275  int dist = to_the_left ? left - neighbour_right : neighbour_left - right;
1276  if (dist <= kMaxPartitionSpacing * width) {
1277  if (dist < best_dist || best_neighbour == nullptr) {
1278  best_dist = dist;
1279  best_neighbour = neighbour;
1280  }
1281  } else {
1282  break;
1283  }
1284  }
1285  // For vertical partitions, the upper partner is to the left, and lower is
1286  // to the right.
1287  if (best_neighbour != nullptr)
1288  part->AddPartner(to_the_left, best_neighbour);
1289 }
1290 
1291 // For every ColPartition with multiple partners in the grid, reduces the
1292 // number of partners to 0 or 1. If get_desperate is true, goes to more
1293 // desperate merge methods to merge flowing text before breaking partnerships.
1295  ColPartitionGridSearch gsearch(this);
1296  // Refine in type order so that chasing multiple partners can be done
1297  // before eliminating type mis-matching partners.
1298  for (int type = PT_UNKNOWN + 1; type <= PT_COUNT; type++) {
1299  // Iterate the ColPartitions in the grid.
1300  gsearch.StartFullSearch();
1301  ColPartition* part;
1302  while ((part = gsearch.NextFullSearch()) != nullptr) {
1303  part->RefinePartners(static_cast<PolyBlockType>(type),
1304  get_desperate, this);
1305  // Iterator may have been messed up by a merge.
1306  gsearch.RepositionIterator();
1307  }
1308  }
1309 }
1310 
1311 
1312 // ========================== PRIVATE CODE ========================
1313 
1314 // Finds and returns a list of candidate ColPartitions to merge with part.
1315 // The candidates must overlap search_box, and when merged must not
1316 // overlap any other partitions that are not overlapped by each individually.
1317 void ColPartitionGrid::FindMergeCandidates(const ColPartition* part,
1318  const TBOX& search_box, bool debug,
1319  ColPartition_CLIST* candidates) {
1320  int ok_overlap =
1321  static_cast<int>(kTinyEnoughTextlineOverlapFraction * gridsize() + 0.5);
1322  const TBOX& part_box = part->bounding_box();
1323  // Now run the rect search.
1324  ColPartitionGridSearch rsearch(this);
1325  rsearch.SetUniqueMode(true);
1326  rsearch.StartRectSearch(search_box);
1327  ColPartition* candidate;
1328  while ((candidate = rsearch.NextRectSearch()) != nullptr) {
1329  if (!OKMergeCandidate(part, candidate, debug))
1330  continue;
1331  const TBOX& c_box = candidate->bounding_box();
1332  // Candidate seems to be a potential merge with part. If one contains
1333  // the other, then the merge is a no-brainer. Otherwise, search the
1334  // combined box to see if anything else is inappropriately overlapped.
1335  if (!part_box.contains(c_box) && !c_box.contains(part_box)) {
1336  // Search the combined rectangle to see if anything new is overlapped.
1337  // This is a preliminary test designed to quickly weed-out poor
1338  // merge candidates that would create a big list of overlapped objects
1339  // for the squared-order overlap analysis. Eg. vertical and horizontal
1340  // line-like objects that overlap real text when merged:
1341  // || ==========================
1342  // ||
1343  // || r e a l t e x t
1344  // ||
1345  // ||
1346  TBOX merged_box(part_box);
1347  merged_box += c_box;
1348  ColPartitionGridSearch msearch(this);
1349  msearch.SetUniqueMode(true);
1350  msearch.StartRectSearch(merged_box);
1351  ColPartition* neighbour;
1352  while ((neighbour = msearch.NextRectSearch()) != nullptr) {
1353  if (neighbour == part || neighbour == candidate)
1354  continue; // Ignore itself.
1355  if (neighbour->OKMergeOverlap(*part, *candidate, ok_overlap, false))
1356  continue; // This kind of merge overlap is OK.
1357  TBOX n_box = neighbour->bounding_box();
1358  // The overlap is OK if:
1359  // * the n_box already overlapped the part or the candidate OR
1360  // * the n_box is a suitable merge with either part or candidate
1361  if (!n_box.overlap(part_box) && !n_box.overlap(c_box) &&
1362  !OKMergeCandidate(part, neighbour, false) &&
1363  !OKMergeCandidate(candidate, neighbour, false))
1364  break;
1365  }
1366  if (neighbour != nullptr) {
1367  if (debug) {
1368  tprintf("Combined box overlaps another that is not OK despite"
1369  " allowance of %d:", ok_overlap);
1370  neighbour->bounding_box().print();
1371  tprintf("Reason:");
1372  OKMergeCandidate(part, neighbour, true);
1373  tprintf("...and:");
1374  OKMergeCandidate(candidate, neighbour, true);
1375  tprintf("Overlap:");
1376  neighbour->OKMergeOverlap(*part, *candidate, ok_overlap, true);
1377  }
1378  continue;
1379  }
1380  }
1381  if (debug) {
1382  tprintf("Adding candidate:");
1383  candidate->bounding_box().print();
1384  }
1385  // Unique elements as they arrive.
1386  candidates->add_sorted(SortByBoxLeft<ColPartition>, true, candidate);
1387  }
1388 }
1389 
1390 // Smoothes the region type/flow type of the given part by looking at local
1391 // neighbours and the given image mask. Searches a padded rectangle with the
1392 // padding truncated on one size of the part's box in turn for each side,
1393 // using the result (if any) that has the least distance to all neighbours
1394 // that contribute to the decision. This biases in favor of rectangular
1395 // regions without completely enforcing them.
1396 // If a good decision cannot be reached, the part is left unchanged.
1397 // im_box and rerotation are used to map blob coordinates onto the
1398 // nontext_map, which is used to prevent the spread of text neighbourhoods
1399 // into images.
1400 // Returns true if the partition was changed.
1401 bool ColPartitionGrid::SmoothRegionType(Pix* nontext_map,
1402  const TBOX& im_box,
1403  const FCOORD& rerotation,
1404  bool debug,
1405  ColPartition* part) {
1406  const TBOX& part_box = part->bounding_box();
1407  if (debug) {
1408  tprintf("Smooothing part at:");
1409  part_box.print();
1410  }
1411  BlobRegionType best_type = BRT_UNKNOWN;
1412  int best_dist = INT32_MAX;
1413  int max_dist = std::min(part_box.width(), part_box.height());
1414  max_dist = std::max(max_dist * kMaxNeighbourDistFactor, gridsize() * 2);
1415  // Search with the pad truncated on each side of the box in turn.
1416  bool any_image = false;
1417  bool all_image = true;
1418  for (int d = 0; d < BND_COUNT; ++d) {
1419  int dist;
1420  BlobNeighbourDir dir = static_cast<BlobNeighbourDir>(d);
1421  BlobRegionType type = SmoothInOneDirection(dir, nontext_map, im_box,
1422  rerotation, debug, *part,
1423  &dist);
1424  if (debug) {
1425  tprintf("Result in dir %d = %d at dist %d\n", dir, type, dist);
1426  }
1427  if (type != BRT_UNKNOWN && dist < best_dist) {
1428  best_dist = dist;
1429  best_type = type;
1430  }
1431  if (type == BRT_POLYIMAGE)
1432  any_image = true;
1433  else
1434  all_image = false;
1435  }
1436  if (best_dist > max_dist)
1437  return false; // Too far away to set the type with it.
1438  if (part->flow() == BTFT_STRONG_CHAIN && !all_image) {
1439  return false; // We are not modifying it.
1440  }
1441  BlobRegionType new_type = part->blob_type();
1442  BlobTextFlowType new_flow = part->flow();
1443  if (best_type == BRT_TEXT && !any_image) {
1444  new_flow = BTFT_STRONG_CHAIN;
1445  new_type = BRT_TEXT;
1446  } else if (best_type == BRT_VERT_TEXT && !any_image) {
1447  new_flow = BTFT_STRONG_CHAIN;
1448  new_type = BRT_VERT_TEXT;
1449  } else if (best_type == BRT_POLYIMAGE) {
1450  new_flow = BTFT_NONTEXT;
1451  new_type = BRT_UNKNOWN;
1452  }
1453  if (new_type != part->blob_type() || new_flow != part->flow()) {
1454  part->set_flow(new_flow);
1455  part->set_blob_type(new_type);
1456  part->SetBlobTypes();
1457  if (debug) {
1458  tprintf("Modified part:");
1459  part->Print();
1460  }
1461  return true;
1462  } else {
1463  return false;
1464  }
1465 }
1466 
1467 // Sets up a search box based on the part_box, padded in all directions
1468 // except direction. Also setup dist_scaling to weight x,y distances according
1469 // to the given direction.
1470 static void ComputeSearchBoxAndScaling(BlobNeighbourDir direction,
1471  const TBOX& part_box,
1472  int min_padding,
1473  TBOX* search_box,
1474  ICOORD* dist_scaling) {
1475  *search_box = part_box;
1476  // Generate a pad value based on the min dimension of part_box, but at least
1477  // min_padding and then scaled by kMaxPadFactor.
1478  int padding = std::min(part_box.height(), part_box.width());
1479  padding = std::max(padding, min_padding);
1480  padding *= kMaxPadFactor;
1481  search_box->pad(padding, padding);
1482  // Truncate the box in the appropriate direction and make the distance
1483  // metric slightly biased in the truncated direction.
1484  switch (direction) {
1485  case BND_LEFT:
1486  search_box->set_left(part_box.left());
1487  *dist_scaling = ICOORD(2, 1);
1488  break;
1489  case BND_BELOW:
1490  search_box->set_bottom(part_box.bottom());
1491  *dist_scaling = ICOORD(1, 2);
1492  break;
1493  case BND_RIGHT:
1494  search_box->set_right(part_box.right());
1495  *dist_scaling = ICOORD(2, 1);
1496  break;
1497  case BND_ABOVE:
1498  search_box->set_top(part_box.top());
1499  *dist_scaling = ICOORD(1, 2);
1500  break;
1501  default:
1502  ASSERT_HOST(false);
1503  }
1504 }
1505 
1506 // Local enum used by SmoothInOneDirection and AccumulatePartDistances
1507 // for the different types of partition neighbour.
1509  NPT_HTEXT, // Definite horizontal text.
1510  NPT_VTEXT, // Definite vertical text.
1511  NPT_WEAK_HTEXT, // Weakly horizontal text. Counts as HTEXT for HTEXT, but
1512  // image for image and VTEXT.
1513  NPT_WEAK_VTEXT, // Weakly vertical text. Counts as VTEXT for VTEXT, but
1514  // image for image and HTEXT.
1515  NPT_IMAGE, // Defininte non-text.
1516  NPT_COUNT // Number of array elements.
1517 };
1518 
1519 // Executes the search for SmoothRegionType in a single direction.
1520 // Creates a bounding box that is padded in all directions except direction,
1521 // and searches it for other partitions. Finds the nearest collection of
1522 // partitions that makes a decisive result (if any) and returns the type
1523 // and the distance of the collection. If there are any pixels in the
1524 // nontext_map, then the decision is biased towards image.
1525 BlobRegionType ColPartitionGrid::SmoothInOneDirection(
1526  BlobNeighbourDir direction, Pix* nontext_map,
1527  const TBOX& im_box, const FCOORD& rerotation,
1528  bool debug, const ColPartition& part, int* best_distance) {
1529  // Set up a rectangle search bounded by the part.
1530  const TBOX& part_box = part.bounding_box();
1531  TBOX search_box;
1532  ICOORD dist_scaling;
1533  ComputeSearchBoxAndScaling(direction, part_box, gridsize(),
1534  &search_box, &dist_scaling);
1535  bool image_region = ImageFind::CountPixelsInRotatedBox(search_box, im_box,
1536  rerotation,
1537  nontext_map) > 0;
1539  AccumulatePartDistances(part, dist_scaling, search_box,
1540  nontext_map, im_box, rerotation, debug, dists);
1541  // By iteratively including the next smallest distance across the vectors,
1542  // (as in a merge sort) we can use the vector indices as counts of each type
1543  // and find the nearest set of objects that give us a definite decision.
1544  int counts[NPT_COUNT];
1545  memset(counts, 0, sizeof(counts[0]) * NPT_COUNT);
1546  // If there is image in the search box, tip the balance in image's favor.
1547  int image_bias = image_region ? kSmoothDecisionMargin / 2 : 0;
1548  BlobRegionType text_dir = part.blob_type();
1549  BlobTextFlowType flow_type = part.flow();
1550  int min_dist = 0;
1551  do {
1552  // Find the minimum new entry across the vectors
1553  min_dist = INT32_MAX;
1554  for (int i = 0; i < NPT_COUNT; ++i) {
1555  if (counts[i] < dists[i].size() && dists[i][counts[i]] < min_dist)
1556  min_dist = dists[i][counts[i]];
1557  }
1558  // Step all the indices/counts forward to include min_dist.
1559  for (int i = 0; i < NPT_COUNT; ++i) {
1560  while (counts[i] < dists[i].size() && dists[i][counts[i]] <= min_dist)
1561  ++counts[i];
1562  }
1563  *best_distance = min_dist;
1564  if (debug) {
1565  tprintf("Totals: htext=%d+%d, vtext=%d+%d, image=%d+%d, at dist=%d\n",
1566  counts[NPT_HTEXT], counts[NPT_WEAK_HTEXT],
1567  counts[NPT_VTEXT], counts[NPT_WEAK_VTEXT],
1568  counts[NPT_IMAGE], image_bias, min_dist);
1569  }
1570  // See if we have a decision yet.
1571  int image_count = counts[NPT_IMAGE];
1572  int htext_score = counts[NPT_HTEXT] + counts[NPT_WEAK_HTEXT] -
1573  (image_count + counts[NPT_WEAK_VTEXT]);
1574  int vtext_score = counts[NPT_VTEXT] + counts[NPT_WEAK_VTEXT] -
1575  (image_count + counts[NPT_WEAK_HTEXT]);
1576  if (image_count > 0 &&
1577  image_bias - htext_score >= kSmoothDecisionMargin &&
1578  image_bias - vtext_score >= kSmoothDecisionMargin) {
1579  *best_distance = dists[NPT_IMAGE][0];
1580  if (!dists[NPT_WEAK_VTEXT].empty() &&
1581  *best_distance > dists[NPT_WEAK_VTEXT][0])
1582  *best_distance = dists[NPT_WEAK_VTEXT][0];
1583  if (!dists[NPT_WEAK_HTEXT].empty() &&
1584  *best_distance > dists[NPT_WEAK_HTEXT][0])
1585  *best_distance = dists[NPT_WEAK_HTEXT][0];
1586  return BRT_POLYIMAGE;
1587  }
1588  if ((text_dir != BRT_VERT_TEXT || flow_type != BTFT_CHAIN) &&
1589  counts[NPT_HTEXT] > 0 && htext_score >= kSmoothDecisionMargin) {
1590  *best_distance = dists[NPT_HTEXT][0];
1591  return BRT_TEXT;
1592  } else if ((text_dir != BRT_TEXT || flow_type != BTFT_CHAIN) &&
1593  counts[NPT_VTEXT] > 0 && vtext_score >= kSmoothDecisionMargin) {
1594  *best_distance = dists[NPT_VTEXT][0];
1595  return BRT_VERT_TEXT;
1596  }
1597  } while (min_dist < INT32_MAX);
1598  return BRT_UNKNOWN;
1599 }
1600 
1601 // Counts the partitions in the given search_box by appending the gap
1602 // distance (scaled by dist_scaling) of the part from the base_part to the
1603 // vector of the appropriate type for the partition. Prior to return, the
1604 // vectors in the dists array are sorted in increasing order.
1605 // The nontext_map (+im_box, rerotation) is used to make text invisible if
1606 // there is non-text in between.
1607 // dists must be an array of GenericVectors of size NPT_COUNT.
1608 void ColPartitionGrid::AccumulatePartDistances(const ColPartition& base_part,
1609  const ICOORD& dist_scaling,
1610  const TBOX& search_box,
1611  Pix* nontext_map,
1612  const TBOX& im_box,
1613  const FCOORD& rerotation,
1614  bool debug,
1615  GenericVector<int>* dists) {
1616  const TBOX& part_box = base_part.bounding_box();
1617  ColPartitionGridSearch rsearch(this);
1618  rsearch.SetUniqueMode(true);
1619  rsearch.StartRectSearch(search_box);
1620  ColPartition* neighbour;
1621  // Search for compatible neighbours with a similar strokewidth, but not
1622  // on the other side of a tab vector.
1623  while ((neighbour = rsearch.NextRectSearch()) != nullptr) {
1624  if (neighbour->IsUnMergeableType() ||
1625  !base_part.ConfirmNoTabViolation(*neighbour) ||
1626  neighbour == &base_part)
1627  continue;
1628  TBOX nbox = neighbour->bounding_box();
1629  BlobRegionType n_type = neighbour->blob_type();
1630  if ((n_type == BRT_TEXT || n_type == BRT_VERT_TEXT) &&
1631  !ImageFind::BlankImageInBetween(part_box, nbox, im_box, rerotation,
1632  nontext_map))
1633  continue; // Text not visible the other side of image.
1634  if (BLOBNBOX::IsLineType(n_type))
1635  continue; // Don't use horizontal lines as neighbours.
1636  int x_gap = std::max(part_box.x_gap(nbox), 0);
1637  int y_gap = std::max(part_box.y_gap(nbox), 0);
1638  int n_dist = x_gap * dist_scaling.x() + y_gap* dist_scaling.y();
1639  if (debug) {
1640  tprintf("Part has x-gap=%d, y=%d, dist=%d at:",
1641  x_gap, y_gap, n_dist);
1642  nbox.print();
1643  }
1644  // Truncate the number of boxes, so text doesn't get too much advantage.
1645  int n_boxes = std::min(neighbour->boxes_count(), kSmoothDecisionMargin);
1646  BlobTextFlowType n_flow = neighbour->flow();
1647  GenericVector<int>* count_vector = nullptr;
1648  if (n_flow == BTFT_STRONG_CHAIN) {
1649  if (n_type == BRT_TEXT)
1650  count_vector = &dists[NPT_HTEXT];
1651  else
1652  count_vector = &dists[NPT_VTEXT];
1653  if (debug) {
1654  tprintf("%s %d\n", n_type == BRT_TEXT ? "Htext" : "Vtext", n_boxes);
1655  }
1656  } else if ((n_type == BRT_TEXT || n_type == BRT_VERT_TEXT) &&
1657  (n_flow == BTFT_CHAIN || n_flow == BTFT_NEIGHBOURS)) {
1658  // Medium text counts as weak, and all else counts as image.
1659  if (n_type == BRT_TEXT)
1660  count_vector = &dists[NPT_WEAK_HTEXT];
1661  else
1662  count_vector = &dists[NPT_WEAK_VTEXT];
1663  if (debug) tprintf("Weak %d\n", n_boxes);
1664  } else {
1665  count_vector = &dists[NPT_IMAGE];
1666  if (debug) tprintf("Image %d\n", n_boxes);
1667  }
1668  if (count_vector != nullptr) {
1669  for (int i = 0; i < n_boxes; ++i)
1670  count_vector->push_back(n_dist);
1671  }
1672  if (debug) {
1673  neighbour->Print();
1674  }
1675  }
1676  for (int i = 0; i < NPT_COUNT; ++i)
1677  dists[i].sort();
1678 }
1679 
1680 // Improves the margins of the part ColPartition by searching for
1681 // neighbours that vertically overlap significantly.
1682 // columns may be nullptr, and indicates the assigned column structure this
1683 // is applicable to part.
1684 void ColPartitionGrid::FindPartitionMargins(ColPartitionSet* columns,
1685  ColPartition* part) {
1686  // Set up a rectangle search x-bounded by the column and y by the part.
1687  TBOX box = part->bounding_box();
1688  int y = part->MidY();
1689  // Initial left margin is based on the column, if there is one.
1690  int left_margin = bleft().x();
1691  int right_margin = tright().x();
1692  if (columns != nullptr) {
1693  ColPartition* column = columns->ColumnContaining(box.left(), y);
1694  if (column != nullptr)
1695  left_margin = column->LeftAtY(y);
1696  column = columns->ColumnContaining(box.right(), y);
1697  if (column != nullptr)
1698  right_margin = column->RightAtY(y);
1699  }
1700  left_margin -= kColumnWidthFactor;
1701  right_margin += kColumnWidthFactor;
1702  // Search for ColPartitions that reduce the margin.
1703  left_margin = FindMargin(box.left() + box.height(), true, left_margin,
1704  box.bottom(), box.top(), part);
1705  part->set_left_margin(left_margin);
1706  // Search for ColPartitions that reduce the margin.
1707  right_margin = FindMargin(box.right() - box.height(), false, right_margin,
1708  box.bottom(), box.top(), part);
1709  part->set_right_margin(right_margin);
1710 }
1711 
1712 // Starting at x, and going in the specified direction, up to x_limit, finds
1713 // the margin for the given y range by searching sideways,
1714 // and ignoring not_this.
1715 int ColPartitionGrid::FindMargin(int x, bool right_to_left, int x_limit,
1716  int y_bottom, int y_top,
1717  const ColPartition* not_this) {
1718  int height = y_top - y_bottom;
1719  // Iterate the ColPartitions in the grid.
1720  ColPartitionGridSearch side_search(this);
1721  side_search.SetUniqueMode(true);
1722  side_search.StartSideSearch(x, y_bottom, y_top);
1723  ColPartition* part;
1724  while ((part = side_search.NextSideSearch(right_to_left)) != nullptr) {
1725  // Ignore itself.
1726  if (part == not_this) // || part->IsLineType())
1727  continue;
1728  // Must overlap by enough, based on the min of the heights, so
1729  // large partitions can't smash through small ones.
1730  TBOX box = part->bounding_box();
1731  int min_overlap = std::min(height, static_cast<int>(box.height()));
1732  min_overlap = static_cast<int>(min_overlap * kMarginOverlapFraction + 0.5);
1733  int y_overlap = std::min(y_top, static_cast<int>(box.top())) - std::max(y_bottom, static_cast<int>(box.bottom()));
1734  if (y_overlap < min_overlap)
1735  continue;
1736  // Must be going the right way.
1737  int x_edge = right_to_left ? box.right() : box.left();
1738  if ((x_edge < x) != right_to_left)
1739  continue;
1740  // If we have gone past x_limit, then x_limit will do.
1741  if ((x_edge < x_limit) == right_to_left)
1742  break;
1743  // It reduces x limit, so save the new one.
1744  x_limit = x_edge;
1745  }
1746  return x_limit;
1747 }
1748 
1749 
1750 } // namespace tesseract.
void ReTypeBlobs(BLOBNBOX_LIST *im_blobs)
void CopyRightTab(const ColPartition &src, bool take_box)
bool textord_tabfind_show_color_fit
void RepositionIterator()
Definition: bbgrid.h:894
const ICOORD & topright() const
Definition: rect.h:104
BlobNeighbourDir
Definition: blobbox.h:88
void ListFindMargins(ColPartitionSet **best_columns, ColPartition_LIST *parts)
const double kMinCaptionGapRatio
void set_poly_block(POLY_BLOCK *blk)
set the poly block
Definition: pdblock.h:58
Definition: capi.h:101
const int kMaxNeighbourDistFactor
bool TypesMatch(const ColPartition &other) const
Definition: colpartition.h:410
void DeleteUnknownParts(TO_BLOCK *block)
void GridFindMargins(ColPartitionSet **best_columns)
void RemoveBox(BLOBNBOX *box)
void SetRightTab(const TabVector *tab_vector)
#define BOOL_VAR(name, val, comment)
Definition: params.h:279
static int CountPixelsInRotatedBox(TBOX box, const TBOX &im_box, const FCOORD &rotation, Pix *pix)
Definition: imagefind.cpp:598
int gridsize() const
Definition: bbgrid.h:64
const int kSmoothDecisionMargin
const double kMaxPartitionSpacing
void set_vertical(const ICOORD &v)
Definition: colpartition.h:194
bool IsRightTab() const
Definition: tabvector.h:217
void StartSideSearch(int x, int ymin, int ymax)
Definition: bbgrid.h:748
void set_top(int y)
Definition: rect.h:61
bool IsSingleton() const
Definition: colpartition.h:362
TBOX intersection(const TBOX &box) const
Definition: rect.cpp:87
void print() const
Definition: rect.h:278
void Merges(TessResultCallback2< bool, ColPartition *, TBOX *> *box_cb, TessResultCallback2< bool, const ColPartition *, const ColPartition *> *confirm_cb)
BBC * NextFullSearch()
Definition: bbgrid.h:677
int y_gap(const TBOX &box) const
Definition: rect.h:233
const double kBigPartSizeRatio
void set_bottom(int y)
Definition: rect.h:68
BlobRegionType blob_type() const
Definition: colpartition.h:149
const ICOORD & bleft() const
Definition: bbgrid.h:73
int16_t y() const
access_function
Definition: points.h:57
BlobRegionType
Definition: blobbox.h:73
Definition: rect.h:34
void reserve(int size)
BlobTextFlowType flow() const
Definition: blobbox.h:296
PolyBlockType
Definition: publictypes.h:53
ColPartition * SingletonPartner(bool upper)
static bool WithinTestRegion(int detail_level, int x, int y)
int x_gap(const TBOX &box) const
Definition: rect.h:225
int direction(EDGEPT *point)
Definition: vecfuncs.cpp:43
const double kMarginOverlapFraction
BlobTextFlowType
Definition: blobbox.h:115
void HandleClick(int x, int y)
void SplitOverlappingPartitions(ColPartition_LIST *big_parts)
int HCoreOverlap(const ColPartition &other) const
Definition: colpartition.h:385
TabVector * RightTabForBox(const TBOX &box, bool crossing, bool extended)
Definition: tabfind.cpp:305
void SetTabStops(TabFind *tabgrid)
Definition: capi.h:100
bool MergePart(TessResultCallback2< bool, ColPartition *, TBOX *> *box_cb, TessResultCallback2< bool, const ColPartition *, const ColPartition *> *confirm_cb, ColPartition *part)
TO_ROW_LIST * get_rows()
Definition: blobbox.h:717
void Absorb(ColPartition *other, WidthCallback *cb)
bool OKDiacriticMerge(const ColPartition &candidate, bool debug) const
bool HOverlaps(const ColPartition &other) const
Definition: colpartition.h:366
bool IsLeftTab() const
Definition: tabvector.h:213
TabVector * LeftTabForBox(const TBOX &box, bool crossing, bool extended)
Definition: tabfind.cpp:349
ColPartition_CLIST * lower_partners()
Definition: colpartition.h:200
BBC * NextRadSearch()
Definition: bbgrid.h:715
void StartRectSearch(const TBOX &rect)
Definition: bbgrid.h:832
const int kColumnWidthFactor
Definition: tabfind.h:42
float line_spacing
Definition: blobbox.h:792
void set_right(int x)
Definition: rect.h:82
void CopyLeftTab(const ColPartition &src, bool take_box)
BLOBNBOX_CLIST * boxes()
Definition: colpartition.h:188
int16_t width() const
Definition: rect.h:115
bool WithinSameMargins(const ColPartition &other) const
Definition: colpartition.h:402
void set_blob_type(BlobRegionType t)
Definition: colpartition.h:152
void set_flow(BlobTextFlowType f)
Definition: colpartition.h:158
bool GridSmoothNeighbours(BlobTextFlowType source_type, Pix *nontext_map, const TBOX &im_box, const FCOORD &rerotation)
ICOORD tright_
Definition: bbgrid.h:92
void RefinePartners(PolyBlockType type, bool get_desperate, ColPartitionGrid *grid)
float max_blob_size
Definition: blobbox.h:799
int16_t left() const
Definition: rect.h:72
const int kMaxPadFactor
static ColPartition * MakeBigPartition(BLOBNBOX *box, ColPartition_LIST *big_part_list)
GridSearch< ColPartition, ColPartition_CLIST, ColPartition_C_IT > ColPartitionGridSearch
Definition: colpartition.h:936
int16_t top() const
Definition: rect.h:58
void StartRadSearch(int x, int y, int max_radius)
Definition: bbgrid.h:700
void set_region_type(BlobRegionType new_type)
Definition: blobbox.h:287
BBC * NextSideSearch(bool right_to_left)
Definition: bbgrid.h:763
Definition: capi.h:101
const double kTinyEnoughTextlineOverlapFraction
void set_owner(tesseract::ColPartition *new_owner)
Definition: blobbox.h:356
ColPartition * SplitAtBlob(BLOBNBOX *split_blob)
integer coordinate
Definition: points.h:32
int16_t x() const
access function
Definition: points.h:53
BBC * NextRectSearch()
Definition: bbgrid.h:844
bool MakeColPartSets(PartSetVector *part_sets)
BlobRegionType region_type() const
Definition: blobbox.h:284
virtual R Run(A1, A2)=0
const double kMinCaptionGapHeightRatio
void FindVPartitionPartners(bool to_the_left, ColPartition *part)
const int kMaxCaptionLines
void InsertBBox(bool h_spread, bool v_spread, ColPartition *bbox)
Definition: bbgrid.h:488
ColPartition_CLIST * upper_partners()
Definition: colpartition.h:197
void rotate_large(const FCOORD &vec)
Definition: rect.cpp:72
WidthCallback * WidthCB()
Definition: tabfind.h:158
void StartVerticalSearch(int xmin, int xmax, int y)
Definition: bbgrid.h:790
DLLSYM void tprintf(const char *format,...)
Definition: tprintf.cpp:37
Definition: ocrblock.h:30
void GridCoords(int x, int y, int *grid_x, int *grid_y) const
Definition: bbgrid.cpp:53
void AddPartner(bool upper, ColPartition *partner)
int32_t area() const
Definition: rect.h:122
void DeleteUnownedNoise()
Definition: blobbox.cpp:1038
bool IsUnMergeableType() const
Definition: colpartition.h:450
ColPartition * BestMergeCandidate(const ColPartition *part, ColPartition_CLIST *candidates, bool debug, TessResultCallback2< bool, const ColPartition *, const ColPartition *> *confirm_cb, int *overlap_increase)
void FindOverlappingPartitions(const TBOX &box, const ColPartition *not_this, ColPartition_CLIST *parts)
int push_back(T object)
const ICOORD & botleft() const
Definition: rect.h:92
int GridY() const
Definition: bbgrid.h:247
BlobTextFlowType flow() const
Definition: colpartition.h:155
void set_flow(BlobTextFlowType value)
Definition: blobbox.h:299
void set_left(int x)
Definition: rect.h:75
const TBOX & bounding_box() const
Definition: colpartition.h:110
bool VSignificantCoreOverlap(const ColPartition &other) const
Definition: colpartition.h:391
int CountOverlappingBoxes(const TBOX &box)
bool overlap(const TBOX &box) const
Definition: rect.h:355
int VCoreOverlap(const ColPartition &other) const
Definition: colpartition.h:376
BLOBNBOX * OverlapSplitBlob(const TBOX &box)
bool contains(const FCOORD pt) const
Definition: rect.h:333
void Init(int gridsize, const ICOORD &bleft, const ICOORD &tright)
Definition: bbgrid.h:447
void RefinePartitionPartners(bool get_desperate)
int32_t area()
Definition: stepblob.cpp:275
Definition: points.h:189
int ComputeTotalOverlap(ColPartitionGrid **overlap_grid)
const TBOX & bounding_box() const
Definition: blobbox.h:231
ColPartitionSet * MakeSingleColumnSet(WidthCallback *cb)
bool IsVerticalType() const
Definition: colpartition.h:442
static bool BlankImageInBetween(const TBOX &box1, const TBOX &box2, const TBOX &im_box, const FCOORD &rotation, Pix *pix)
Definition: imagefind.cpp:577
int16_t right() const
Definition: rect.h:79
bool IsImageType() const
Definition: colpartition.h:430
bool OKMergeOverlap(const ColPartition &merge1, const ColPartition &merge2, int ok_box_overlap, bool debug)
void Deskew(const FCOORD &deskew)
TBOX bounding_union(const TBOX &box) const
Definition: rect.cpp:129
void ExtractPartitionsAsBlocks(BLOCK_LIST *blocks, TO_BLOCK_LIST *to_blocks)
BBC * NextVerticalSearch(bool top_to_bottom)
Definition: bbgrid.h:804
TBOX BoundsWithoutBox(BLOBNBOX *box)
void StartFullSearch()
Definition: bbgrid.h:667
void SetLeftTab(const TabVector *tab_vector)
void SetUniqueMode(bool mode)
Definition: bbgrid.h:255
void SetColumnGoodness(WidthCallback *cb)
ColPartition * ShallowCopy() const
int16_t bottom() const
Definition: rect.h:65
PDBLK pdblk
Definition: ocrblock.h:192
int16_t height() const
Definition: rect.h:108
C_BLOB * cblob() const
Definition: blobbox.h:269
bool VOverlaps(const ColPartition &other) const
Definition: colpartition.h:371
void set_type(PolyBlockType t)
Definition: colpartition.h:185
void pad(int xpad, int ypad)
Definition: rect.h:131
int gridheight() const
Definition: bbgrid.h:70
const ICOORD & tright() const
Definition: bbgrid.h:76
static bool IsTextType(BlobRegionType type)
Definition: blobbox.h:419
float line_size
Definition: blobbox.h:798
void RecomputeBounds(int gridsize, const ICOORD &bleft, const ICOORD &tright, const ICOORD &vertical)
void set_block_owned(bool owned)
Definition: colpartition.h:209
#define ASSERT_HOST(x)
Definition: errcode.h:84
PolyBlockType type() const
Definition: colpartition.h:182
static bool IsLineType(BlobRegionType type)
Definition: blobbox.h:427