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