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