tesseract  3.05.00
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
equationdetect.cpp
Go to the documentation of this file.
1 // File: equationdetect.cpp
3 // Description: Helper classes to detect equations.
4 // Author: Zongyi (Joe) Liu (joeliu@google.com)
5 // Created: Fri Aug 31 11:13:01 PST 2011
6 //
7 // (C) Copyright 2011, 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 _MSC_VER
21 #pragma warning(disable:4244) // Conversion warnings
22 #include <mathfix.h>
23 #include <windows.h>
24 #endif
25 
26 #ifdef __MINGW32__
27 #include <limits.h>
28 #endif
29 
30 #include <float.h>
31 
32 // Include automatically generated configuration file if running autoconf.
33 #ifdef HAVE_CONFIG_H
34 #include "config_auto.h"
35 #endif
36 
37 #include "equationdetect.h"
38 
39 #include "bbgrid.h"
40 #include "classify.h"
41 #include "colpartition.h"
42 #include "colpartitiongrid.h"
43 #include "colpartitionset.h"
44 #include "helpers.h"
45 #include "ratngs.h"
46 #include "tesseractclass.h"
47 
48 // Config variables.
49 BOOL_VAR(equationdetect_save_bi_image, false, "Save input bi image");
50 BOOL_VAR(equationdetect_save_spt_image, false, "Save special character image");
51 BOOL_VAR(equationdetect_save_seed_image, false, "Save the seed image");
52 BOOL_VAR(equationdetect_save_merged_image, false, "Save the merged image");
53 
54 namespace tesseract {
55 
57 // Utility ColParition sort functions.
59 static int SortCPByTopReverse(const void* p1, const void* p2) {
60  const ColPartition* cp1 = *reinterpret_cast<ColPartition* const*>(p1);
61  const ColPartition* cp2 = *reinterpret_cast<ColPartition* const*>(p2);
62  ASSERT_HOST(cp1 != NULL && cp2 != NULL);
63  const TBOX &box1(cp1->bounding_box()), &box2(cp2->bounding_box());
64  return box2.top() - box1.top();
65 }
66 
67 static int SortCPByBottom(const void* p1, const void* p2) {
68  const ColPartition* cp1 = *reinterpret_cast<ColPartition* const*>(p1);
69  const ColPartition* cp2 = *reinterpret_cast<ColPartition* const*>(p2);
70  ASSERT_HOST(cp1 != NULL && cp2 != NULL);
71  const TBOX &box1(cp1->bounding_box()), &box2(cp2->bounding_box());
72  return box1.bottom() - box2.bottom();
73 }
74 
75 static int SortCPByHeight(const void* p1, const void* p2) {
76  const ColPartition* cp1 = *reinterpret_cast<ColPartition* const*>(p1);
77  const ColPartition* cp2 = *reinterpret_cast<ColPartition* const*>(p2);
78  ASSERT_HOST(cp1 != NULL && cp2 != NULL);
79  const TBOX &box1(cp1->bounding_box()), &box2(cp2->bounding_box());
80  return box1.height() - box2.height();
81 }
82 
83 // TODO(joeliu): we may want to parameterize these constants.
84 const float kMathDigitDensityTh1 = 0.25;
85 const float kMathDigitDensityTh2 = 0.1;
86 const float kMathItalicDensityTh = 0.5;
87 const float kUnclearDensityTh = 0.25;
88 const int kSeedBlobsCountTh = 10;
90 
91 // Returns true if PolyBlockType is of text type or equation type.
93  return PTIsTextType(type) || type == PT_EQUATION;
94 }
95 
96 inline bool IsLeftIndented(const EquationDetect::IndentType type) {
97  return type == EquationDetect::LEFT_INDENT ||
99 }
100 
102  return type == EquationDetect::RIGHT_INDENT ||
104 }
105 
106 EquationDetect::EquationDetect(const char* equ_datapath,
107  const char* equ_name) {
108  const char* default_name = "equ";
109  if (equ_name == NULL) {
110  equ_name = default_name;
111  }
113  resolution_ = 0;
114  page_count_ = 0;
115 
116  // Construct equ_tesseract_.
117  equ_tesseract_ = new Tesseract();
118  if (equ_tesseract_->init_tesseract(equ_datapath, equ_name,
120  tprintf("Warning: equation region detection requested,"
121  " but %s failed to load from %s\n", equ_name, equ_datapath);
122  delete equ_tesseract_;
123  equ_tesseract_ = NULL;
124  }
125 
126  cps_super_bbox_ = NULL;
127 }
128 
130  if (equ_tesseract_) {
131  delete (equ_tesseract_);
132  }
133  if (cps_super_bbox_) {
134  delete(cps_super_bbox_);
135  }
136 }
137 
139  lang_tesseract_ = lang_tesseract;
140 }
141 
142 void EquationDetect::SetResolution(const int resolution) {
143  resolution_ = resolution;
144 }
145 
147  if (to_block == NULL) {
148  tprintf("Warning: input to_block is NULL!\n");
149  return -1;
150  }
151 
153  blob_lists.push_back(&(to_block->blobs));
154  blob_lists.push_back(&(to_block->large_blobs));
155  for (int i = 0; i < blob_lists.size(); ++i) {
156  BLOBNBOX_IT bbox_it(blob_lists[i]);
157  for (bbox_it.mark_cycle_pt (); !bbox_it.cycled_list();
158  bbox_it.forward()) {
159  bbox_it.data()->set_special_text_type(BSTT_NONE);
160  }
161  }
162 
163  return 0;
164 }
165 
167  BLOBNBOX *blobnbox, const int height_th) {
168  ASSERT_HOST(blobnbox != NULL);
169  if (blobnbox->bounding_box().height() < height_th && height_th > 0) {
170  // For small blob, we simply set to BSTT_NONE.
171  blobnbox->set_special_text_type(BSTT_NONE);
172  return;
173  }
174 
175  BLOB_CHOICE_LIST ratings_equ, ratings_lang;
176  C_BLOB* blob = blobnbox->cblob();
177  // TODO(joeliu/rays) Fix this. We may have to normalize separately for
178  // each classifier here, as they may require different PolygonalCopy.
179  TBLOB* tblob = TBLOB::PolygonalCopy(false, blob);
180  const TBOX& box = tblob->bounding_box();
181 
182  // Normalize the blob. Set the origin to the place we want to be the
183  // bottom-middle, and scaling is to make the height the x-height.
184  float scaling = static_cast<float>(kBlnXHeight) / box.height();
185  float x_orig = (box.left() + box.right()) / 2.0f, y_orig = box.bottom();
186  TBLOB* normed_blob = new TBLOB(*tblob);
187  normed_blob->Normalize(NULL, NULL, NULL, x_orig, y_orig, scaling, scaling,
188  0.0f, static_cast<float>(kBlnBaselineOffset),
189  false, NULL);
190  equ_tesseract_->AdaptiveClassifier(normed_blob, &ratings_equ);
191  lang_tesseract_->AdaptiveClassifier(normed_blob, &ratings_lang);
192  delete normed_blob;
193  delete tblob;
194 
195  // Get the best choice from ratings_lang and rating_equ. As the choice in the
196  // list has already been sorted by the certainty, we simply use the first
197  // choice.
198  BLOB_CHOICE *lang_choice = NULL, *equ_choice = NULL;
199  if (ratings_lang.length() > 0) {
200  BLOB_CHOICE_IT choice_it(&ratings_lang);
201  lang_choice = choice_it.data();
202  }
203  if (ratings_equ.length() > 0) {
204  BLOB_CHOICE_IT choice_it(&ratings_equ);
205  equ_choice = choice_it.data();
206  }
207 
208  float lang_score = lang_choice ? lang_choice->certainty() : -FLT_MAX;
209  float equ_score = equ_choice ? equ_choice->certainty() : -FLT_MAX;
210 
211  const float kConfScoreTh = -5.0f, kConfDiffTh = 1.8;
212  // The scores here are negative, so the max/min == fabs(min/max).
213  // float ratio = fmax(lang_score, equ_score) / fmin(lang_score, equ_score);
214  float diff = fabs(lang_score - equ_score);
216 
217  // Classification.
218  if (fmax(lang_score, equ_score) < kConfScoreTh) {
219  // If both score are very small, then mark it as unclear.
220  type = BSTT_UNCLEAR;
221  } else if (diff > kConfDiffTh && equ_score > lang_score) {
222  // If equ_score is significantly higher, then we classify this character as
223  // math symbol.
224  type = BSTT_MATH;
225  } else if (lang_choice) {
226  // For other cases: lang_score is similar or significantly higher.
227  type = EstimateTypeForUnichar(
228  lang_tesseract_->unicharset, lang_choice->unichar_id());
229  }
230 
231  if (type == BSTT_NONE && lang_tesseract_->get_fontinfo_table().get(
232  lang_choice->fontinfo_id()).is_italic()) {
233  // For text symbol, we still check if it is italic.
235  } else {
236  blobnbox->set_special_text_type(type);
237  }
238 }
239 
241  const UNICHARSET& unicharset, const UNICHAR_ID id) const {
242  STRING s = unicharset.id_to_unichar(id);
243  if (unicharset.get_isalpha(id)) {
244  return BSTT_NONE;
245  }
246 
247  if (unicharset.get_ispunctuation(id)) {
248  // Exclude some special texts that are likely to be confused as math symbol.
249  static GenericVector<UNICHAR_ID> ids_to_exclude;
250  if (ids_to_exclude.empty()) {
251  static const STRING kCharsToEx[] = {"'", "`", "\"", "\\", ",", ".",
252  "〈", "〉", "《", "》", "」", "「", ""};
253  int i = 0;
254  while (kCharsToEx[i] != "") {
255  ids_to_exclude.push_back(
256  unicharset.unichar_to_id(kCharsToEx[i++].string()));
257  }
258  ids_to_exclude.sort();
259  }
260  return ids_to_exclude.bool_binary_search(id) ? BSTT_NONE : BSTT_MATH;
261  }
262 
263  // Check if it is digit. In addition to the isdigit attribute, we also check
264  // if this character belongs to those likely to be confused with a digit.
265  static const STRING kDigitsChars = "|";
266  if (unicharset.get_isdigit(id) ||
267  (s.length() == 1 && kDigitsChars.contains(s[0]))) {
268  return BSTT_DIGIT;
269  } else {
270  return BSTT_MATH;
271  }
272 }
273 
275  // Set configuration for Tesseract::AdaptiveClassifier.
276  equ_tesseract_->tess_cn_matching.set_value(true); // turn it on
277  equ_tesseract_->tess_bn_matching.set_value(false);
278 
279  // Set the multiplier to zero for lang_tesseract_ to improve the accuracy.
280  int classify_class_pruner = lang_tesseract_->classify_class_pruner_multiplier;
281  int classify_integer_matcher =
285 
287  ColPartition *part = NULL;
288  gsearch.StartFullSearch();
289  while ((part = gsearch.NextFullSearch()) != NULL) {
290  if (!IsTextOrEquationType(part->type())) {
291  continue;
292  }
293  IdentifyBlobsToSkip(part);
294  BLOBNBOX_C_IT bbox_it(part->boxes());
295  // Compute the height threshold.
296  GenericVector<int> blob_heights;
297  for (bbox_it.mark_cycle_pt (); !bbox_it.cycled_list();
298  bbox_it.forward()) {
299  if (bbox_it.data()->special_text_type() != BSTT_SKIP) {
300  blob_heights.push_back(bbox_it.data()->bounding_box().height());
301  }
302  }
303  blob_heights.sort();
304  int height_th = blob_heights[blob_heights.size() / 2] / 3 * 2;
305  for (bbox_it.mark_cycle_pt (); !bbox_it.cycled_list();
306  bbox_it.forward()) {
307  if (bbox_it.data()->special_text_type() != BSTT_SKIP) {
308  IdentifySpecialText(bbox_it.data(), height_th);
309  }
310  }
311  }
312 
313  // Set the multiplier values back.
315  classify_class_pruner);
317  classify_integer_matcher);
318 
319  if (equationdetect_save_spt_image) { // For debug.
320  STRING outfile;
321  GetOutputTiffName("_spt", &outfile);
322  PaintSpecialTexts(outfile);
323  }
324 }
325 
327  ASSERT_HOST(part);
328  BLOBNBOX_C_IT blob_it(part->boxes());
329 
330  for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
331  // At this moment, no blob should have been joined.
332  ASSERT_HOST(!blob_it.data()->joined_to_prev());
333  }
334  for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
335  BLOBNBOX* blob = blob_it.data();
336  if (blob->joined_to_prev() || blob->special_text_type() == BSTT_SKIP) {
337  continue;
338  }
339  TBOX blob_box = blob->bounding_box();
340 
341  // Search if any blob can be merged into blob. If found, then we mark all
342  // these blobs as BSTT_SKIP.
343  BLOBNBOX_C_IT blob_it2 = blob_it;
344  bool found = false;
345  while (!blob_it2.at_last()) {
346  BLOBNBOX* nextblob = blob_it2.forward();
347  const TBOX& nextblob_box = nextblob->bounding_box();
348  if (nextblob_box.left() >= blob_box.right()) {
349  break;
350  }
351  const float kWidthR = 0.4, kHeightR = 0.3;
352  bool xoverlap = blob_box.major_x_overlap(nextblob_box),
353  yoverlap = blob_box.y_overlap(nextblob_box);
354  float widthR = static_cast<float>(
355  MIN(nextblob_box.width(), blob_box.width())) /
356  MAX(nextblob_box.width(), blob_box.width());
357  float heightR = static_cast<float>(
358  MIN(nextblob_box.height(), blob_box.height())) /
359  MAX(nextblob_box.height(), blob_box.height());
360 
361  if (xoverlap && yoverlap && widthR > kWidthR && heightR > kHeightR) {
362  // Found one, set nextblob type and recompute blob_box.
363  found = true;
364  nextblob->set_special_text_type(BSTT_SKIP);
365  blob_box += nextblob_box;
366  }
367  }
368  if (found) {
370  }
371  }
372 }
373 
375  ColPartitionGrid* part_grid, ColPartitionSet** best_columns) {
376  if (!equ_tesseract_ || !lang_tesseract_) {
377  tprintf("Warning: equ_tesseract_/lang_tesseract_ is NULL!\n");
378  return -1;
379  }
380  if (!part_grid || !best_columns) {
381  tprintf("part_grid/best_columns is NULL!!\n");
382  return -1;
383  }
384  cp_seeds_.clear();
385  part_grid_ = part_grid;
386  best_columns_ = best_columns;
388  STRING outfile;
389  page_count_++;
390 
392  GetOutputTiffName("_bi", &outfile);
393  pixWrite(outfile.string(), lang_tesseract_->pix_binary(), IFF_TIFF_G4);
394  }
395 
396  // Pass 0: Compute special text type for blobs.
398 
399  // Pass 1: Merge parts by overlap.
401 
402  // Pass 2: compute the math blob density and find the seed partition.
404  // We still need separate seed into block seed and inline seed partition.
406 
408  GetOutputTiffName("_seed", &outfile);
409  PaintColParts(outfile);
410  }
411 
412  // Pass 3: expand block equation seeds.
413  while (!cp_seeds_.empty()) {
414  GenericVector<ColPartition*> seeds_expanded;
415  for (int i = 0; i < cp_seeds_.size(); ++i) {
416  if (ExpandSeed(cp_seeds_[i])) {
417  // If this seed is expanded, then we add it into seeds_expanded. Note
418  // this seed has been removed from part_grid_ if it is expanded.
419  seeds_expanded.push_back(cp_seeds_[i]);
420  }
421  }
422  // Add seeds_expanded back into part_grid_ and reset cp_seeds_.
423  for (int i = 0; i < seeds_expanded.size(); ++i) {
424  InsertPartAfterAbsorb(seeds_expanded[i]);
425  }
426  cp_seeds_ = seeds_expanded;
427  }
428 
429  // Pass 4: find math block satellite text partitions and merge them.
431 
432  if (equationdetect_save_merged_image) { // For debug.
433  GetOutputTiffName("_merged", &outfile);
434  PaintColParts(outfile);
435  }
436 
437  return 0;
438 }
439 
441  while (true) {
442  ColPartition* part = NULL;
443  // partitions that have been updated.
444  GenericVector<ColPartition*> parts_updated;
446  gsearch.StartFullSearch();
447  while ((part = gsearch.NextFullSearch()) != NULL) {
448  if (!IsTextOrEquationType(part->type())) {
449  continue;
450  }
451  GenericVector<ColPartition*> parts_to_merge;
452  SearchByOverlap(part, &parts_to_merge);
453  if (parts_to_merge.empty()) {
454  continue;
455  }
456 
457  // Merge parts_to_merge with part, and remove them from part_grid_.
458  part_grid_->RemoveBBox(part);
459  for (int i = 0; i < parts_to_merge.size(); ++i) {
460  ASSERT_HOST(parts_to_merge[i] != NULL && parts_to_merge[i] != part);
461  part->Absorb(parts_to_merge[i], NULL);
462  }
463  gsearch.RepositionIterator();
464 
465  parts_updated.push_back(part);
466  }
467 
468  if (parts_updated.empty()) { // Exit the loop
469  break;
470  }
471 
472  // Re-insert parts_updated into part_grid_.
473  for (int i = 0; i < parts_updated.size(); ++i) {
474  InsertPartAfterAbsorb(parts_updated[i]);
475  }
476  }
477 }
478 
480  ColPartition* seed,
481  GenericVector<ColPartition*>* parts_overlap) {
482  ASSERT_HOST(seed != NULL && parts_overlap != NULL);
483  if (!IsTextOrEquationType(seed->type())) {
484  return;
485  }
487  const TBOX& seed_box(seed->bounding_box());
488  const int kRadNeighborCells = 30;
489  search.StartRadSearch((seed_box.left() + seed_box.right()) / 2,
490  (seed_box.top() + seed_box.bottom()) / 2,
491  kRadNeighborCells);
492  search.SetUniqueMode(true);
493 
494  // Search iteratively.
495  ColPartition *part;
497  const float kLargeOverlapTh = 0.95;
498  const float kEquXOverlap = 0.4, kEquYOverlap = 0.5;
499  while ((part = search.NextRadSearch()) != NULL) {
500  if (part == seed || !IsTextOrEquationType(part->type())) {
501  continue;
502  }
503  const TBOX& part_box(part->bounding_box());
504  bool merge = false;
505 
506  float x_overlap_fraction = part_box.x_overlap_fraction(seed_box),
507  y_overlap_fraction = part_box.y_overlap_fraction(seed_box);
508 
509  // If part is large overlapped with seed, then set merge to true.
510  if (x_overlap_fraction >= kLargeOverlapTh &&
511  y_overlap_fraction >= kLargeOverlapTh) {
512  merge = true;
513  } else if (seed->type() == PT_EQUATION &&
514  IsTextOrEquationType(part->type())) {
515  if ((x_overlap_fraction > kEquXOverlap && y_overlap_fraction > 0.0) ||
516  (x_overlap_fraction > 0.0 && y_overlap_fraction > kEquYOverlap)) {
517  merge = true;
518  }
519  }
520 
521  if (merge) { // Remove the part from search and put it into parts.
522  search.RemoveBBox();
523  parts_overlap->push_back(part);
524  }
525  }
526 }
527 
529  ASSERT_HOST(part);
530 
531  // Before insert part back into part_grid_, we will need re-compute some
532  // of its attributes such as first_column_, last_column_. However, we still
533  // want to preserve its type.
534  BlobTextFlowType flow_type = part->flow();
535  PolyBlockType part_type = part->type();
536  BlobRegionType blob_type = part->blob_type();
537 
538  // Call SetPartitionType to re-compute the attributes of part.
539  const TBOX& part_box(part->bounding_box());
540  int grid_x, grid_y;
542  part_box.left(), part_box.bottom(), &grid_x, &grid_y);
544 
545  // Reset the types back.
546  part->set_type(part_type);
547  part->set_blob_type(blob_type);
548  part->set_flow(flow_type);
549  part->SetBlobTypes();
550 
551  // Insert into part_grid_.
552  part_grid_->InsertBBox(true, true, part);
553 }
554 
557  ColPartition *part = NULL;
558  gsearch.StartFullSearch();
559 
560  GenericVector<ColPartition*> seeds1, seeds2;
561  // The left coordinates of indented text partitions.
562  GenericVector<int> indented_texts_left;
563  // The foreground density of text partitions.
564  GenericVector<float> texts_foreground_density;
565  while ((part = gsearch.NextFullSearch()) != NULL) {
566  if (!IsTextOrEquationType(part->type())) {
567  continue;
568  }
570  bool blobs_check = CheckSeedBlobsCount(part);
571  const int kTextBlobsTh = 20;
572 
574  blobs_check) {
575  // Passed high density threshold test, save into seeds1.
576  seeds1.push_back(part);
577  } else {
578  IndentType indent = IsIndented(part);
579  if (IsLeftIndented(indent) && blobs_check &&
581  // Passed low density threshold test and is indented, save into seeds2.
582  seeds2.push_back(part);
583  } else if (!IsRightIndented(indent) &&
584  part->boxes_count() > kTextBlobsTh) {
585  // This is likely to be a text part, save the features.
586  const TBOX&box = part->bounding_box();
587  if (IsLeftIndented(indent)) {
588  indented_texts_left.push_back(box.left());
589  }
590  texts_foreground_density.push_back(ComputeForegroundDensity(box));
591  }
592  }
593  }
594 
595  // Sort the features collected from text regions.
596  indented_texts_left.sort();
597  texts_foreground_density.sort();
598  float foreground_density_th = 0.15; // Default value.
599  if (!texts_foreground_density.empty()) {
600  // Use the median of the texts_foreground_density.
601  foreground_density_th = 0.8 * texts_foreground_density[
602  texts_foreground_density.size() / 2];
603  }
604 
605  for (int i = 0; i < seeds1.size(); ++i) {
606  const TBOX& box = seeds1[i]->bounding_box();
607  if (CheckSeedFgDensity(foreground_density_th, seeds1[i]) &&
608  !(IsLeftIndented(IsIndented(seeds1[i])) &&
609  CountAlignment(indented_texts_left, box.left()) >=
611  // Mark as PT_EQUATION type.
612  seeds1[i]->set_type(PT_EQUATION);
613  cp_seeds_.push_back(seeds1[i]);
614  } else { // Mark as PT_INLINE_EQUATION type.
615  seeds1[i]->set_type(PT_INLINE_EQUATION);
616  }
617  }
618 
619  for (int i = 0; i < seeds2.size(); ++i) {
620  if (CheckForSeed2(indented_texts_left, foreground_density_th, seeds2[i])) {
621  seeds2[i]->set_type(PT_EQUATION);
622  cp_seeds_.push_back(seeds2[i]);
623  }
624  }
625 }
626 
628 #if LIBLEPT_MINOR_VERSION < 69 && LIBLEPT_MAJOR_VERSION <= 1
629  // This will disable the detector because no seed will be identified.
630  return 1.0f;
631 #else
632  Pix *pix_bi = lang_tesseract_->pix_binary();
633  int pix_height = pixGetHeight(pix_bi);
634  Box* box = boxCreate(tbox.left(), pix_height - tbox.top(),
635  tbox.width(), tbox.height());
636  Pix *pix_sub = pixClipRectangle(pix_bi, box, NULL);
637  l_float32 fract;
638  pixForegroundFraction(pix_sub, &fract);
639  pixDestroy(&pix_sub);
640  boxDestroy(&box);
641 
642  return fract;
643 #endif
644 }
645 
646 bool EquationDetect::CheckSeedFgDensity(const float density_th,
647  ColPartition* part) {
648  ASSERT_HOST(part);
649 
650  // Split part horizontall, and check for each sub part.
651  GenericVector<TBOX> sub_boxes;
652  SplitCPHorLite(part, &sub_boxes);
653  float parts_passed = 0.0;
654  for (int i = 0; i < sub_boxes.size(); ++i) {
655  float density = ComputeForegroundDensity(sub_boxes[i]);
656  if (density < density_th) {
657  parts_passed++;
658  }
659  }
660 
661  // If most sub parts passed, then we return true.
662  const float kSeedPartRatioTh = 0.3;
663  bool retval = (parts_passed / sub_boxes.size() >= kSeedPartRatioTh);
664 
665  return retval;
666 }
667 
669  GenericVector<ColPartition*>* parts_splitted) {
670  ASSERT_HOST(part && parts_splitted);
671  if (part->median_width() == 0 || part->boxes_count() == 0) {
672  return;
673  }
674 
675  // Make a copy of part, and reset parts_splitted.
676  ColPartition* right_part = part->CopyButDontOwnBlobs();
677  parts_splitted->delete_data_pointers();
678  parts_splitted->clear();
679 
680  const double kThreshold = part->median_width() * 3.0;
681  bool found_split = true;
682  while (found_split) {
683  found_split = false;
684  BLOBNBOX_C_IT box_it(right_part->boxes());
685  // Blobs are sorted left side first. If blobs overlap,
686  // the previous blob may have a "more right" right side.
687  // Account for this by always keeping the largest "right"
688  // so far.
689  int previous_right = MIN_INT32;
690 
691  // Look for the next split in the partition.
692  for (box_it.mark_cycle_pt(); !box_it.cycled_list(); box_it.forward()) {
693  const TBOX& box = box_it.data()->bounding_box();
694  if (previous_right != MIN_INT32 &&
695  box.left() - previous_right > kThreshold) {
696  // We have a split position. Split the partition in two pieces.
697  // Insert the left piece in the grid and keep processing the right.
698  int mid_x = (box.left() + previous_right) / 2;
699  ColPartition* left_part = right_part;
700  right_part = left_part->SplitAt(mid_x);
701 
702  parts_splitted->push_back(left_part);
703  left_part->ComputeSpecialBlobsDensity();
704  found_split = true;
705  break;
706  }
707 
708  // The right side of the previous blobs.
709  previous_right = MAX(previous_right, box.right());
710  }
711  }
712 
713  // Add the last piece.
714  right_part->ComputeSpecialBlobsDensity();
715  parts_splitted->push_back(right_part);
716 }
717 
719  GenericVector<TBOX>* splitted_boxes) {
720  ASSERT_HOST(part && splitted_boxes);
721  splitted_boxes->clear();
722  if (part->median_width() == 0) {
723  return;
724  }
725 
726  const double kThreshold = part->median_width() * 3.0;
727 
728  // Blobs are sorted left side first. If blobs overlap,
729  // the previous blob may have a "more right" right side.
730  // Account for this by always keeping the largest "right"
731  // so far.
732  TBOX union_box;
733  int previous_right = MIN_INT32;
734  BLOBNBOX_C_IT box_it(part->boxes());
735  for (box_it.mark_cycle_pt(); !box_it.cycled_list(); box_it.forward()) {
736  const TBOX& box = box_it.data()->bounding_box();
737  if (previous_right != MIN_INT32 &&
738  box.left() - previous_right > kThreshold) {
739  // We have a split position.
740  splitted_boxes->push_back(union_box);
741  previous_right = MIN_INT32;
742  }
743  if (previous_right == MIN_INT32) {
744  union_box = box;
745  } else {
746  union_box += box;
747  }
748  // The right side of the previous blobs.
749  previous_right = MAX(previous_right, box.right());
750  }
751 
752  // Add the last piece.
753  if (previous_right != MIN_INT32) {
754  splitted_boxes->push_back(union_box);
755  }
756 }
757 
759  const GenericVector<int>& indented_texts_left,
760  const float foreground_density_th,
761  ColPartition* part) {
762  ASSERT_HOST(part);
763  const TBOX& box = part->bounding_box();
764 
765  // Check if it is aligned with any indented_texts_left.
766  if (!indented_texts_left.empty() &&
767  CountAlignment(indented_texts_left, box.left()) >=
769  return false;
770  }
771 
772  // Check the foreground density.
773  if (ComputeForegroundDensity(box) > foreground_density_th) {
774  return false;
775  }
776 
777  return true;
778 }
779 
781  const GenericVector<int>& sorted_vec, const int val) const {
782  if (sorted_vec.empty()) {
783  return 0;
784  }
785  const int kDistTh = static_cast<int>(roundf(0.03 * resolution_));
786  int pos = sorted_vec.binary_search(val), count = 0;
787 
788  // Search left side.
789  int index = pos;
790  while (index >= 0 && abs(val - sorted_vec[index--]) < kDistTh) {
791  count++;
792  }
793 
794  // Search right side.
795  index = pos + 1;
796  while (index < sorted_vec.size() && sorted_vec[index++] - val < kDistTh) {
797  count++;
798  }
799 
800  return count;
801 }
802 
806  int textparts_linespacing = EstimateTextPartLineSpacing();
807  IdentifyInlinePartsVertical(true, textparts_linespacing);
808  IdentifyInlinePartsVertical(false, textparts_linespacing);
809 }
810 
813  ColPartition *part = NULL;
814  gsearch.StartFullSearch();
815  if (cps_super_bbox_) {
816  delete cps_super_bbox_;
817  }
818  cps_super_bbox_ = new TBOX();
819  while ((part = gsearch.NextFullSearch()) != NULL) {
820  (*cps_super_bbox_) += part->bounding_box();
821  }
822 }
823 
827  const int kMarginDiffTh = IntCastRounded(
829  const int kGapTh = static_cast<int>(roundf(
832  search.SetUniqueMode(true);
833  // The center x coordinate of the cp_super_bbox_.
834  int cps_cx = cps_super_bbox_->left() + cps_super_bbox_->width() / 2;
835  for (int i = 0; i < cp_seeds_.size(); ++i) {
836  ColPartition* part = cp_seeds_[i];
837  const TBOX& part_box(part->bounding_box());
838  int left_margin = part_box.left() - cps_super_bbox_->left(),
839  right_margin = cps_super_bbox_->right() - part_box.right();
840  bool right_to_left;
841  if (left_margin + kMarginDiffTh < right_margin &&
842  left_margin < kMarginDiffTh) {
843  // part is left aligned, so we search if it has any right neighbor.
844  search.StartSideSearch(
845  part_box.right(), part_box.top(), part_box.bottom());
846  right_to_left = false;
847  } else if (left_margin > cps_cx) {
848  // part locates on the right half on image, so search if it has any left
849  // neighbor.
850  search.StartSideSearch(
851  part_box.left(), part_box.top(), part_box.bottom());
852  right_to_left = true;
853  } else { // part is not an inline equation.
854  new_seeds.push_back(part);
855  continue;
856  }
857  ColPartition* neighbor = NULL;
858  bool side_neighbor_found = false;
859  while ((neighbor = search.NextSideSearch(right_to_left)) != NULL) {
860  const TBOX& neighbor_box(neighbor->bounding_box());
861  if (!IsTextOrEquationType(neighbor->type()) ||
862  part_box.x_gap(neighbor_box) > kGapTh ||
863  !part_box.major_y_overlap(neighbor_box) ||
864  part_box.major_x_overlap(neighbor_box)) {
865  continue;
866  }
867  // We have found one. Set the side_neighbor_found flag.
868  side_neighbor_found = true;
869  break;
870  }
871  if (!side_neighbor_found) { // Mark part as PT_INLINE_EQUATION.
873  } else {
874  // Check the geometric feature of neighbor.
875  const TBOX& neighbor_box(neighbor->bounding_box());
876  if (neighbor_box.width() > part_box.width() &&
877  neighbor->type() != PT_EQUATION) { // Mark as PT_INLINE_EQUATION.
879  } else { // part is not an inline equation type.
880  new_seeds.push_back(part);
881  }
882  }
883  }
884 
885  // Reset the cp_seeds_ using the new_seeds.
886  cp_seeds_ = new_seeds;
887 }
888 
891 
892  // Get the y gap between text partitions;
893  ColPartition *current = NULL, *prev = NULL;
894  gsearch.StartFullSearch();
895  GenericVector<int> ygaps;
896  while ((current = gsearch.NextFullSearch()) != NULL) {
897  if (!PTIsTextType(current->type())) {
898  continue;
899  }
900  if (prev != NULL) {
901  const TBOX &current_box = current->bounding_box();
902  const TBOX &prev_box = prev->bounding_box();
903  // prev and current should be x major overlap and non y overlap.
904  if (current_box.major_x_overlap(prev_box) &&
905  !current_box.y_overlap(prev_box)) {
906  int gap = current_box.y_gap(prev_box);
907  if (gap < MIN(current_box.height(), prev_box.height())) {
908  // The gap should be smaller than the height of the bounding boxes.
909  ygaps.push_back(gap);
910  }
911  }
912  }
913  prev = current;
914  }
915 
916  if (ygaps.size() < 8) { // We do not have enough data.
917  return -1;
918  }
919 
920  // Compute the line spacing from ygaps: use the mean of the first half.
921  ygaps.sort();
922  int spacing = 0, count;
923  for (count = 0; count < ygaps.size() / 2; count++) {
924  spacing += ygaps[count];
925  }
926  return spacing / count;
927 }
928 
930  const bool top_to_bottom, const int textparts_linespacing) {
931  if (cp_seeds_.empty()) {
932  return;
933  }
934 
935  // Sort cp_seeds_.
936  if (top_to_bottom) { // From top to bottom.
937  cp_seeds_.sort(&SortCPByTopReverse);
938  } else { // From bottom to top.
939  cp_seeds_.sort(&SortCPByBottom);
940  }
941 
943  for (int i = 0; i < cp_seeds_.size(); ++i) {
944  ColPartition* part = cp_seeds_[i];
945  // If we sort cp_seeds_ from top to bottom, then for each cp_seeds_, we look
946  // for its top neighbors, so that if two/more inline regions are connected
947  // to each other, then we will identify the top one, and then use it to
948  // identify the bottom one.
949  if (IsInline(!top_to_bottom, textparts_linespacing, part)) {
951  } else {
952  new_seeds.push_back(part);
953  }
954  }
955  cp_seeds_ = new_seeds;
956 }
957 
958 bool EquationDetect::IsInline(const bool search_bottom,
959  const int textparts_linespacing,
960  ColPartition* part) {
961  ASSERT_HOST(part != NULL);
962  // Look for its nearest vertical neighbor that hardly overlaps in y but
963  // largely overlaps in x.
965  ColPartition *neighbor = NULL;
966  const TBOX& part_box(part->bounding_box());
967  const float kYGapRatioTh = 1.0;
968 
969  if (search_bottom) {
970  search.StartVerticalSearch(part_box.left(), part_box.right(),
971  part_box.bottom());
972  } else {
973  search.StartVerticalSearch(part_box.left(), part_box.right(),
974  part_box.top());
975  }
976  search.SetUniqueMode(true);
977  while ((neighbor = search.NextVerticalSearch(search_bottom)) != NULL) {
978  const TBOX& neighbor_box(neighbor->bounding_box());
979  if (part_box.y_gap(neighbor_box) > kYGapRatioTh *
980  MIN(part_box.height(), neighbor_box.height())) {
981  // Finished searching.
982  break;
983  }
984  if (!PTIsTextType(neighbor->type())) {
985  continue;
986  }
987 
988  // Check if neighbor and part is inline similar.
989  const float kHeightRatioTh = 0.5;
990  const int kYGapTh = textparts_linespacing > 0 ?
991  textparts_linespacing + static_cast<int>(roundf(0.02 * resolution_)):
992  static_cast<int>(roundf(0.05 * resolution_)); // Default value.
993  if (part_box.x_overlap(neighbor_box) && // Location feature.
994  part_box.y_gap(neighbor_box) <= kYGapTh && // Line spacing.
995  // Geo feature.
996  static_cast<float>(MIN(part_box.height(), neighbor_box.height())) /
997  MAX(part_box.height(), neighbor_box.height()) > kHeightRatioTh) {
998  return true;
999  }
1000  }
1001 
1002  return false;
1003 }
1004 
1006  if (!part) {
1007  return false;
1008  }
1009  const int kSeedMathBlobsCount = 2;
1010  const int kSeedMathDigitBlobsCount = 5;
1011 
1012  int blobs = part->boxes_count(),
1013  math_blobs = part->SpecialBlobsCount(BSTT_MATH),
1014  digit_blobs = part->SpecialBlobsCount(BSTT_DIGIT);
1015  if (blobs < kSeedBlobsCountTh || math_blobs <= kSeedMathBlobsCount ||
1016  math_blobs + digit_blobs <= kSeedMathDigitBlobsCount) {
1017  return false;
1018  }
1019 
1020  return true;
1021 }
1022 
1024  const float math_density_high,
1025  const float math_density_low,
1026  const ColPartition* part) const {
1027  ASSERT_HOST(part);
1028  float math_digit_density = part->SpecialBlobsDensity(BSTT_MATH)
1030  float italic_density = part->SpecialBlobsDensity(BSTT_ITALIC);
1031  if (math_digit_density > math_density_high) {
1032  return true;
1033  }
1034  if (math_digit_density + italic_density > kMathItalicDensityTh &&
1035  math_digit_density > math_density_low) {
1036  return true;
1037  }
1038 
1039  return false;
1040 }
1041 
1043  ASSERT_HOST(part);
1044 
1046  ColPartition *neighbor = NULL;
1047  const TBOX& part_box(part->bounding_box());
1048  const int kXGapTh = static_cast<int>(roundf(0.5 * resolution_));
1049  const int kRadiusTh = static_cast<int>(roundf(3.0 * resolution_));
1050  const int kYGapTh = static_cast<int>(roundf(0.5 * resolution_));
1051 
1052  // Here we use a simple approximation algorithm: from the center of part, We
1053  // perform the radius search, and check if we can find a neighboring parition
1054  // that locates on the top/bottom left of part.
1055  search.StartRadSearch((part_box.left() + part_box.right()) / 2,
1056  (part_box.top() + part_box.bottom()) / 2, kRadiusTh);
1057  search.SetUniqueMode(true);
1058  bool left_indented = false, right_indented = false;
1059  while ((neighbor = search.NextRadSearch()) != NULL &&
1060  (!left_indented || !right_indented)) {
1061  if (neighbor == part) {
1062  continue;
1063  }
1064  const TBOX& neighbor_box(neighbor->bounding_box());
1065 
1066  if (part_box.major_y_overlap(neighbor_box) &&
1067  part_box.x_gap(neighbor_box) < kXGapTh) {
1068  // When this happens, it is likely part is a fragment of an
1069  // over-segmented colpartition. So we return false.
1070  return NO_INDENT;
1071  }
1072 
1073  if (!IsTextOrEquationType(neighbor->type())) {
1074  continue;
1075  }
1076 
1077  // The neighbor should be above/below part, and overlap in x direction.
1078  if (!part_box.x_overlap(neighbor_box) || part_box.y_overlap(neighbor_box)) {
1079  continue;
1080  }
1081 
1082  if (part_box.y_gap(neighbor_box) < kYGapTh) {
1083  int left_gap = part_box.left() - neighbor_box.left();
1084  int right_gap = neighbor_box.right() - part_box.right();
1085  if (left_gap > kXGapTh) {
1086  left_indented = true;
1087  }
1088  if (right_gap > kXGapTh) {
1089  right_indented = true;
1090  }
1091  }
1092  }
1093 
1094  if (left_indented && right_indented) {
1095  return BOTH_INDENT;
1096  }
1097  if (left_indented) {
1098  return LEFT_INDENT;
1099  }
1100  if (right_indented) {
1101  return RIGHT_INDENT;
1102  }
1103  return NO_INDENT;
1104 }
1105 
1107  if (seed == NULL || // This seed has been absorbed by other seeds.
1108  seed->IsVerticalType()) { // We skip vertical type right now.
1109  return false;
1110  }
1111 
1112  // Expand in four directions.
1113  GenericVector<ColPartition*> parts_to_merge;
1114  ExpandSeedHorizontal(true, seed, &parts_to_merge);
1115  ExpandSeedHorizontal(false, seed, &parts_to_merge);
1116  ExpandSeedVertical(true, seed, &parts_to_merge);
1117  ExpandSeedVertical(false, seed, &parts_to_merge);
1118  SearchByOverlap(seed, &parts_to_merge);
1119 
1120  if (parts_to_merge.empty()) { // We don't find any partition to merge.
1121  return false;
1122  }
1123 
1124  // Merge all partitions in parts_to_merge with seed. We first remove seed
1125  // from part_grid_ as its bounding box is going to expand. Then we add it
1126  // back after it aborbs all parts_to_merge parititions.
1127  part_grid_->RemoveBBox(seed);
1128  for (int i = 0; i < parts_to_merge.size(); ++i) {
1129  ColPartition* part = parts_to_merge[i];
1130  if (part->type() == PT_EQUATION) {
1131  // If part is in cp_seeds_, then we mark it as NULL so that we won't
1132  // process it again.
1133  for (int j = 0; j < cp_seeds_.size(); ++j) {
1134  if (part == cp_seeds_[j]) {
1135  cp_seeds_[j] = NULL;
1136  break;
1137  }
1138  }
1139  }
1140 
1141  // part has already been removed from part_grid_ in function
1142  // ExpandSeedHorizontal/ExpandSeedVertical.
1143  seed->Absorb(part, NULL);
1144  }
1145 
1146  return true;
1147 }
1148 
1150  const bool search_left,
1151  ColPartition* seed,
1152  GenericVector<ColPartition*>* parts_to_merge) {
1153  ASSERT_HOST(seed != NULL && parts_to_merge != NULL);
1154  const float kYOverlapTh = 0.6;
1155  const int kXGapTh = static_cast<int>(roundf(0.2 * resolution_));
1156 
1158  const TBOX& seed_box(seed->bounding_box());
1159  int x = search_left ? seed_box.left() : seed_box.right();
1160  search.StartSideSearch(x, seed_box.bottom(), seed_box.top());
1161  search.SetUniqueMode(true);
1162 
1163  // Search iteratively.
1164  ColPartition *part = NULL;
1165  while ((part = search.NextSideSearch(search_left)) != NULL) {
1166  if (part == seed) {
1167  continue;
1168  }
1169  const TBOX& part_box(part->bounding_box());
1170  if (part_box.x_gap(seed_box) > kXGapTh) { // Out of scope.
1171  break;
1172  }
1173 
1174  // Check part location.
1175  if ((part_box.left() >= seed_box.left() && search_left) ||
1176  (part_box.right() <= seed_box.right() && !search_left)) {
1177  continue;
1178  }
1179 
1180  if (part->type() != PT_EQUATION) { // Non-equation type.
1181  // Skip PT_LINLINE_EQUATION and non text type.
1182  if (part->type() == PT_INLINE_EQUATION ||
1183  (!IsTextOrEquationType(part->type()) &&
1184  part->blob_type() != BRT_HLINE)) {
1185  continue;
1186  }
1187  // For other types, it should be the near small neighbor of seed.
1188  if (!IsNearSmallNeighbor(seed_box, part_box) ||
1189  !CheckSeedNeighborDensity(part)) {
1190  continue;
1191  }
1192  } else { // Equation type, check the y overlap.
1193  if (part_box.y_overlap_fraction(seed_box) < kYOverlapTh &&
1194  seed_box.y_overlap_fraction(part_box) < kYOverlapTh) {
1195  continue;
1196  }
1197  }
1198 
1199  // Passed the check, delete it from search and add into parts_to_merge.
1200  search.RemoveBBox();
1201  parts_to_merge->push_back(part);
1202  }
1203 }
1204 
1206  const bool search_bottom,
1207  ColPartition* seed,
1208  GenericVector<ColPartition*>* parts_to_merge) {
1209  ASSERT_HOST(seed != NULL && parts_to_merge != NULL &&
1210  cps_super_bbox_ != NULL);
1211  const float kXOverlapTh = 0.4;
1212  const int kYGapTh = static_cast<int>(roundf(0.2 * resolution_));
1213 
1215  const TBOX& seed_box(seed->bounding_box());
1216  int y = search_bottom ? seed_box.bottom() : seed_box.top();
1217  search.StartVerticalSearch(
1219  search.SetUniqueMode(true);
1220 
1221  // Search iteratively.
1222  ColPartition *part = NULL;
1224  int skipped_min_top = INT_MAX, skipped_max_bottom = -1;
1225  while ((part = search.NextVerticalSearch(search_bottom)) != NULL) {
1226  if (part == seed) {
1227  continue;
1228  }
1229  const TBOX& part_box(part->bounding_box());
1230 
1231  if (part_box.y_gap(seed_box) > kYGapTh) { // Out of scope.
1232  break;
1233  }
1234 
1235  // Check part location.
1236  if ((part_box.bottom() >= seed_box.bottom() && search_bottom) ||
1237  (part_box.top() <= seed_box.top() && !search_bottom)) {
1238  continue;
1239  }
1240 
1241  bool skip_part = false;
1242  if (part->type() != PT_EQUATION) { // Non-equation type.
1243  // Skip PT_LINLINE_EQUATION and non text type.
1244  if (part->type() == PT_INLINE_EQUATION ||
1245  (!IsTextOrEquationType(part->type()) &&
1246  part->blob_type() != BRT_HLINE)) {
1247  skip_part = true;
1248  } else if (!IsNearSmallNeighbor(seed_box, part_box) ||
1249  !CheckSeedNeighborDensity(part)) {
1250  // For other types, it should be the near small neighbor of seed.
1251  skip_part = true;
1252  }
1253  } else { // Equation type, check the x overlap.
1254  if (part_box.x_overlap_fraction(seed_box) < kXOverlapTh &&
1255  seed_box.x_overlap_fraction(part_box) < kXOverlapTh) {
1256  skip_part = true;
1257  }
1258  }
1259  if (skip_part) {
1260  if (part->type() != PT_EQUATION) {
1261  if (skipped_min_top > part_box.top()) {
1262  skipped_min_top = part_box.top();
1263  }
1264  if (skipped_max_bottom < part_box.bottom()) {
1265  skipped_max_bottom = part_box.bottom();
1266  }
1267  }
1268  } else {
1269  parts.push_back(part);
1270  }
1271  }
1272 
1273  // For every part in parts, we need verify it is not above skipped_min_top
1274  // when search top, or not below skipped_max_bottom when search bottom. I.e.,
1275  // we will skip a part if it looks like:
1276  // search bottom | search top
1277  // seed: ****************** | part: **********
1278  // skipped: xxx | skipped: xxx
1279  // part: ********** | seed: ***********
1280  for (int i = 0; i < parts.size(); i++) {
1281  const TBOX& part_box(parts[i]->bounding_box());
1282  if ((search_bottom && part_box.top() <= skipped_max_bottom) ||
1283  (!search_bottom && part_box.bottom() >= skipped_min_top)) {
1284  continue;
1285  }
1286  // Add parts[i] into parts_to_merge, and delete it from part_grid_.
1287  parts_to_merge->push_back(parts[i]);
1288  part_grid_->RemoveBBox(parts[i]);
1289  }
1290 }
1291 
1293  const TBOX& part_box) const {
1294  const int kXGapTh = static_cast<int>(roundf(0.25 * resolution_));
1295  const int kYGapTh = static_cast<int>(roundf(0.05 * resolution_));
1296 
1297  // Check geometric feature.
1298  if (part_box.height() > seed_box.height() ||
1299  part_box.width() > seed_box.width()) {
1300  return false;
1301  }
1302 
1303  // Check overlap and distance.
1304  if ((!part_box.major_x_overlap(seed_box) ||
1305  part_box.y_gap(seed_box) > kYGapTh) &&
1306  (!part_box.major_y_overlap(seed_box) ||
1307  part_box.x_gap(seed_box) > kXGapTh)) {
1308  return false;
1309  }
1310 
1311  return true;
1312 }
1313 
1315  ASSERT_HOST(part);
1316  if (part->boxes_count() < kSeedBlobsCountTh) {
1317  // Too few blobs, skip the check.
1318  return true;
1319  }
1320 
1321  // We check the math blobs density and the unclear blobs density.
1322  if (part->SpecialBlobsDensity(BSTT_MATH) +
1325  return true;
1326  }
1327 
1328  return false;
1329 }
1330 
1332  // Iterate over part_grid_, and find all parts that are text type but not
1333  // equation type.
1334  ColPartition *part = NULL;
1335  GenericVector<ColPartition*> text_parts;
1337  gsearch.StartFullSearch();
1338  while ((part = gsearch.NextFullSearch()) != NULL) {
1339  if (part->type() == PT_FLOWING_TEXT || part->type() == PT_HEADING_TEXT) {
1340  text_parts.push_back(part);
1341  }
1342  }
1343  if (text_parts.empty()) {
1344  return;
1345  }
1346 
1347  // Compute the medium height of the text_parts.
1348  text_parts.sort(&SortCPByHeight);
1349  const TBOX& text_box = text_parts[text_parts.size() / 2]->bounding_box();
1350  int med_height = text_box.height();
1351  if (text_parts.size() % 2 == 0 && text_parts.size() > 1) {
1352  const TBOX& text_box =
1353  text_parts[text_parts.size() / 2 - 1]->bounding_box();
1354  med_height = static_cast<int>(roundf(
1355  0.5 * (text_box.height() + med_height)));
1356  }
1357 
1358  // Iterate every text_parts and check if it is a math block satellite.
1359  for (int i = 0; i < text_parts.size(); ++i) {
1360  const TBOX& text_box(text_parts[i]->bounding_box());
1361  if (text_box.height() > med_height) {
1362  continue;
1363  }
1364  GenericVector<ColPartition*> math_blocks;
1365  if (!IsMathBlockSatellite(text_parts[i], &math_blocks)) {
1366  continue;
1367  }
1368 
1369  // Found. merge text_parts[i] with math_blocks.
1370  part_grid_->RemoveBBox(text_parts[i]);
1371  text_parts[i]->set_type(PT_EQUATION);
1372  for (int j = 0; j < math_blocks.size(); ++j) {
1373  part_grid_->RemoveBBox(math_blocks[j]);
1374  text_parts[i]->Absorb(math_blocks[j], NULL);
1375  }
1376  InsertPartAfterAbsorb(text_parts[i]);
1377  }
1378 }
1379 
1381  ColPartition* part, GenericVector<ColPartition*>* math_blocks) {
1382  ASSERT_HOST(part != NULL && math_blocks != NULL);
1383  math_blocks->clear();
1384  const TBOX& part_box(part->bounding_box());
1385  // Find the top/bottom nearest neighbor of part.
1386  ColPartition *neighbors[2];
1387  int y_gaps[2] = {INT_MAX, INT_MAX};
1388  // The horizontal boundary of the neighbors.
1389  int neighbors_left = INT_MAX, neighbors_right = 0;
1390  for (int i = 0; i < 2; ++i) {
1391  neighbors[i] = SearchNNVertical(i != 0, part);
1392  if (neighbors[i]) {
1393  const TBOX& neighbor_box = neighbors[i]->bounding_box();
1394  y_gaps[i] = neighbor_box.y_gap(part_box);
1395  if (neighbor_box.left() < neighbors_left) {
1396  neighbors_left = neighbor_box.left();
1397  }
1398  if (neighbor_box.right() > neighbors_right) {
1399  neighbors_right = neighbor_box.right();
1400  }
1401  }
1402  }
1403  if (neighbors[0] == neighbors[1]) {
1404  // This happens when part is inside neighbor.
1405  neighbors[1] = NULL;
1406  y_gaps[1] = INT_MAX;
1407  }
1408 
1409  // Check if part is within [neighbors_left, neighbors_right].
1410  if (part_box.left() < neighbors_left || part_box.right() > neighbors_right) {
1411  return false;
1412  }
1413 
1414  // Get the index of the near one in neighbors.
1415  int index = y_gaps[0] < y_gaps[1] ? 0 : 1;
1416 
1417  // Check the near one.
1418  if (IsNearMathNeighbor(y_gaps[index], neighbors[index])) {
1419  math_blocks->push_back(neighbors[index]);
1420  } else {
1421  // If the near one failed the check, then we skip checking the far one.
1422  return false;
1423  }
1424 
1425  // Check the far one.
1426  index = 1 - index;
1427  if (IsNearMathNeighbor(y_gaps[index], neighbors[index])) {
1428  math_blocks->push_back(neighbors[index]);
1429  }
1430 
1431  return true;
1432 }
1433 
1435  const bool search_bottom, const ColPartition* part) {
1436  ASSERT_HOST(part);
1437  ColPartition *nearest_neighbor = NULL, *neighbor = NULL;
1438  const int kYGapTh = static_cast<int>(roundf(resolution_ * 0.5));
1439 
1441  search.SetUniqueMode(true);
1442  const TBOX& part_box(part->bounding_box());
1443  int y = search_bottom ? part_box.bottom() : part_box.top();
1444  search.StartVerticalSearch(part_box.left(), part_box.right(), y);
1445  int min_y_gap = INT_MAX;
1446  while ((neighbor = search.NextVerticalSearch(search_bottom)) != NULL) {
1447  if (neighbor == part || !IsTextOrEquationType(neighbor->type())) {
1448  continue;
1449  }
1450  const TBOX& neighbor_box(neighbor->bounding_box());
1451  int y_gap = neighbor_box.y_gap(part_box);
1452  if (y_gap > kYGapTh) { // Out of scope.
1453  break;
1454  }
1455  if (!neighbor_box.major_x_overlap(part_box) ||
1456  (search_bottom && neighbor_box.bottom() > part_box.bottom()) ||
1457  (!search_bottom && neighbor_box.top() < part_box.top())) {
1458  continue;
1459  }
1460  if (y_gap < min_y_gap) {
1461  min_y_gap = y_gap;
1462  nearest_neighbor = neighbor;
1463  }
1464  }
1465 
1466  return nearest_neighbor;
1467 }
1468 
1470  const int y_gap, const ColPartition *neighbor) const {
1471  if (!neighbor) {
1472  return false;
1473  }
1474  const int kYGapTh = static_cast<int>(roundf(resolution_ * 0.1));
1475  return neighbor->type() == PT_EQUATION && y_gap <= kYGapTh;
1476 }
1477 
1479  STRING* image_name) const {
1480  ASSERT_HOST(image_name && name);
1481  char page[50];
1482  snprintf(page, sizeof(page), "%04d", page_count_);
1483  *image_name = STRING(lang_tesseract_->imagebasename) + page + name + ".tif";
1484 }
1485 
1486 void EquationDetect::PaintSpecialTexts(const STRING& outfile) const {
1487  Pix *pix = NULL, *pixBi = lang_tesseract_->pix_binary();
1488  pix = pixConvertTo32(pixBi);
1490  ColPartition* part = NULL;
1491  gsearch.StartFullSearch();
1492  while ((part = gsearch.NextFullSearch()) != NULL) {
1493  BLOBNBOX_C_IT blob_it(part->boxes());
1494  for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
1495  RenderSpecialText(pix, blob_it.data());
1496  }
1497  }
1498 
1499  pixWrite(outfile.string(), pix, IFF_TIFF_LZW);
1500  pixDestroy(&pix);
1501 }
1502 
1503 void EquationDetect::PaintColParts(const STRING& outfile) const {
1504  Pix *pix = pixConvertTo32(lang_tesseract_->BestPix());
1506  gsearch.StartFullSearch();
1507  ColPartition* part = NULL;
1508  while ((part = gsearch.NextFullSearch()) != NULL) {
1509  const TBOX& tbox = part->bounding_box();
1510  Box *box = boxCreate(tbox.left(), pixGetHeight(pix) - tbox.top(),
1511  tbox.width(), tbox.height());
1512  if (part->type() == PT_EQUATION) {
1513  pixRenderBoxArb(pix, box, 5, 255, 0, 0);
1514  } else if (part->type() == PT_INLINE_EQUATION) {
1515  pixRenderBoxArb(pix, box, 5, 0, 255, 0);
1516  } else {
1517  pixRenderBoxArb(pix, box, 5, 0, 0, 255);
1518  }
1519  boxDestroy(&box);
1520  }
1521 
1522  pixWrite(outfile.string(), pix, IFF_TIFF_LZW);
1523  pixDestroy(&pix);
1524 }
1525 
1527  ASSERT_HOST(part);
1528  TBOX box(part->bounding_box());
1529  int h = pixGetHeight(lang_tesseract_->BestPix());
1530  tprintf("Printing special blobs density values for ColParition (t=%d,b=%d) ",
1531  h - box.top(), h - box.bottom());
1532  box.print();
1533  tprintf("blobs count = %d, density = ", part->boxes_count());
1534  for (int i = 0; i < BSTT_COUNT; ++i) {
1535  BlobSpecialTextType type = static_cast<BlobSpecialTextType>(i);
1536  tprintf("%d:%f ", i, part->SpecialBlobsDensity(type));
1537  }
1538  tprintf("\n");
1539 }
1540 
1541 }; // namespace tesseract
void set_type(PolyBlockType t)
Definition: colpartition.h:184
bool IsNearMathNeighbor(const int y_gap, const ColPartition *neighbor) const
bool joined_to_prev() const
Definition: blobbox.h:241
void IdentifyBlobsToSkip(ColPartition *part)
bool CheckSeedBlobsCount(ColPartition *part)
int count(LIST var_list)
Definition: oldlist.cpp:108
bool equationdetect_save_seed_image
int UNICHAR_ID
Definition: unichar.h:33
void SplitCPHorLite(ColPartition *part, GenericVector< TBOX > *splitted_boxes)
BOOL8 contains(const char c) const
Definition: strngs.cpp:192
const int kSeedBlobsCountTh
BBC * NextRadSearch()
Definition: bbgrid.h:716
bool IsVerticalType() const
Definition: colpartition.h:435
int boxes_count() const
Definition: colpartition.h:190
BlobRegionType blob_type() const
Definition: colpartition.h:148
inT16 bottom() const
Definition: rect.h:61
#define MAX(x, y)
Definition: ndminx.h:24
BlobSpecialTextType
Definition: blobbox.h:81
float certainty() const
Definition: ratngs.h:82
int y_gap(const TBOX &box) const
Definition: rect.h:225
const float kMathItalicDensityTh
int init_tesseract(const char *arg0, const char *textbase, const char *language, OcrEngineMode oem, char **configs, int configs_size, const GenericVector< STRING > *vars_vec, const GenericVector< STRING > *vars_values, bool set_only_init_params)
Definition: tessedit.cpp:290
float ComputeForegroundDensity(const TBOX &tbox)
Definition: strngs.h:44
void SetLangTesseract(Tesseract *lang_tesseract)
int CountAlignment(const GenericVector< int > &sorted_vec, const int val) const
LIST search(LIST list, void *key, int_compare is_equal)
Definition: oldlist.cpp:412
GenericVector< ColPartition * > cp_seeds_
UNICHAR_ID unichar_to_id(const char *const unichar_repr) const
Definition: unicharset.cpp:194
Pix * BestPix() const
void SplitCPHor(ColPartition *part, GenericVector< ColPartition * > *parts_splitted)
static TBLOB * PolygonalCopy(bool allow_detailed_fx, C_BLOB *src)
Definition: blobs.cpp:344
bool empty() const
Definition: genericvector.h:84
bool CheckSeedDensity(const float math_density_high, const float math_density_low, const ColPartition *part) const
BLOBNBOX_LIST large_blobs
Definition: blobbox.h:772
Pix * pix_binary() const
BlobRegionType
Definition: blobbox.h:57
void SearchByOverlap(ColPartition *seed, GenericVector< ColPartition * > *parts_overlap)
ColPartition * SplitAt(int split_x)
bool CheckSeedNeighborDensity(const ColPartition *part) const
TBOX bounding_box() const
Definition: blobs.cpp:482
inT16 height() const
Definition: rect.h:104
PolyBlockType
Definition: publictypes.h:41
bool ExpandSeed(ColPartition *seed)
BLOBNBOX_CLIST * boxes()
Definition: colpartition.h:187
void RepositionIterator()
Definition: bbgrid.h:895
const float kMathDigitDensityTh1
void AdaptiveClassifier(TBLOB *Blob, BLOB_CHOICE_LIST *Choices)
Definition: adaptmatch.cpp:185
bool equationdetect_save_spt_image
bool IsTextOrEquationType(PolyBlockType type)
int median_width() const
Definition: colpartition.h:142
BlobSpecialTextType special_text_type() const
Definition: blobbox.h:274
#define tprintf(...)
Definition: tprintf.h:31
bool get_isdigit(UNICHAR_ID unichar_id) const
Definition: unicharset.h:469
bool PTIsTextType(PolyBlockType type)
Definition: publictypes.h:70
BLOBNBOX_LIST blobs
Definition: blobbox.h:768
double x_overlap_fraction(const TBOX &box) const
Definition: rect.h:447
int size() const
Definition: genericvector.h:72
ColPartitionSet ** best_columns_
Definition: rect.h:30
const TBOX & bounding_box() const
Definition: blobbox.h:215
EquationDetect(const char *equ_datapath, const char *equ_language)
int FindEquationParts(ColPartitionGrid *part_grid, ColPartitionSet **best_columns)
ColPartition * SearchNNVertical(const bool search_bottom, const ColPartition *part)
void StartSideSearch(int x, int ymin, int ymax)
Definition: bbgrid.h:749
bool get_isalpha(UNICHAR_ID unichar_id) const
Definition: unicharset.h:448
void GridCoords(int x, int y, int *grid_x, int *grid_y) const
Definition: bbgrid.cpp:54
void Absorb(ColPartition *other, WidthCallback *cb)
inT16 fontinfo_id() const
Definition: ratngs.h:85
bool equationdetect_save_merged_image
BBC * NextFullSearch()
Definition: bbgrid.h:678
BlobTextFlowType flow() const
Definition: colpartition.h:154
inT16 right() const
Definition: rect.h:75
void set_special_text_type(BlobSpecialTextType new_type)
Definition: blobbox.h:277
STRING imagebasename
Definition: ccutil.h:66
inT32 length() const
Definition: strngs.cpp:196
const char * id_to_unichar(UNICHAR_ID id) const
Definition: unicharset.cpp:266
ColPartition * CopyButDontOwnBlobs()
bool major_y_overlap(const TBOX &box) const
Definition: rect.h:429
BlobTextFlowType
Definition: blobbox.h:99
#define MIN(x, y)
Definition: ndminx.h:28
void PaintSpecialTexts(const STRING &outfile) const
int classify_class_pruner_multiplier
Definition: classify.h:465
bool IsLeftIndented(const EquationDetect::IndentType type)
bool IsRightIndented(const EquationDetect::IndentType type)
bool IsInline(const bool search_bottom, const int textPartsLineSpacing, ColPartition *part)
inT16 left() const
Definition: rect.h:68
UNICHARSET unicharset
Definition: ccutil.h:70
void Normalize(const BLOCK *block, const FCOORD *rotation, const DENORM *predecessor, float x_origin, float y_origin, float x_scale, float y_scale, float final_xshift, float final_yshift, bool inverse, Pix *pix)
Definition: blobs.cpp:413
void InsertBBox(bool h_spread, bool v_spread, BBC *bbox)
Definition: bbgrid.h:489
UnicityTable< FontInfo > & get_fontinfo_table()
Definition: classify.h:345
void delete_data_pointers()
const float kUnclearDensityTh
void IdentifyInlinePartsVertical(const bool top_to_bottom, const int textPartsLineSpacing)
bool get_ispunctuation(UNICHAR_ID unichar_id) const
Definition: unicharset.h:476
BBC * NextVerticalSearch(bool top_to_bottom)
Definition: bbgrid.h:805
bool IsNearSmallNeighbor(const TBOX &seed_box, const TBOX &part_box) const
const TBOX & bounding_box() const
Definition: colpartition.h:109
void StartFullSearch()
Definition: bbgrid.h:668
BlobSpecialTextType EstimateTypeForUnichar(const UNICHARSET &unicharset, const UNICHAR_ID id) const
bool major_x_overlap(const TBOX &box) const
Definition: rect.h:402
bool bool_binary_search(const T &target) const
ColPartitionGrid * part_grid_
const int kBlnXHeight
Definition: normalis.h:28
void SetResolution(const int resolution)
PolyBlockType type() const
Definition: colpartition.h:181
int push_back(T object)
int source_resolution() const
IndentType IsIndented(ColPartition *part)
void ExpandSeedVertical(const bool search_bottom, ColPartition *seed, GenericVector< ColPartition * > *parts_to_merge)
inT16 top() const
Definition: rect.h:54
void set_blob_type(BlobRegionType t)
Definition: colpartition.h:151
int IntCastRounded(double x)
Definition: helpers.h:172
void set_flow(BlobTextFlowType f)
Definition: colpartition.h:157
void StartRadSearch(int x, int y, int max_radius)
Definition: bbgrid.h:701
const int kBlnBaselineOffset
Definition: normalis.h:29
int x_gap(const TBOX &box) const
Definition: rect.h:217
#define ASSERT_HOST(x)
Definition: errcode.h:84
const char * string() const
Definition: strngs.cpp:201
Definition: blobs.h:261
int classify_integer_matcher_multiplier
Definition: classify.h:469
float SpecialBlobsDensity(const BlobSpecialTextType type) const
void InsertPartAfterAbsorb(ColPartition *part)
bool IsMathBlockSatellite(ColPartition *part, GenericVector< ColPartition * > *math_blocks)
bool equationdetect_save_bi_image
#define MIN_INT32
Definition: host.h:128
void GetOutputTiffName(const char *name, STRING *image_name) const
void StartVerticalSearch(int xmin, int xmax, int y)
Definition: bbgrid.h:791
void PrintSpecialBlobsDensity(const ColPartition *part) const
void RemoveBBox(BBC *bbox)
Definition: bbgrid.h:536
void ExpandSeedHorizontal(const bool search_left, ColPartition *seed, GenericVector< ColPartition * > *parts_to_merge)
#define BOOL_VAR(name, val, comment)
Definition: params.h:280
inT16 width() const
Definition: rect.h:111
int SpecialBlobsCount(const BlobSpecialTextType type)
bool CheckForSeed2(const GenericVector< int > &indented_texts_left, const float foreground_density_th, ColPartition *part)
const int kLeftIndentAlignmentCountTh
BBC * NextSideSearch(bool right_to_left)
Definition: bbgrid.h:764
bool CheckSeedFgDensity(const float density_th, ColPartition *part)
void SetPartitionType(int resolution, ColPartitionSet *columns)
C_BLOB * cblob() const
Definition: blobbox.h:253
static void RenderSpecialText(Pix *pix, BLOBNBOX *blob)
const float kMathDigitDensityTh2
void PaintColParts(const STRING &outfile) const
void SetUniqueMode(bool mode)
Definition: bbgrid.h:254
int LabelSpecialText(TO_BLOCK *to_block)
UNICHAR_ID unichar_id() const
Definition: ratngs.h:76
int binary_search(const T &target) const
bool y_overlap(const TBOX &box) const
Definition: rect.h:418