Example #1
0
// Finds vertical lines in the given list of BLOBNBOXes. bleft and tright
// are the bounds of the image on which the input line_bblobs were found.
// The input line_bblobs list is const really.
// The output vertical_x and vertical_y are the total of all the vectors.
// The output list of TabVector makes no reference to the input BLOBNBOXes.
void LineFinder::FindLineVectors(const ICOORD& bleft, const ICOORD& tright,
                                 BLOBNBOX_LIST* line_bblobs,
                                 int* vertical_x, int* vertical_y,
                                 TabVector_LIST* vectors) {
  BLOBNBOX_IT bbox_it(line_bblobs);
  int b_count = 0;
  // Put all the blobs into the grid to find the lines, and move the blobs
  // to the output lists.
  AlignedBlob blob_grid(kLineFindGridSize, bleft, tright);
  for (bbox_it.mark_cycle_pt(); !bbox_it.cycled_list(); bbox_it.forward()) {
    BLOBNBOX* bblob = bbox_it.data();
    bblob->set_left_tab_type(TT_UNCONFIRMED);
    bblob->set_left_rule(bleft.x());
    bblob->set_right_rule(tright.x());
    bblob->set_left_crossing_rule(bleft.x());
    bblob->set_right_crossing_rule(tright.x());
    blob_grid.InsertBBox(false, true, bblob);
    ++b_count;
  }
  if (textord_debug_tabfind)
    tprintf("Inserted %d line blobs into grid\n", b_count);
  if (b_count == 0)
    return;

  // Search the entire grid, looking for vertical line vectors.
  GridSearch<BLOBNBOX, BLOBNBOX_CLIST, BLOBNBOX_C_IT> lsearch(&blob_grid);
  BLOBNBOX* bbox;
  TabVector_IT vector_it(vectors);
  *vertical_x = 0;
  *vertical_y = 1;
  lsearch.StartFullSearch();
  while ((bbox = lsearch.NextFullSearch()) != NULL) {
    if (bbox->left_tab_type() == TT_UNCONFIRMED) {
      const TBOX& box = bbox->bounding_box();
      if (AlignedBlob::WithinTestRegion(2, box.left(), box.bottom()))
        tprintf("Finding line vector starting at bbox (%d,%d)\n",
                box.left(), box.bottom());
      AlignedBlobParams align_params(*vertical_x, *vertical_y, box.width());
      TabVector* vector = blob_grid.FindVerticalAlignment(align_params, bbox,
                                                          vertical_x,
                                                          vertical_y);
      if (vector != NULL) {
        vector->Freeze();
        vector_it.add_to_end(vector);
      }
    }
  }
  ScrollView* line_win = NULL;
  if (textord_tabfind_show_vlines) {
    line_win = blob_grid.MakeWindow(0, 50, "Vlines");
    blob_grid.DisplayBoxes(line_win);
    line_win = blob_grid.DisplayTabs("Vlines", line_win);
  }
}
Example #2
0
// Tests each blob in the list to see if it is certain non-text using 2
// conditions:
// 1. blob overlaps a cell with high value in noise_density_ (previously set
// by ComputeNoiseDensity).
// OR 2. The blob overlaps more than max_blob_overlaps in *this grid. This
// condition is disabled with max_blob_overlaps == -1.
// If it does, the blob is declared non-text, and is used to mark up the
// nontext_mask. Such blobs are fully deleted, and non-noise blobs have their
// neighbours reset, as they may now point to deleted data.
// WARNING: The blobs list blobs may be in the *this grid, but they are
// not removed. If any deleted blobs might be in *this, then this must be
// Clear()ed immediately after MarkAndDeleteNonTextBlobs is called.
// If the win is not NULL, deleted blobs are drawn on it in red, and kept
// blobs are drawn on it in ok_color.
void CCNonTextDetect::MarkAndDeleteNonTextBlobs(BLOBNBOX_LIST* blobs,
        int max_blob_overlaps,
        ScrollView* win,
        ScrollView::Color ok_color,
        Pix* nontext_mask) {
    int imageheight = tright().y() - bleft().x();
    BLOBNBOX_IT blob_it(blobs);
    BLOBNBOX_LIST dead_blobs;
    BLOBNBOX_IT dead_it(&dead_blobs);
    for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
        BLOBNBOX* blob = blob_it.data();
        TBOX box = blob->bounding_box();
        if (!noise_density_->RectMostlyOverThreshold(box, max_noise_count_) &&
                (max_blob_overlaps < 0 ||
                 !BlobOverlapsTooMuch(blob, max_blob_overlaps))) {
            blob->ClearNeighbours();
#ifndef GRAPHICS_DISABLED
            if (win != NULL)
                blob->plot(win, ok_color, ok_color);
#endif  // GRAPHICS_DISABLED
        } else {
            if (noise_density_->AnyZeroInRect(box)) {
                // There is a danger that the bounding box may overlap real text, so
                // we need to render the outline.
                Pix* blob_pix = blob->cblob()->render_outline();
                pixRasterop(nontext_mask, box.left(), imageheight - box.top(),
                            box.width(), box.height(), PIX_SRC | PIX_DST,
                            blob_pix, 0, 0);
                pixDestroy(&blob_pix);
            } else {
                if (box.area() < gridsize() * gridsize()) {
                    // It is a really bad idea to make lots of small components in the
                    // photo mask, so try to join it to a bigger area by expanding the
                    // box in a way that does not touch any zero noise density cell.
                    box = AttemptBoxExpansion(box, *noise_density_, gridsize());
                }
                // All overlapped cells are non-zero, so just mark the rectangle.
                pixRasterop(nontext_mask, box.left(), imageheight - box.top(),
                            box.width(), box.height(), PIX_SET, NULL, 0, 0);
            }
#ifndef GRAPHICS_DISABLED
            if (win != NULL)
                blob->plot(win, ScrollView::RED, ScrollView::RED);
#endif  // GRAPHICS_DISABLED
            // It is safe to delete the cblob now, as it isn't used by the grid
            // or BlobOverlapsTooMuch, and the BLOBNBOXes will go away with the
            // dead_blobs list.
            // TODO(rays) delete the delete when the BLOBNBOX destructor deletes
            // the cblob.
            delete blob->cblob();
            dead_it.add_to_end(blob_it.extract());
        }
    }
}
Example #3
0
// Extend this vector to include the supplied blob if it doesn't
// already have it.
void TabVector::ExtendToBox(BLOBNBOX* new_blob) {
  TBOX new_box = new_blob->bounding_box();
  BLOBNBOX_C_IT it(&boxes_);
  if (!it.empty()) {
    BLOBNBOX* blob = it.data();
    TBOX box = blob->bounding_box();
    while (!it.at_last() && box.top() <= new_box.top()) {
      if (blob == new_blob)
        return;  // We have it already.
      it.forward();
      blob = it.data();
      box = blob->bounding_box();
    }
    if (box.top() >= new_box.top()) {
      it.add_before_stay_put(new_blob);
      needs_refit_ = true;
      return;
    }
  }
  needs_refit_ = true;
  it.add_after_stay_put(new_blob);
}
Example #4
0
// Returns the box gaps between this and its neighbours_ in an array
// indexed by BlobNeighbourDir.
void BLOBNBOX::NeighbourGaps(int gaps[BND_COUNT]) const {
  for (int dir = 0; dir < BND_COUNT; ++dir) {
    gaps[dir] = MAX_INT16;
    BLOBNBOX* neighbour = neighbours_[dir];
    if (neighbour != NULL) {
      TBOX n_box = neighbour->bounding_box();
      if (dir == BND_LEFT || dir == BND_RIGHT) {
        gaps[dir] = box.x_gap(n_box);
      } else {
        gaps[dir] = box.y_gap(n_box);
      }
    }
  }
}
// Moves blobs that look like they don't sit well on a textline from the
// input blobs list to the output small_blobs list.
// This gets them away from initial textline finding to stop diacritics
// from forming incorrect textlines. (Introduced mainly to fix Thai.)
void TextlineProjection::MoveNonTextlineBlobs(
    BLOBNBOX_LIST* blobs, BLOBNBOX_LIST* small_blobs) const {
  BLOBNBOX_IT it(blobs);
  BLOBNBOX_IT small_it(small_blobs);
  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
    BLOBNBOX* blob = it.data();
    const TBOX& box = blob->bounding_box();
    bool debug = AlignedBlob::WithinTestRegion(2, box.left(),
                                               box.bottom());
    if (BoxOutOfHTextline(box, NULL, debug) && !blob->UniquelyVertical()) {
      blob->ClearNeighbours();
      small_it.add_to_end(it.extract());
    }
  }
}
Example #6
0
TBOX box_next_pre_chopped(                 //get bounding box
                         BLOBNBOX_IT *it  //iterator to blobds
                        ) {
  BLOBNBOX *blob;                //current blob
  TBOX result;                    //total box

  blob = it->data ();
  result = blob->bounding_box ();
  do {
    it->forward ();
    blob = it->data ();
  }
                                 //until next real blob
  while (blob->joined_to_prev ());
  return result;
}
Example #7
0
// Finds vertical line objects in the given pix.
// Uses the given resolution to determine size thresholds instead of any
// that may be present in the pix.
// The output vertical_x and vertical_y contain a sum of the output vectors,
// thereby giving the mean vertical direction.
// The output vectors are owned by the list and Frozen (cannot refit) by
// having no boxes, as there is no need to refit or merge separator lines.
void LineFinder::FindVerticalLines(int resolution,  Pix* pix,
                                   int* vertical_x, int* vertical_y,
                                   TabVector_LIST* vectors) {
#ifdef HAVE_LIBLEPT
  Pix* line_pix;
  Boxa* boxes = GetVLineBoxes(resolution, pix, &line_pix);
  C_BLOB_LIST line_cblobs;
  int width = pixGetWidth(pix);
  int height = pixGetHeight(pix);
  ConvertBoxaToBlobs(width, height, &boxes, &line_cblobs);
  // Make the BLOBNBOXes from the C_BLOBs.
  BLOBNBOX_LIST line_bblobs;
  C_BLOB_IT blob_it(&line_cblobs);
  BLOBNBOX_IT bbox_it(&line_bblobs);
  for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
    C_BLOB* cblob = blob_it.data();
    BLOBNBOX* bblob = new BLOBNBOX(cblob);
    bbox_it.add_to_end(bblob);
  }
  ICOORD bleft(0, 0);
  ICOORD tright(width, height);
  FindLineVectors(bleft, tright, &line_bblobs, vertical_x, vertical_y, vectors);
  if (!vectors->empty()) {
    // Some lines were found, so erase the unused blobs from the line image
    // and then subtract the line image from the source.
    bbox_it.move_to_first();
    for (bbox_it.mark_cycle_pt(); !bbox_it.cycled_list(); bbox_it.forward()) {
      BLOBNBOX* blob = bbox_it.data();
      if (blob->left_tab_type() == TT_UNCONFIRMED) {
        const TBOX& box = blob->bounding_box();
        Box* pixbox = boxCreate(box.left(), height - box.top(),
                                box.width(), box.height());
        pixClearInRect(line_pix, pixbox);
        boxDestroy(&pixbox);
      }
    }
    pixDilateBrick(line_pix, line_pix, 1, 3);
    pixSubtract(pix, pix, line_pix);
    if (textord_tabfind_show_vlines)
      pixWrite("vlinesclean.png", line_pix, IFF_PNG);
    ICOORD vertical;
    vertical.set_with_shrink(*vertical_x, *vertical_y);
    TabVector::MergeSimilarTabVectors(vertical, vectors, NULL);
  }
  pixDestroy(&line_pix);
#endif
}
// Display the blobs in the window colored according to textline quality.
void TextlineProjection::PlotGradedBlobs(BLOBNBOX_LIST* blobs,
                                         ScrollView* win) {
#ifndef GRAPHICS_DISABLED
  BLOBNBOX_IT it(blobs);
  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
    BLOBNBOX* blob = it.data();
    const TBOX& box = blob->bounding_box();
    bool bad_box = BoxOutOfHTextline(box, NULL, false);
    if (blob->UniquelyVertical())
      win->Pen(ScrollView::YELLOW);
    else
      win->Pen(bad_box ? ScrollView::RED : ScrollView::BLUE);
    win->Rectangle(box.left(), box.bottom(), box.right(), box.top());
  }
  win->Update();
#endif  // GRAPHICS_DISABLED
}
Example #9
0
// Display the tab codes of the BLOBNBOXes in this grid.
ScrollView* AlignedBlob::DisplayTabs(const char* window_name,
                                     ScrollView* tab_win) {
#ifndef GRAPHICS_DISABLED
  if (tab_win == nullptr)
    tab_win = MakeWindow(0, 50, window_name);
  // For every tab in the grid, display it.
  GridSearch<BLOBNBOX, BLOBNBOX_CLIST, BLOBNBOX_C_IT> gsearch(this);
  gsearch.StartFullSearch();
  BLOBNBOX* bbox;
  while ((bbox = gsearch.NextFullSearch()) != nullptr) {
    const TBOX& box = bbox->bounding_box();
    int left_x = box.left();
    int right_x = box.right();
    int top_y = box.top();
    int bottom_y = box.bottom();
    TabType tabtype = bbox->left_tab_type();
    if (tabtype != TT_NONE) {
      if (tabtype == TT_MAYBE_ALIGNED)
        tab_win->Pen(ScrollView::BLUE);
      else if (tabtype == TT_MAYBE_RAGGED)
        tab_win->Pen(ScrollView::YELLOW);
      else if (tabtype == TT_CONFIRMED)
        tab_win->Pen(ScrollView::GREEN);
      else
        tab_win->Pen(ScrollView::GREY);
      tab_win->Line(left_x, top_y, left_x, bottom_y);
    }
    tabtype = bbox->right_tab_type();
    if (tabtype != TT_NONE) {
      if (tabtype == TT_MAYBE_ALIGNED)
        tab_win->Pen(ScrollView::MAGENTA);
      else if (tabtype == TT_MAYBE_RAGGED)
        tab_win->Pen(ScrollView::ORANGE);
      else if (tabtype == TT_CONFIRMED)
        tab_win->Pen(ScrollView::RED);
      else
        tab_win->Pen(ScrollView::GREY);
      tab_win->Line(right_x, top_y, right_x, bottom_y);
    }
  }
  tab_win->Update();
#endif
  return tab_win;
}
Example #10
0
// Returns true if the given blob overlaps more than max_overlaps blobs
// in the current grid.
bool CCNonTextDetect::BlobOverlapsTooMuch(BLOBNBOX* blob, int max_overlaps) {
    // Search the grid to see what intersects it.
    // Setup a Rectangle search for overlapping this blob.
    BlobGridSearch rsearch(this);
    TBOX box = blob->bounding_box();
    rsearch.StartRectSearch(box);
    rsearch.SetUniqueMode(true);
    BLOBNBOX* neighbour;
    int overlap_count = 0;
    while (overlap_count <= max_overlaps &&
            (neighbour = rsearch.NextRectSearch()) != NULL) {
        if (box.major_overlap(neighbour->bounding_box())) {
            ++overlap_count;
            if (overlap_count > max_overlaps)
                return true;
        }
    }
    return false;
}
Example #11
0
// Sets up displacement_modes_ with the top few modes of the perpendicular
// distance of each blob from the given direction vector, after rounding.
void BaselineRow::SetupBlobDisplacements(const FCOORD& direction) {
  // Set of perpendicular displacements of the blob bottoms from the required
  // baseline direction.
  GenericVector<double> perp_blob_dists;
  displacement_modes_.truncate(0);
  // Gather the skew-corrected position of every blob.
  double min_dist = MAX_FLOAT32;
  double max_dist = -MAX_FLOAT32;
  BLOBNBOX_IT blob_it(blobs_);
  bool debug = false;
  for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
    BLOBNBOX* blob = blob_it.data();
    const TBOX& box = blob->bounding_box();
#ifdef kDebugYCoord
    if (box.bottom() < kDebugYCoord && box.top() > kDebugYCoord) debug = true;
#endif
    FCOORD blob_pos((box.left() + box.right()) / 2.0f,
                    blob->baseline_position());
    double offset = direction * blob_pos;
    perp_blob_dists.push_back(offset);
    if (debug) {
      tprintf("Displacement %g for blob at:", offset);
      box.print();
    }
    UpdateRange(offset, &min_dist, &max_dist);
  }
  // Set up a histogram using disp_quant_factor_ as the bucket size.
  STATS dist_stats(IntCastRounded(min_dist / disp_quant_factor_),
                   IntCastRounded(max_dist / disp_quant_factor_) + 1);
  for (int i = 0; i < perp_blob_dists.size(); ++i) {
    dist_stats.add(IntCastRounded(perp_blob_dists[i] / disp_quant_factor_), 1);
  }
  GenericVector<KDPairInc<float, int> > scaled_modes;
  dist_stats.top_n_modes(kMaxDisplacementsModes, &scaled_modes);
  if (debug) {
    for (int i = 0; i < scaled_modes.size(); ++i) {
      tprintf("Top mode = %g * %d\n",
              scaled_modes[i].key * disp_quant_factor_, scaled_modes[i].data);
    }
  }
  for (int i = 0; i < scaled_modes.size(); ++i)
    displacement_modes_.push_back(disp_quant_factor_ * scaled_modes[i].key);
}
Example #12
0
// Places a copy of blobs that are near a word (after applying rotation to the
// blob) in the most appropriate word, unless there is doubt, in which case a
// blob can end up in two words. Source blobs are not touched.
void Textord::TransferDiacriticsToWords(BLOBNBOX_LIST* diacritic_blobs,
                                        const FCOORD& rotation,
                                        WordGrid* word_grid) {
  WordSearch ws(word_grid);
  BLOBNBOX_IT b_it(diacritic_blobs);
  // Apply rotation to each blob before finding the nearest words. The rotation
  // allows us to only consider above/below placement and not left/right on
  // vertical text, because all text is horizontal here.
  for (b_it.mark_cycle_pt(); !b_it.cycled_list(); b_it.forward()) {
    BLOBNBOX* blobnbox = b_it.data();
    TBOX blob_box = blobnbox->bounding_box();
    blob_box.rotate(rotation);
    ws.StartRectSearch(blob_box);
    // Above/below refer to word position relative to diacritic. Since some
    // scripts eg Kannada/Telugu habitually put diacritics below words, and
    // others eg Thai/Vietnamese/Latin put most diacritics above words, try
    // for both if there isn't much in it.
    WordWithBox* best_above_word = nullptr;
    WordWithBox* best_below_word = nullptr;
    int best_above_distance = 0;
    int best_below_distance = 0;
    for (WordWithBox* word = ws.NextRectSearch(); word != nullptr;
         word = ws.NextRectSearch()) {
      if (word->word()->flag(W_REP_CHAR)) continue;
      TBOX word_box = word->true_bounding_box();
      int x_distance = blob_box.x_gap(word_box);
      int y_distance = blob_box.y_gap(word_box);
      if (x_distance > 0) {
        // Arbitrarily divide x-distance by 2 if there is a major y overlap,
        // and the word is to the left of the diacritic. If the
        // diacritic is a dropped broken character between two words, this will
        // help send all the pieces to a single word, instead of splitting them
        // over the 2 words.
        if (word_box.major_y_overlap(blob_box) &&
            blob_box.left() > word_box.right()) {
          x_distance /= 2;
        }
        y_distance += x_distance;
      }
      if (word_box.y_middle() > blob_box.y_middle() &&
          (best_above_word == nullptr || y_distance < best_above_distance)) {
        best_above_word = word;
        best_above_distance = y_distance;
      }
      if (word_box.y_middle() <= blob_box.y_middle() &&
          (best_below_word == nullptr || y_distance < best_below_distance)) {
        best_below_word = word;
        best_below_distance = y_distance;
      }
    }
    bool above_good =
        best_above_word != nullptr &&
        (best_below_word == nullptr ||
         best_above_distance < best_below_distance + blob_box.height());
    bool below_good =
        best_below_word != nullptr && best_below_word != best_above_word &&
        (best_above_word == nullptr ||
         best_below_distance < best_above_distance + blob_box.height());
    if (below_good) {
      C_BLOB* copied_blob = C_BLOB::deep_copy(blobnbox->cblob());
      copied_blob->rotate(rotation);
      // Put the blob into the word's reject blobs list.
      C_BLOB_IT blob_it(best_below_word->RejBlobs());
      blob_it.add_to_end(copied_blob);
    }
    if (above_good) {
      C_BLOB* copied_blob = C_BLOB::deep_copy(blobnbox->cblob());
      copied_blob->rotate(rotation);
      // Put the blob into the word's reject blobs list.
      C_BLOB_IT blob_it(best_above_word->RejBlobs());
      blob_it.add_to_end(copied_blob);
    }
  }
}
Example #13
0
// (Re)Fit a line to the stored points. Returns false if the line
// is degenerate. Althougth the TabVector code mostly doesn't care about the
// direction of lines, XAtY would give silly results for a horizontal line.
// The class is mostly aimed at use for vertical lines representing
// horizontal tab stops.
bool TabVector::Fit(ICOORD vertical, bool force_parallel) {
  needs_refit_ = false;
  if (boxes_.empty()) {
    // Don't refit something with no boxes, as that only happens
    // in Evaluate, and we don't want to end up with a zero vector.
    if (!force_parallel)
      return false;
    // If we are forcing parallel, then we just need to set the sort_key_.
    ICOORD midpt = startpt_;
    midpt += endpt_;
    midpt /= 2;
    sort_key_ = SortKey(vertical, midpt.x(), midpt.y());
    return startpt_.y() != endpt_.y();
  }
  if (!force_parallel && !IsRagged()) {
    // Use a fitted line as the vertical.
    DetLineFit linepoints;
    BLOBNBOX_C_IT it(&boxes_);
    // Fit a line to all the boxes in the list.
    for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
      BLOBNBOX* bbox = it.data();
      TBOX box = bbox->bounding_box();
      int x1 = IsRightTab() ? box.right() : box.left();
      ICOORD boxpt(x1, box.bottom());
      linepoints.Add(boxpt);
      if (it.at_last()) {
        ICOORD top_pt(x1, box.top());
        linepoints.Add(top_pt);
      }
    }
    linepoints.Fit(&startpt_, &endpt_);
    if (startpt_.y() != endpt_.y()) {
      vertical = endpt_;
      vertical -= startpt_;
    }
  }
  int start_y = startpt_.y();
  int end_y = endpt_.y();
  sort_key_ = IsLeftTab() ? MAX_INT32 : -MAX_INT32;
  BLOBNBOX_C_IT it(&boxes_);
  // Choose a line parallel to the vertical such that all boxes are on the
  // correct side of it.
  mean_width_ = 0;
  int width_count = 0;
  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
    BLOBNBOX* bbox = it.data();
    TBOX box = bbox->bounding_box();
    mean_width_ += box.width();
    ++width_count;
    int x1 = IsRightTab() ? box.right() : box.left();
    // Test both the bottom and the top, as one will be more extreme, depending
    // on the direction of skew.
    int bottom_y = box.bottom();
    int top_y = box.top();
    int key = SortKey(vertical, x1, bottom_y);
    if (IsLeftTab() == (key < sort_key_)) {
      sort_key_ = key;
      startpt_ = ICOORD(x1, bottom_y);
    }
    key = SortKey(vertical, x1, top_y);
    if (IsLeftTab() == (key < sort_key_)) {
      sort_key_ = key;
      startpt_ = ICOORD(x1, top_y);
    }
    if (it.at_first())
      start_y = bottom_y;
    if (it.at_last())
      end_y = top_y;
  }
  if (width_count > 0) {
    mean_width_ = (mean_width_ + width_count - 1) / width_count;
  }
  endpt_ = startpt_ + vertical;
  needs_evaluation_ = true;
  if (start_y != end_y) {
    // Set the ends of the vector to fully include the first and last blobs.
    startpt_.set_x(XAtY(vertical, sort_key_, start_y));
    startpt_.set_y(start_y);
    endpt_.set_x(XAtY(vertical, sort_key_, end_y));
    endpt_.set_y(end_y);
    return true;
  }
  return false;
}
Example #14
0
// Fits a straight baseline to the points. Returns true if it had enough
// points to be reasonably sure of the fitted baseline.
// If use_box_bottoms is false, baselines positions are formed by
// considering the outlines of the blobs.
bool BaselineRow::FitBaseline(bool use_box_bottoms) {
  // Deterministic fitting is used wherever possible.
  fitter_.Clear();
  // Linear least squares is a backup if the DetLineFit produces a bad line.
  LLSQ llsq;
  BLOBNBOX_IT blob_it(blobs_);

  for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
    BLOBNBOX* blob = blob_it.data();
    if (!use_box_bottoms) blob->EstimateBaselinePosition();
    const TBOX& box = blob->bounding_box();
    int x_middle = (box.left() + box.right()) / 2;
#ifdef kDebugYCoord
    if (box.bottom() < kDebugYCoord && box.top() > kDebugYCoord) {
      tprintf("Box bottom = %d, baseline pos=%d for box at:",
              box.bottom(), blob->baseline_position());
      box.print();
    }
#endif
    fitter_.Add(ICOORD(x_middle, blob->baseline_position()), box.width() / 2);
    llsq.add(x_middle, blob->baseline_position());
  }
  // Fit the line.
  ICOORD pt1, pt2;
  baseline_error_ = fitter_.Fit(&pt1, &pt2);
  baseline_pt1_ = pt1;
  baseline_pt2_ = pt2;
  if (baseline_error_ > max_baseline_error_ &&
      fitter_.SufficientPointsForIndependentFit()) {
    // The fit was bad but there were plenty of points, so try skipping
    // the first and last few, and use the new line if it dramatically improves
    // the error of fit.
    double error = fitter_.Fit(kNumSkipPoints, kNumSkipPoints, &pt1, &pt2);
    if (error < baseline_error_ / 2.0) {
      baseline_error_ = error;
      baseline_pt1_ = pt1;
      baseline_pt2_ = pt2;
    }
  }
  int debug = 0;
#ifdef kDebugYCoord
  Print();
  debug = bounding_box_.bottom() < kDebugYCoord &&
      bounding_box_.top() > kDebugYCoord
            ? 3 : 2;
#endif
  // Now we obtained a direction from that fit, see if we can improve the
  // fit using the same direction and some other start point.
  FCOORD direction(pt2 - pt1);
  double target_offset = direction * pt1;
  good_baseline_ = false;
  FitConstrainedIfBetter(debug, direction, 0.0, target_offset);
  // Wild lines can be produced because DetLineFit allows vertical lines, but
  // vertical text has been rotated so angles over pi/4 should be disallowed.
  // Near vertical lines can still be produced by vertically aligned components
  // on very short lines.
  double angle = BaselineAngle();
  if (fabs(angle) > M_PI * 0.25) {
    // Use the llsq fit as a backup.
    baseline_pt1_ = llsq.mean_point();
    baseline_pt2_ = baseline_pt1_ + FCOORD(1.0f, llsq.m());
    // TODO(rays) get rid of this when m and c are no longer used.
    double m = llsq.m();
    double c = llsq.c(m);
    baseline_error_ = llsq.rms(m, c);
    good_baseline_ = false;
  }
  return good_baseline_;
}
Example #15
0
// Return true if this vector is the same side, overlaps, and close
// enough to the other to be merged.
bool TabVector::SimilarTo(const ICOORD& vertical,
                          const TabVector& other, BlobGrid* grid) const {
  if ((IsRightTab() && other.IsRightTab()) ||
      (IsLeftTab() && other.IsLeftTab())) {
    // If they don't overlap, at least in extensions, then there is no chance.
    if (ExtendedOverlap(other.extended_ymax_, other.extended_ymin_) < 0)
      return false;
    // A fast approximation to the scale factor of the sort_key_.
    int v_scale = abs(vertical.y());
    if (v_scale == 0)
      v_scale = 1;
    // If they are close enough, then OK.
    if (sort_key_ + kSimilarVectorDist * v_scale >= other.sort_key_ &&
        sort_key_ - kSimilarVectorDist * v_scale <= other.sort_key_)
      return true;
    // Ragged tabs get a bigger threshold.
    if (!IsRagged() || !other.IsRagged() ||
        sort_key_ + kSimilarRaggedDist * v_scale < other.sort_key_ ||
        sort_key_ - kSimilarRaggedDist * v_scale > other.sort_key_)
      return false;
    if (grid == NULL) {
      // There is nothing else to test!
      return true;
    }
    // If there is nothing in the rectangle between the vector that is going to
    // move, and the place it is moving to, then they can be merged.
    // Setup a vertical search for any blob.
    const TabVector* mover = (IsRightTab() &&
       sort_key_ < other.sort_key_) ? this : &other;
    int top_y = mover->endpt_.y();
    int bottom_y = mover->startpt_.y();
    int left = MIN(mover->XAtY(top_y), mover->XAtY(bottom_y));
    int right = MAX(mover->XAtY(top_y), mover->XAtY(bottom_y));
    int shift = abs(sort_key_ - other.sort_key_) / v_scale;
    if (IsRightTab()) {
      right += shift;
    } else {
      left -= shift;
    }

    GridSearch<BLOBNBOX, BLOBNBOX_CLIST, BLOBNBOX_C_IT> vsearch(grid);
    vsearch.StartVerticalSearch(left, right, top_y);
    BLOBNBOX* blob;
    while ((blob = vsearch.NextVerticalSearch(true)) != NULL) {
      TBOX box = blob->bounding_box();
      if (box.top() > bottom_y)
        return true;  // Nothing found.
      if (box.bottom() < top_y)
        continue;  // Doesn't overlap.
      int left_at_box = XAtY(box.bottom());
      int right_at_box = left_at_box;
      if (IsRightTab())
        right_at_box += shift;
      else
        left_at_box -= shift;
      if (MIN(right_at_box, box.right()) > MAX(left_at_box, box.left()))
        return false;
    }
    return true;  // Nothing found.
  }
  return false;
}
Example #16
0
// Evaluate the vector in terms of coverage of its length by good-looking
// box edges. A good looking box is one where its nearest neighbour on the
// inside is nearer than half the distance its nearest neighbour on the
// outside of the putative column. Bad boxes are removed from the line.
// A second pass then further filters boxes by requiring that the gutter
// width be a minimum fraction of the mean gutter along the line.
void TabVector::Evaluate(const ICOORD& vertical, TabFind* finder) {
  bool debug = false;
  needs_evaluation_ = false;
  int length = endpt_.y() - startpt_.y();
  if (length == 0 || boxes_.empty()) {
    percent_score_ = 0;
    Print("Zero length in evaluate");
    return;
  }
  // Compute the mean box height.
  BLOBNBOX_C_IT it(&boxes_);
  int mean_height = 0;
  int height_count = 0;
  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
    BLOBNBOX* bbox = it.data();
    const TBOX& box = bbox->bounding_box();
    int height = box.height();
    mean_height += height;
    ++height_count;
  }
  if (height_count > 0) mean_height /= height_count;
  int max_gutter = kGutterMultiple * mean_height;
  if (IsRagged()) {
    // Ragged edges face a tougher test in that the gap must always be within
    // the height of the blob.
    max_gutter = kGutterToNeighbourRatio * mean_height;
  }

  STATS gutters(0, max_gutter + 1);
  // Evaluate the boxes for their goodness, calculating the coverage as we go.
  // Remove boxes that are not good and shorten the list to the first and
  // last good boxes.
  int num_deleted_boxes = 0;
  bool text_on_image = false;
  int good_length = 0;
  const TBOX* prev_good_box = NULL;
  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
    BLOBNBOX* bbox = it.data();
    const TBOX& box = bbox->bounding_box();
    int mid_y = (box.top() + box.bottom()) / 2;
    if (TabFind::WithinTestRegion(2, XAtY(box.bottom()), box.bottom())) {
      if (!debug) {
        tprintf("After already deleting %d boxes, ", num_deleted_boxes);
        Print("Starting evaluation");
      }
      debug = true;
    }
    // A good box is one where the nearest neighbour on the inside is closer
    // than half the distance to the nearest neighbour on the outside
    // (of the putative column).
    bool left = IsLeftTab();
    int tab_x = XAtY(mid_y);
    int gutter_width;
    int neighbour_gap;
    finder->GutterWidthAndNeighbourGap(tab_x, mean_height, max_gutter, left,
                                       bbox, &gutter_width, &neighbour_gap);
    if (debug) {
      tprintf("Box (%d,%d)->(%d,%d) has gutter %d, ndist %d\n",
              box.left(), box.bottom(), box.right(), box.top(),
              gutter_width, neighbour_gap);
    }
    // Now we can make the test.
    if (neighbour_gap * kGutterToNeighbourRatio <= gutter_width) {
      // A good box contributes its height to the good_length.
      good_length += box.top() - box.bottom();
      gutters.add(gutter_width, 1);
      // Two good boxes together contribute the gap between them
      // to the good_length as well, as long as the gap is not
      // too big.
      if (prev_good_box != NULL) {
        int vertical_gap = box.bottom() - prev_good_box->top();
        double size1 = sqrt(static_cast<double>(prev_good_box->area()));
        double size2 = sqrt(static_cast<double>(box.area()));
        if (vertical_gap < kMaxFillinMultiple * MIN(size1, size2))
          good_length += vertical_gap;
        if (debug) {
          tprintf("Box and prev good, gap=%d, target %g, goodlength=%d\n",
                  vertical_gap, kMaxFillinMultiple * MIN(size1, size2),
                  good_length);
        }
      } else {
        // Adjust the start to the first good box.
        SetYStart(box.bottom());
      }
      prev_good_box = &box;
      if (bbox->flow() == BTFT_TEXT_ON_IMAGE)
        text_on_image = true;
    } else {
      // Get rid of boxes that are not good.
      if (debug) {
        tprintf("Bad Box (%d,%d)->(%d,%d) with gutter %d, ndist %d\n",
                box.left(), box.bottom(), box.right(), box.top(),
                gutter_width, neighbour_gap);
      }
      it.extract();
      ++num_deleted_boxes;
    }
  }
  if (debug) {
    Print("Evaluating:");
  }
  // If there are any good boxes, do it again, except this time get rid of
  // boxes that have a gutter that is a small fraction of the mean gutter.
  // This filters out ends that run into a coincidental gap in the text.
  int search_top = endpt_.y();
  int search_bottom = startpt_.y();
  int median_gutter = IntCastRounded(gutters.median());
  if (gutters.get_total() > 0) {
    prev_good_box = NULL;
    for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
      BLOBNBOX* bbox = it.data();
      const TBOX& box = bbox->bounding_box();
      int mid_y = (box.top() + box.bottom()) / 2;
      // A good box is one where the gutter width is at least some constant
      // fraction of the mean gutter width.
      bool left = IsLeftTab();
      int tab_x = XAtY(mid_y);
      int max_gutter = kGutterMultiple * mean_height;
      if (IsRagged()) {
        // Ragged edges face a tougher test in that the gap must always be
        // within the height of the blob.
        max_gutter = kGutterToNeighbourRatio * mean_height;
      }
      int gutter_width;
      int neighbour_gap;
      finder->GutterWidthAndNeighbourGap(tab_x, mean_height, max_gutter, left,
                                         bbox, &gutter_width, &neighbour_gap);
      // Now we can make the test.
      if (gutter_width >= median_gutter * kMinGutterFraction) {
        if (prev_good_box == NULL) {
          // Adjust the start to the first good box.
          SetYStart(box.bottom());
          search_bottom = box.top();
        }
        prev_good_box = &box;
        search_top = box.bottom();
      } else {
        // Get rid of boxes that are not good.
        if (debug) {
          tprintf("Bad Box (%d,%d)->(%d,%d) with gutter %d, mean gutter %d\n",
                  box.left(), box.bottom(), box.right(), box.top(),
                  gutter_width, median_gutter);
        }
        it.extract();
        ++num_deleted_boxes;
      }
    }
  }
  // If there has been a good box, adjust the end.
  if (prev_good_box != NULL) {
    SetYEnd(prev_good_box->top());
    // Compute the percentage of the vector that is occupied by good boxes.
    int length = endpt_.y() - startpt_.y();
    percent_score_ = 100 * good_length / length;
    if (num_deleted_boxes > 0) {
      needs_refit_ = true;
      FitAndEvaluateIfNeeded(vertical, finder);
      if (boxes_.empty())
        return;
    }
    // Test the gutter over the whole vector, instead of just at the boxes.
    int required_shift;
    if (search_bottom > search_top) {
      search_bottom = startpt_.y();
      search_top = endpt_.y();
    }
    double min_gutter_width = kLineCountReciprocal / boxes_.length();
    min_gutter_width += IsRagged() ? kMinRaggedGutter : kMinAlignedGutter;
    min_gutter_width *= mean_height;
    int max_gutter_width = IntCastRounded(min_gutter_width) + 1;
    if (median_gutter > max_gutter_width)
      max_gutter_width = median_gutter;
    int gutter_width = finder->GutterWidth(search_bottom, search_top, *this,
                                           text_on_image, max_gutter_width,
                                           &required_shift);
    if (gutter_width < min_gutter_width) {
      if (debug) {
        tprintf("Rejecting bad tab Vector with %d gutter vs %g min\n",
                gutter_width, min_gutter_width);
      }
      boxes_.shallow_clear();
      percent_score_ = 0;
    } else if (debug) {
      tprintf("Final gutter %d, vs limit of %g, required shift = %d\n",
              gutter_width, min_gutter_width, required_shift);
    }
  } else {
    // There are no good boxes left, so score is 0.
    percent_score_ = 0;
  }

  if (debug) {
    Print("Evaluation complete:");
  }
}
Example #17
0
void plot_word_decisions(              //draw words
        ScrollView *win,   //window tro draw in
        inT16 pitch,  //of block
        TO_ROW *row   //row to draw
) {
    ScrollView::Color colour = ScrollView::MAGENTA;       //current colour
    ScrollView::Color rect_colour;            //fuzzy colour
    inT32 prev_x;                  //end of prev blob
    inT16 blob_count;              //blobs in word
    BLOBNBOX *blob;                //current blob
    TBOX blob_box;                  //bounding box
    //iterator
    BLOBNBOX_IT blob_it = row->blob_list();
    BLOBNBOX_IT start_it = blob_it;//word start

    rect_colour = ScrollView::BLACK;
    prev_x = -MAX_INT16;
    blob_count = 0;
    for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
        blob = blob_it.data();
        blob_box = blob->bounding_box();
        if (!blob->joined_to_prev()
            && blob_box.left() - prev_x > row->max_nonspace) {
            if ((blob_box.left() - prev_x >= row->min_space
                 || blob_box.left() - prev_x > row->space_threshold)
                && blob_count > 0) {
                if (pitch > 0 && textord_show_fixed_cuts)
                    plot_fp_cells(win, colour, &start_it, pitch, blob_count,
                                  &row->projection, row->projection_left,
                                  row->projection_right,
                                  row->xheight * textord_projection_scale);
                blob_count = 0;
                start_it = blob_it;
            }
            if (colour == ScrollView::MAGENTA)
                colour = ScrollView::RED;
            else
                colour = (ScrollView::Color)(colour + 1);
            if (blob_box.left() - prev_x < row->min_space) {
                if (blob_box.left() - prev_x > row->space_threshold)
                    rect_colour = ScrollView::GOLDENROD;
                else
                    rect_colour = ScrollView::CORAL;
                //fill_color_index(win, rect_colour);
                win->Brush(rect_colour);
                win->Rectangle(prev_x, blob_box.bottom(),
                               blob_box.left(), blob_box.top());
            }
        }
        if (!blob->joined_to_prev())
            prev_x = blob_box.right();
        if (blob->cblob() != NULL)
            blob->cblob()->plot(win, colour, colour);
        if (!blob->joined_to_prev() && blob->cblob() != NULL)
            blob_count++;
    }
    if (pitch > 0 && textord_show_fixed_cuts && blob_count > 0)
        plot_fp_cells(win, colour, &start_it, pitch, blob_count,
                      &row->projection, row->projection_left,
                      row->projection_right,
                      row->xheight * textord_projection_scale);
}
Example #18
0
// Search vertically for a blob that is aligned with the input bbox.
// The search parameters are determined by AlignedBlobParams.
// top_to_bottom tells whether to search down or up.
// The return value is nullptr if nothing was found in the search box
// or if a blob was found in the gutter. On a nullptr return, end_y
// is set to the edge of the search box or the leading edge of the
// gutter blob if one was found.
BLOBNBOX* AlignedBlob::FindAlignedBlob(const AlignedBlobParams& p,
                                       bool top_to_bottom, BLOBNBOX* bbox,
                                       int x_start, int* end_y) {
  TBOX box = bbox->bounding_box();
  // If there are separator lines, get the column edges.
  int left_column_edge = bbox->left_rule();
  int right_column_edge = bbox->right_rule();
  // start_y is used to guarantee that forward progress is made and the
  // search does not go into an infinite loop. New blobs must extend the
  // line beyond start_y.
  int start_y = top_to_bottom ? box.bottom() : box.top();
  if (WithinTestRegion(2, x_start, start_y)) {
    tprintf("Column edges for blob at (%d,%d)->(%d,%d) are [%d, %d]\n",
            box.left(), box.top(), box.right(), box.bottom(),
            left_column_edge, right_column_edge);
  }
  // Compute skew tolerance.
  int skew_tolerance = p.max_v_gap / kMaxSkewFactor;
  // Calculate xmin and xmax of the search box so that it contains
  // all possibly relevant boxes up to p.max_v_gap above or below accoording
  // to top_to_bottom.
  // Start with a notion of vertical with the current estimate.
  int x2 = (p.max_v_gap * p.vertical.x() + p.vertical.y()/2) / p.vertical.y();
  if (top_to_bottom) {
    x2 = x_start - x2;
    *end_y = start_y - p.max_v_gap;
  } else {
    x2 = x_start + x2;
    *end_y = start_y + p.max_v_gap;
  }
  // Expand the box by an additional skew tolerance
  int xmin = std::min(x_start, x2) - skew_tolerance;
  int xmax = std::max(x_start, x2) + skew_tolerance;
  // Now add direction-specific tolerances.
  if (p.right_tab) {
    xmax += p.min_gutter;
    xmin -= p.l_align_tolerance;
  } else {
    xmax += p.r_align_tolerance;
    xmin -= p.min_gutter;
  }
  // Setup a vertical search for an aligned blob.
  GridSearch<BLOBNBOX, BLOBNBOX_CLIST, BLOBNBOX_C_IT> vsearch(this);
  if (WithinTestRegion(2, x_start, start_y))
    tprintf("Starting %s %s search at %d-%d,%d, search_size=%d, gutter=%d\n",
            p.ragged ? "Ragged" : "Aligned", p.right_tab ? "Right" : "Left",
            xmin, xmax, start_y, p.max_v_gap, p.min_gutter);
  vsearch.StartVerticalSearch(xmin, xmax, start_y);
  // result stores the best real return value.
  BLOBNBOX* result = nullptr;
  // The backup_result is not a tab candidate and can be used if no
  // real tab candidate result is found.
  BLOBNBOX* backup_result = nullptr;
  // neighbour is the blob that is currently being investigated.
  BLOBNBOX* neighbour = nullptr;
  while ((neighbour = vsearch.NextVerticalSearch(top_to_bottom)) != nullptr) {
    if (neighbour == bbox)
      continue;
    TBOX nbox = neighbour->bounding_box();
    int n_y = (nbox.top() + nbox.bottom()) / 2;
    if ((!top_to_bottom && n_y > start_y + p.max_v_gap) ||
        (top_to_bottom && n_y < start_y - p.max_v_gap)) {
      if (WithinTestRegion(2, x_start, start_y))
        tprintf("Neighbour too far at (%d,%d)->(%d,%d)\n",
                nbox.left(), nbox.bottom(), nbox.right(), nbox.top());
      break;  // Gone far enough.
    }
    // It is CRITICAL to ensure that forward progress is made, (strictly
    // in/decreasing n_y) or the caller could loop infinitely, while
    // waiting for a sequence of blobs in a line to end.
    // NextVerticalSearch alone does not guarantee this, as there may be
    // more than one blob in a grid cell. See comment in AlignTabs.
    if ((n_y < start_y) != top_to_bottom || nbox.y_overlap(box))
      continue;  // Only look in the required direction.
    if (result != nullptr && result->bounding_box().y_gap(nbox) > gridsize())
      return result;  // This result is clear.
    if (backup_result != nullptr && p.ragged && result == nullptr &&
        backup_result->bounding_box().y_gap(nbox) > gridsize())
      return backup_result;  // This result is clear.

    // If the neighbouring blob is the wrong side of a separator line, then it
    // "doesn't exist" as far as we are concerned.
    int x_at_n_y = x_start + (n_y - start_y) * p.vertical.x() / p.vertical.y();
    if (x_at_n_y < neighbour->left_crossing_rule() ||
        x_at_n_y > neighbour->right_crossing_rule())
      continue;  // Separator line in the way.
    int n_left = nbox.left();
    int n_right = nbox.right();
    int n_x = p.right_tab ? n_right : n_left;
    if (WithinTestRegion(2, x_start, start_y))
      tprintf("neighbour at (%d,%d)->(%d,%d), n_x=%d, n_y=%d, xatn=%d\n",
              nbox.left(), nbox.bottom(), nbox.right(), nbox.top(),
              n_x, n_y, x_at_n_y);
    if (p.right_tab &&
        n_left < x_at_n_y + p.min_gutter &&
        n_right > x_at_n_y + p.r_align_tolerance &&
        (p.ragged || n_left < x_at_n_y + p.gutter_fraction * nbox.height())) {
      // In the gutter so end of line.
      if (bbox->right_tab_type() >= TT_MAYBE_ALIGNED)
        bbox->set_right_tab_type(TT_DELETED);
      *end_y = top_to_bottom ? nbox.top() : nbox.bottom();
      if (WithinTestRegion(2, x_start, start_y))
        tprintf("gutter\n");
      return nullptr;
    }
    if (!p.right_tab &&
        n_left < x_at_n_y - p.l_align_tolerance &&
        n_right > x_at_n_y - p.min_gutter &&
        (p.ragged || n_right > x_at_n_y - p.gutter_fraction * nbox.height())) {
      // In the gutter so end of line.
      if (bbox->left_tab_type() >= TT_MAYBE_ALIGNED)
        bbox->set_left_tab_type(TT_DELETED);
      *end_y = top_to_bottom ? nbox.top() : nbox.bottom();
      if (WithinTestRegion(2, x_start, start_y))
        tprintf("gutter\n");
      return nullptr;
    }
    if ((p.right_tab && neighbour->leader_on_right()) ||
        (!p.right_tab && neighbour->leader_on_left()))
      continue;  // Neighbours of leaders are not allowed to be used.
    if (n_x <= x_at_n_y + p.r_align_tolerance &&
        n_x >= x_at_n_y - p.l_align_tolerance) {
      // Aligned so keep it. If it is a marked tab save it as result,
      // otherwise keep it as backup_result to return in case of later failure.
      if (WithinTestRegion(2, x_start, start_y))
        tprintf("aligned, seeking%d, l=%d, r=%d\n",
                p.right_tab, neighbour->left_tab_type(),
                neighbour->right_tab_type());
      TabType n_type = p.right_tab ? neighbour->right_tab_type()
                                   : neighbour->left_tab_type();
      if (n_type != TT_NONE && (p.ragged || n_type != TT_MAYBE_RAGGED)) {
        if (result == nullptr) {
          result = neighbour;
        } else {
          // Keep the closest neighbour by Euclidean distance.
          // This prevents it from picking a tab blob in another column.
          const TBOX& old_box = result->bounding_box();
          int x_diff = p.right_tab ? old_box.right() : old_box.left();
          x_diff -= x_at_n_y;
          int y_diff = (old_box.top() + old_box.bottom()) / 2 - start_y;
          int old_dist = x_diff * x_diff + y_diff * y_diff;
          x_diff = n_x - x_at_n_y;
          y_diff = n_y - start_y;
          int new_dist = x_diff * x_diff + y_diff * y_diff;
          if (new_dist < old_dist)
            result = neighbour;
        }
      } else if (backup_result == nullptr) {
        if (WithinTestRegion(2, x_start, start_y))
          tprintf("Backup\n");
        backup_result = neighbour;
      } else {
        TBOX backup_box = backup_result->bounding_box();
        if ((p.right_tab && backup_box.right() < nbox.right()) ||
            (!p.right_tab && backup_box.left() > nbox.left())) {
          if (WithinTestRegion(2, x_start, start_y))
            tprintf("Better backup\n");
          backup_result = neighbour;
        }
      }
    }
  }
  return result != nullptr ? result : backup_result;
}
Example #19
0
float Textord::filter_noise_blobs(
    BLOBNBOX_LIST *src_list,      // original list
    BLOBNBOX_LIST *noise_list,    // noise list
    BLOBNBOX_LIST *small_list,    // small blobs
    BLOBNBOX_LIST *large_list) {  // large blobs
  int16_t height;                  //height of blob
  int16_t width;                   //of blob
  BLOBNBOX *blob;                //current blob
  float initial_x;               //first guess
  BLOBNBOX_IT src_it = src_list; //iterators
  BLOBNBOX_IT noise_it = noise_list;
  BLOBNBOX_IT small_it = small_list;
  BLOBNBOX_IT large_it = large_list;
  STATS size_stats (0, MAX_NEAREST_DIST);
  //blob heights
  float min_y;                   //size limits
  float max_y;
  float max_x;
  float max_height;              //of good blobs

  for (src_it.mark_cycle_pt(); !src_it.cycled_list(); src_it.forward()) {
    blob = src_it.data();
    if (blob->bounding_box().height() < textord_max_noise_size)
      noise_it.add_after_then_move(src_it.extract());
    else if (blob->enclosed_area() >= blob->bounding_box().height()
      * blob->bounding_box().width() * textord_noise_area_ratio)
      small_it.add_after_then_move(src_it.extract());
  }
  for (src_it.mark_cycle_pt(); !src_it.cycled_list(); src_it.forward()) {
    size_stats.add(src_it.data()->bounding_box().height(), 1);
  }
  initial_x = size_stats.ile(textord_initialx_ile);
  max_y = ceil(initial_x *
               (tesseract::CCStruct::kDescenderFraction +
                tesseract::CCStruct::kXHeightFraction +
                2 * tesseract::CCStruct::kAscenderFraction) /
               tesseract::CCStruct::kXHeightFraction);
  min_y = floor (initial_x / 2);
  max_x = ceil (initial_x * textord_width_limit);
  small_it.move_to_first ();
  for (small_it.mark_cycle_pt (); !small_it.cycled_list ();
  small_it.forward ()) {
    height = small_it.data()->bounding_box().height();
    if (height > max_y)
      large_it.add_after_then_move(small_it.extract ());
    else if (height >= min_y)
      src_it.add_after_then_move(small_it.extract ());
  }
  size_stats.clear ();
  for (src_it.mark_cycle_pt (); !src_it.cycled_list (); src_it.forward ()) {
    height = src_it.data ()->bounding_box ().height ();
    width = src_it.data ()->bounding_box ().width ();
    if (height < min_y)
      small_it.add_after_then_move (src_it.extract ());
    else if (height > max_y || width > max_x)
      large_it.add_after_then_move (src_it.extract ());
    else
      size_stats.add (height, 1);
  }
  max_height = size_stats.ile (textord_initialasc_ile);
  //      tprintf("max_y=%g, min_y=%g, initial_x=%g, max_height=%g,",
  //              max_y,min_y,initial_x,max_height);
  max_height *= tesseract::CCStruct::kXHeightCapRatio;
  if (max_height > initial_x)
    initial_x = max_height;
  //      tprintf(" ret=%g\n",initial_x);
  return initial_x;
}
Example #20
0
int32_t row_words2(                  //compute space size
        TO_BLOCK* block,  //block it came from
        TO_ROW* row,      //row to operate on
        int32_t maxwidth,   //max expected space size
        FCOORD rotation,  //for drawing
        bool testing_on  //for debug
) {
  bool prev_valid;              //if decent size
  bool this_valid;              //current blob big enough
  int32_t prev_x;                  //end of prev blob
  int32_t min_width;               //min interesting width
  int32_t valid_count;             //good gaps
  int32_t total_count;             //total gaps
  int32_t cluster_count;           //no of clusters
  int32_t prev_count;              //previous cluster_count
  int32_t gap_index;               //which cluster
  int32_t smooth_factor;           //for smoothing stats
  BLOBNBOX *blob;                //current blob
  float lower, upper;            //clustering parameters
  ICOORD testpt;
  TBOX blob_box;                  //bounding box
                                 //iterator
  BLOBNBOX_IT blob_it = row->blob_list ();
  STATS gap_stats (0, maxwidth);
                                 //gap sizes
  float gaps[BLOCK_STATS_CLUSTERS];
  STATS cluster_stats[BLOCK_STATS_CLUSTERS + 1];
  //clusters

  testpt = ICOORD (textord_test_x, textord_test_y);
  smooth_factor =
    static_cast<int32_t>(block->xheight * textord_wordstats_smooth_factor + 1.5);
  //      if (testing_on)
  //              tprintf("Row smooth factor=%d\n",smooth_factor);
  prev_valid = false;
  prev_x = -INT16_MAX;
  const bool testing_row = false;
                                 //min blob size
  min_width = static_cast<int32_t>(block->pr_space);
  total_count = 0;
  for (blob_it.mark_cycle_pt (); !blob_it.cycled_list (); blob_it.forward ()) {
    blob = blob_it.data ();
    if (!blob->joined_to_prev ()) {
      blob_box = blob->bounding_box ();
      this_valid = blob_box.width () >= min_width;
      if (this_valid && prev_valid
      && blob_box.left () - prev_x < maxwidth) {
        gap_stats.add (blob_box.left () - prev_x, 1);
      }
      total_count++;             //count possibles
      prev_x = blob_box.right ();
      prev_valid = this_valid;
    }
  }
  valid_count = gap_stats.get_total ();
  if (valid_count < total_count * textord_words_minlarge) {
    gap_stats.clear ();
    prev_x = -INT16_MAX;
    for (blob_it.mark_cycle_pt (); !blob_it.cycled_list ();
    blob_it.forward ()) {
      blob = blob_it.data ();
      if (!blob->joined_to_prev ()) {
        blob_box = blob->bounding_box ();
        if (blob_box.left () - prev_x < maxwidth) {
          gap_stats.add (blob_box.left () - prev_x, 1);
        }
        prev_x = blob_box.right ();
      }
    }
  }
  if (gap_stats.get_total () == 0) {
    row->min_space = 0;          //no evidence
    row->max_nonspace = 0;
    return 0;
  }

  cluster_count = 0;
  lower = block->xheight * words_initial_lower;
  upper = block->xheight * words_initial_upper;
  gap_stats.smooth (smooth_factor);
  do {
    prev_count = cluster_count;
    cluster_count = gap_stats.cluster (lower, upper,
      textord_spacesize_ratioprop,
      BLOCK_STATS_CLUSTERS, cluster_stats);
  }
  while (cluster_count > prev_count && cluster_count < BLOCK_STATS_CLUSTERS);
  if (cluster_count < 1) {
    row->min_space = 0;
    row->max_nonspace = 0;
    return 0;
  }
  for (gap_index = 0; gap_index < cluster_count; gap_index++)
    gaps[gap_index] = cluster_stats[gap_index + 1].ile (0.5);
  //get medians
  if (testing_on) {
    tprintf ("cluster_count=%d:", cluster_count);
    for (gap_index = 0; gap_index < cluster_count; gap_index++)
      tprintf (" %g(%d)", gaps[gap_index],
        cluster_stats[gap_index + 1].get_total ());
    tprintf ("\n");
  }

  //Try to find proportional non-space and space for row.
  for (gap_index = 0; gap_index < cluster_count
    && gaps[gap_index] > block->max_nonspace; gap_index++);
  if (gap_index < cluster_count)
    lower = gaps[gap_index];     //most frequent below
  else {
    if (testing_on)
      tprintf ("No cluster below block threshold!, using default=%g\n",
        block->pr_nonsp);
    lower = block->pr_nonsp;
  }
  for (gap_index = 0; gap_index < cluster_count
    && gaps[gap_index] <= block->max_nonspace; gap_index++);
  if (gap_index < cluster_count)
    upper = gaps[gap_index];     //most frequent above
  else {
    if (testing_on)
      tprintf ("No cluster above block threshold!, using default=%g\n",
        block->pr_space);
    upper = block->pr_space;
  }
  row->min_space =
    static_cast<int32_t>(ceil (upper - (upper - lower) * textord_words_definite_spread));
  row->max_nonspace =
    static_cast<int32_t>(floor (lower + (upper - lower) * textord_words_definite_spread));
  row->space_threshold = (row->max_nonspace + row->min_space) / 2;
  row->space_size = upper;
  row->kern_size = lower;
  if (testing_on) {
    if (testing_row) {
      tprintf ("GAP STATS\n");
      gap_stats.print();
      tprintf ("SPACE stats\n");
      cluster_stats[2].print_summary();
      tprintf ("NONSPACE stats\n");
      cluster_stats[1].print_summary();
    }
    tprintf ("Row at %g has minspace=%d(%g), max_non=%d(%g)\n",
      row->intercept (), row->min_space, upper,
      row->max_nonspace, lower);
  }
  return 1;
}
Example #21
0
int32_t row_words(                  //compute space size
        TO_BLOCK* block,  //block it came from
        TO_ROW* row,      //row to operate on
        int32_t maxwidth,   //max expected space size
        FCOORD rotation,  //for drawing
        bool testing_on  //for debug
) {
  bool testing_row;             //contains testpt
  bool prev_valid;              //if decent size
  int32_t prev_x;                //end of prev blob
  int32_t cluster_count;         //no of clusters
  int32_t gap_index;             //which cluster
  int32_t smooth_factor;         //for smoothing stats
  BLOBNBOX *blob;                //current blob
  float lower, upper;            //clustering parameters
  float gaps[3];                 //gap clusers
  ICOORD testpt;
  TBOX blob_box;                  //bounding box
                                 //iterator
  BLOBNBOX_IT blob_it = row->blob_list ();
  STATS gap_stats (0, maxwidth);
  STATS cluster_stats[4];        //clusters

  testpt = ICOORD (textord_test_x, textord_test_y);
  smooth_factor =
    static_cast<int32_t>(block->xheight * textord_wordstats_smooth_factor + 1.5);
  //      if (testing_on)
  //              tprintf("Row smooth factor=%d\n",smooth_factor);
  prev_valid = false;
  prev_x = -INT32_MAX;
  testing_row = false;
  for (blob_it.mark_cycle_pt (); !blob_it.cycled_list (); blob_it.forward ()) {
    blob = blob_it.data ();
    blob_box = blob->bounding_box ();
    if (blob_box.contains (testpt))
      testing_row = true;
    gap_stats.add (blob_box.width (), 1);
  }
  gap_stats.clear ();
  for (blob_it.mark_cycle_pt (); !blob_it.cycled_list (); blob_it.forward ()) {
    blob = blob_it.data ();
    if (!blob->joined_to_prev ()) {
      blob_box = blob->bounding_box ();
      if (prev_valid && blob_box.left () - prev_x < maxwidth) {
        gap_stats.add (blob_box.left () - prev_x, 1);
      }
      prev_valid = true;
      prev_x = blob_box.right ();
    }
  }
  if (gap_stats.get_total () == 0) {
    row->min_space = 0;          //no evidence
    row->max_nonspace = 0;
    return 0;
  }
  gap_stats.smooth (smooth_factor);
  lower = row->xheight * textord_words_initial_lower;
  upper = row->xheight * textord_words_initial_upper;
  cluster_count = gap_stats.cluster (lower, upper,
    textord_spacesize_ratioprop, 3,
    cluster_stats);
  while (cluster_count < 2 && ceil (lower) < floor (upper)) {
                                 //shrink gap
    upper = (upper * 3 + lower) / 4;
    lower = (lower * 3 + upper) / 4;
    cluster_count = gap_stats.cluster (lower, upper,
      textord_spacesize_ratioprop, 3,
      cluster_stats);
  }
  if (cluster_count < 2) {
    row->min_space = 0;          //no evidence
    row->max_nonspace = 0;
    return 0;
  }
  for (gap_index = 0; gap_index < cluster_count; gap_index++)
    gaps[gap_index] = cluster_stats[gap_index + 1].ile (0.5);
  //get medians
  if (cluster_count > 2) {
    if (testing_on && textord_show_initial_words) {
      tprintf ("Row at %g has 3 sizes of gap:%g,%g,%g\n",
        row->intercept (),
        cluster_stats[1].ile (0.5),
        cluster_stats[2].ile (0.5), cluster_stats[3].ile (0.5));
    }
    lower = gaps[0];
    if (gaps[1] > lower) {
      upper = gaps[1];           //prefer most frequent
      if (upper < block->xheight * textord_words_min_minspace
      && gaps[2] > gaps[1]) {
        upper = gaps[2];
      }
    }
    else if (gaps[2] > lower
      && gaps[2] >= block->xheight * textord_words_min_minspace)
      upper = gaps[2];
    else if (lower >= block->xheight * textord_words_min_minspace) {
      upper = lower;             //not nice
      lower = gaps[1];
      if (testing_on && textord_show_initial_words) {
        tprintf ("Had to switch most common from lower to upper!!\n");
        gap_stats.print();
      }
    }
    else {
      row->min_space = 0;        //no evidence
      row->max_nonspace = 0;
      return 0;
    }
  }
  else {
    if (gaps[1] < gaps[0]) {
      if (testing_on && textord_show_initial_words) {
        tprintf ("Had to switch most common from lower to upper!!\n");
        gap_stats.print();
      }
      lower = gaps[1];
      upper = gaps[0];
    }
    else {
      upper = gaps[1];
      lower = gaps[0];
    }
  }
  if (upper < block->xheight * textord_words_min_minspace) {
    row->min_space = 0;          //no evidence
    row->max_nonspace = 0;
    return 0;
  }
  if (upper * 3 < block->min_space * 2 + block->max_nonspace
  || lower * 3 > block->min_space * 2 + block->max_nonspace) {
    if (testing_on && textord_show_initial_words) {
      tprintf ("Disagreement between block and row at %g!!\n",
        row->intercept ());
      tprintf ("Lower=%g, upper=%g, Stats:\n", lower, upper);
      gap_stats.print();
    }
  }
  row->min_space =
    static_cast<int32_t>(ceil (upper - (upper - lower) * textord_words_definite_spread));
  row->max_nonspace =
    static_cast<int32_t>(floor (lower + (upper - lower) * textord_words_definite_spread));
  row->space_threshold = (row->max_nonspace + row->min_space) / 2;
  row->space_size = upper;
  row->kern_size = lower;
  if (testing_on && textord_show_initial_words) {
    if (testing_row) {
      tprintf ("GAP STATS\n");
      gap_stats.print();
      tprintf ("SPACE stats\n");
      cluster_stats[2].print_summary();
      tprintf ("NONSPACE stats\n");
      cluster_stats[1].print_summary();
    }
    tprintf ("Row at %g has minspace=%d(%g), max_non=%d(%g)\n",
      row->intercept (), row->min_space, upper,
      row->max_nonspace, lower);
  }
  return cluster_stats[2].get_total ();
}