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