21 #define _USE_MATH_DEFINES 25 #include "config_auto.h" 31 #include "allheaders.h" 66 : blobs_(to_row->blob_list()),
67 baseline_pt1_(0.0f, 0.0f), baseline_pt2_(0.0f, 0.0f),
68 baseline_error_(0.0), good_baseline_(false) {
82 row->
set_line(gradient, para_c, baseline_error_);
88 tprintf(
"Baseline (%g,%g)->(%g,%g), angle=%g, intercept=%g\n",
89 baseline_pt1_.
x(), baseline_pt1_.
y(),
90 baseline_pt2_.
x(), baseline_pt2_.
y(),
92 tprintf(
"Quant factor=%g, error=%g, good=%d, box:",
93 disp_quant_factor_, baseline_error_, good_baseline_);
94 bounding_box_.
print();
99 FCOORD baseline_dir(baseline_pt2_ - baseline_pt1_);
100 double angle = baseline_dir.
angle();
103 return fmod(angle + M_PI * 1.5, M_PI) - M_PI * 0.5;
110 float x = (
MAX(bounding_box_.
left(), other.bounding_box_.
left()) +
111 MIN(bounding_box_.
right(), other.bounding_box_.
right())) / 2.0f;
116 return PerpDistanceFromBaseline(pt) + other.PerpDistanceFromBaseline(pt);
122 float middle_x = (bounding_box_.
left() + bounding_box_.
right()) / 2.0f;
124 return direction * middle_pos / direction.
length();
130 double denominator = baseline_pt2_.
x() - baseline_pt1_.
x();
131 if (denominator == 0.0)
132 return (baseline_pt1_.
y() + baseline_pt2_.
y()) / 2.0;
133 return baseline_pt1_.
y() +
134 (x - baseline_pt1_.
x()) * (baseline_pt2_.
y() - baseline_pt1_.
y()) /
147 BLOBNBOX_IT blob_it(blobs_);
149 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
153 int x_middle = (box.
left() + box.
right()) / 2;
155 if (box.
bottom() < kDebugYCoord && box.
top() > kDebugYCoord) {
156 tprintf(
"Box bottom = %d, baseline pos=%d for box at:",
166 baseline_error_ = fitter_.
Fit(&pt1, &pt2);
169 if (baseline_error_ > max_baseline_error_ &&
175 if (error < baseline_error_ / 2.0) {
176 baseline_error_ = error;
184 debug = bounding_box_.
bottom() < kDebugYCoord &&
185 bounding_box_.
top() > kDebugYCoord
191 double target_offset = direction * pt1;
192 good_baseline_ =
false;
193 FitConstrainedIfBetter(debug, direction, 0.0, target_offset);
199 if (fabs(angle) > M_PI * 0.25) {
202 baseline_pt2_ = baseline_pt1_ +
FCOORD(1.0f, llsq.
m());
205 double c = llsq.
c(m);
206 baseline_error_ = llsq.
rms(m, c);
207 good_baseline_ =
false;
209 return good_baseline_;
216 SetupBlobDisplacements(direction);
217 if (displacement_modes_.
empty())
220 if (bounding_box_.
bottom() < kDebugYCoord &&
221 bounding_box_.
top() > kDebugYCoord && debug < 3)
224 FitConstrainedIfBetter(debug, direction, 0.0, displacement_modes_[0]);
232 double line_offset) {
233 if (blobs_->empty()) {
236 bounding_box_.
print();
241 double best_error = 0.0;
243 for (
int i = 0; i < displacement_modes_.
size(); ++i) {
244 double blob_y = displacement_modes_[i];
248 tprintf(
"Mode at %g has error %g from model \n", blob_y, error);
250 if (best_index < 0 || error < best_error) {
257 double model_margin = max_baseline_error_ - best_error;
258 if (best_index >= 0 && model_margin > 0.0) {
261 double perp_disp =
PerpDisp(direction);
262 double shift = displacement_modes_[best_index] - perp_disp;
263 if (fabs(shift) > max_baseline_error_) {
265 tprintf(
"Attempting linespacing model fit with mode %g to row at:",
266 displacement_modes_[best_index]);
267 bounding_box_.
print();
269 FitConstrainedIfBetter(debug, direction, model_margin,
270 displacement_modes_[best_index]);
271 }
else if (debug > 1) {
272 tprintf(
"Linespacing model only moves current line by %g for row at:",
274 bounding_box_.
print();
276 }
else if (debug > 1) {
277 tprintf(
"Linespacing model not close enough to any mode for row at:");
278 bounding_box_.
print();
280 return fmod(
PerpDisp(direction), line_spacing);
293 BLOBNBOX_IT blob_it(blobs_);
297 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
301 if (box.
bottom() < kDebugYCoord && box.
top() > kDebugYCoord) debug =
true;
305 double offset = direction * blob_pos;
309 tprintf(
"Displacement %g for blob at:", offset);
318 for (
int i = 0; i < perp_blob_dists.
size(); ++i) {
325 for (
int i = 0; i < scaled_modes.
size(); ++i) {
326 tprintf(
"Top mode = %g * %d\n",
327 scaled_modes[i].key * disp_quant_factor_, scaled_modes[i].data);
331 for (
int i = 0; i < scaled_modes.
size(); ++i)
332 displacement_modes_.
push_back(disp_quant_factor_ * scaled_modes[i].key);
345 void BaselineRow::FitConstrainedIfBetter(
int debug,
347 double cheat_allowance,
348 double target_offset) {
349 double halfrange = fit_halfrange_ * direction.
length();
350 double min_dist = target_offset - halfrange;
351 double max_dist = target_offset + halfrange;
353 double new_error = fitter_.
ConstrainedFit(direction, min_dist, max_dist,
354 debug > 2, &line_pt);
356 new_error -= cheat_allowance;
358 double new_angle = direction.
angle();
360 tprintf(
"Constrained error = %g, original = %g",
361 new_error, baseline_error_);
362 tprintf(
" angles = %g, %g, delta=%g vs threshold %g\n",
363 old_angle, new_angle,
366 bool new_good_baseline = new_error <= max_baseline_error_ &&
373 if (new_error <= baseline_error_ ||
374 (!good_baseline_ && new_good_baseline) ||
376 baseline_error_ = new_error;
377 baseline_pt1_ = line_pt;
378 baseline_pt2_ = baseline_pt1_ +
direction;
379 good_baseline_ = new_good_baseline;
381 tprintf(
"Replacing with constrained baseline, good = %d\n",
384 }
else if (debug > 1) {
385 tprintf(
"Keeping old baseline\n");
391 double BaselineRow::PerpDistanceFromBaseline(
const FCOORD& pt)
const {
392 FCOORD baseline_vector(baseline_pt2_ - baseline_pt1_);
393 FCOORD offset_vector(pt - baseline_pt1_);
394 double distance = baseline_vector * offset_vector;
395 return sqrt(distance * distance / baseline_vector.
sqlength());
399 void BaselineRow::ComputeBoundingBox() {
400 BLOBNBOX_IT it(blobs_);
402 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
403 box += it.data()->bounding_box();
410 : block_(block), debug_level_(debug_level), non_text_block_(non_text),
411 good_skew_angle_(false), skew_angle_(0.0),
412 line_spacing_(block->line_spacing), line_offset_(0.0), model_error_(0.0) {
413 TO_ROW_IT row_it(block_->
get_rows());
414 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
424 double line_offset) {
426 int multiple =
IntCastRounded((perp_disp - line_offset) / line_spacing);
427 double model_y = line_spacing * multiple + line_offset;
428 return fabs(perp_disp - model_y);
436 if (non_text_block_)
return false;
438 for (
int r = 0; r < rows_.size(); ++r) {
444 if (debug_level_ > 1)
448 if (!angles.
empty()) {
450 good_skew_angle_ =
true;
453 good_skew_angle_ =
false;
455 if (debug_level_ > 0) {
456 tprintf(
"Initial block skew angle = %g, good = %d\n",
457 skew_angle_, good_skew_angle_);
459 return good_skew_angle_;
465 if (non_text_block_)
return;
466 if (!good_skew_angle_) skew_angle_ = default_block_skew;
467 if (debug_level_ > 0)
468 tprintf(
"Adjusting block to skew angle %g\n", skew_angle_);
470 for (
int r = 0; r < rows_.size(); ++r) {
473 if (debug_level_ > 1)
476 if (rows_.size() < 3 || !ComputeLineSpacing())
483 line_spacing_, line_offset_);
484 for (
int r = 1; r < rows_.size(); ++r) {
486 line_spacing_, line_offset_);
487 if (error < best_error) {
493 double offset = line_offset_;
494 for (
int r = best_row + 1; r < rows_.size(); ++r) {
495 offset = rows_[r]->AdjustBaselineToGrid(debug_level_, direction,
496 line_spacing_, offset);
498 offset = line_offset_;
499 for (
int r = best_row - 1; r >= 0; --r) {
500 offset = rows_[r]->AdjustBaselineToGrid(debug_level_, direction,
501 line_spacing_, offset);
507 if (line_spacing_ > 0.0) {
510 if (min_spacing < block_->line_size)
517 TO_ROW_IT row_it(block_->
get_rows());
518 for (
int r = 0; r < rows_.size(); ++r, row_it.forward()) {
520 TO_ROW* to_row = row_it.data();
534 if (non_text_block_)
return;
538 FCOORD rotation(1.0f, 0.0f);
539 double gradient = tan(skew_angle_);
550 bool show_final_rows,
552 double gradient = tan(skew_angle_);
553 FCOORD rotation(1.0f, 0.0f);
555 if (enable_splines) {
560 TO_ROW_IT row_it = block_->
get_rows();
561 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
562 TO_ROW* row = row_it.data();
563 int32_t xstarts[2] = { block_box.
left(), block_box.
right() };
564 double coeffs[3] = { 0.0, row->
line_m(), row->
line_c() };
579 #ifndef GRAPHICS_DISABLED 580 if (non_text_block_)
return;
581 double gradient = tan(skew_angle_);
582 FCOORD rotation(1.0f, 0.0f);
586 TO_ROW_IT row_it = block_->
get_rows();
587 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
597 if (block_->
blobs.length() > 0)
598 tprintf(
"%d blobs discarded as noise\n", block_->
blobs.length());
604 if (non_text_block_)
return;
605 TO_ROW_IT row_it = block_->
get_rows();
606 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
607 row_it.data()->baseline.plot(pix_in);
617 bool BaselineBlock::ComputeLineSpacing() {
620 ComputeBaselinePositions(direction, &row_positions);
621 if (row_positions.
size() < 2)
return false;
622 EstimateLineSpacing();
623 RefineLineSpacing(row_positions);
626 int non_trivial_gaps = 0;
627 int fitting_gaps = 0;
628 for (
int i = 1; i < row_positions.
size(); ++i) {
629 double row_gap = fabs(row_positions[i - 1] - row_positions[i]);
630 if (row_gap > max_baseline_error) {
632 if (fabs(row_gap - line_spacing_) <= max_baseline_error)
636 if (debug_level_ > 0) {
637 tprintf(
"Spacing %g, in %d rows, %d gaps fitted out of %d non-trivial\n",
638 line_spacing_, row_positions.
size(), fitting_gaps,
652 void BaselineBlock::ComputeBaselinePositions(
const FCOORD& direction,
655 for (
int r = 0; r < rows_.size(); ++r) {
658 float x_middle = (row_box.
left() + row_box.
right()) / 2.0f;
660 float offset = direction * row_pos;
667 void BaselineBlock::EstimateLineSpacing() {
669 for (
int r = 0; r < rows_.size(); ++r) {
676 for (r2 = r + 1; r2 < rows_.size() &&
679 if (r2 < rows_.size()) {
689 if (!spacings.
empty()) {
691 if (debug_level_ > 1)
692 tprintf(
"Estimate of linespacing = %g\n", line_spacing_);
701 double spacings[3], offsets[3], errors[3];
703 errors[0] = FitLineSpacingModel(positions, line_spacing_,
704 &spacings[0], &offsets[0], &index_range);
705 if (index_range > 1) {
706 double spacing_plus = line_spacing_ / (1.0 + 1.0 / index_range);
708 errors[1] = FitLineSpacingModel(positions, spacing_plus,
709 &spacings[1], &offsets[1], NULL);
710 double spacing_minus = line_spacing_ / (1.0 - 1.0 / index_range);
711 errors[2] = FitLineSpacingModel(positions, spacing_minus,
712 &spacings[2], &offsets[2], NULL);
713 for (
int i = 1; i <= 2; ++i) {
714 if (errors[i] < errors[0]) {
715 spacings[0] = spacings[i];
716 offsets[0] = offsets[i];
717 errors[0] = errors[i];
721 if (spacings[0] > 0.0) {
722 line_spacing_ = spacings[0];
723 line_offset_ = offsets[0];
724 model_error_ = errors[0];
725 if (debug_level_ > 0) {
726 tprintf(
"Final linespacing model = %g + offset %g, error %g\n",
727 line_spacing_, line_offset_, model_error_);
737 double BaselineBlock::FitLineSpacingModel(
739 double* m_out,
double* c_out,
int* index_delta) {
740 if (m_in == 0.0f || positions.
size() < 2) {
743 if (index_delta != NULL) *index_delta = 0;
748 for (
int i = 0; i < positions.
size(); ++i)
749 offsets.
push_back(fmod(positions[i], m_in));
754 int min_index = INT32_MAX;
755 int max_index = -INT32_MAX;
756 for (
int i = 0; i < positions.
size(); ++i) {
757 double y_pos = positions[i];
760 llsq.
add(row_index, y_pos);
766 for (
int i = 0; i < positions.
size(); ++i)
767 offsets.
push_back(fmod(positions[i], *m_out));
769 if (debug_level_ > 2) {
770 for (
int i = 0; i < offsets.
size(); ++i)
771 tprintf(
"%d: %g\n", i, offsets[i]);
774 if (debug_level_ > 1) {
775 tprintf(
"Median offset = %g, compared to mean of %g.\n",
776 *c_out, llsq.
c(*m_out));
779 if (index_delta != NULL)
780 *index_delta = max_index - min_index;
783 double rms_error = llsq.
rms(*m_out, llsq.
c(*m_out));
784 if (debug_level_ > 1) {
785 tprintf(
"Linespacing of y=%g x + %g improved to %g x + %g, rms=%g\n",
786 m_in, median_offset, *m_out, *c_out, rms_error);
792 TO_BLOCK_LIST* blocks)
793 : page_skew_(page_skew), debug_level_(debug_level) {
794 TO_BLOCK_IT it(blocks);
795 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
805 bool non_text = pb != NULL && !pb->
IsText();
806 blocks_.push_back(
new BaselineBlock(debug_level_, non_text, to_block));
818 for (
int i = 0; i < blocks_.size(); ++i) {
820 if (debug_level_ > 0)
821 tprintf(
"Fitting initial baselines...\n");
827 double default_block_skew = page_skew_.
angle();
828 if (!block_skew_angles.
empty()) {
831 if (debug_level_ > 0) {
832 tprintf(
"Page skew angle = %g\n", default_block_skew);
836 for (
int i = 0; i < blocks_.size(); ++i) {
851 bool show_final_rows,
853 for (
int i = 0; i < blocks_.size(); ++i) {
858 if (show_final_rows) {
void FitBaselineSplines(bool enable_splines, bool show_final_rows, Textord *textord)
const double kOffsetQuantizationFactor
void DrawPixSpline(Pix *pix_in)
float angle() const
find angle
void draw_meanlines(TO_BLOCK *block, float gradient, int32_t left, ScrollView::Color colour, FCOORD rotation)
void EstimateBaselinePosition()
int baseline_position() const
bool FitBaselinesAndFindSkew(bool use_box_bottoms)
void set_line(float new_m, float new_c, float new_error)
bool major_x_overlap(const TBOX &box) const
double ConstrainedFit(const FCOORD &direction, double min_dist, double max_dist, bool debug, ICOORD *line_pt)
void compute_row_xheight(TO_ROW *row, const FCOORD &rotation, float gradient, int block_line_size)
void ParallelizeBaselines(double default_block_skew)
void PrepareForSplineFitting(ICOORD page_tr, bool remove_noise)
void ComputeStraightBaselines(bool use_box_bottoms)
FCOORD classify_rotation() const
void SetupBlockParameters() const
void vigorous_noise_removal(TO_BLOCK *block)
void plot_parallel_row(TO_ROW *row, float gradient, int32_t left, ScrollView::Color colour, FCOORD rotation)
int choose_nth_item(int target_index)
POLY_BLOCK * poly_block() const
void DrawFinalRows(const ICOORD &page_tr)
bool FitBaseline(bool use_box_bottoms)
const double kMinFittingLinespacings
FCOORD mean_point() const
double SpaceBetween(const BaselineRow &other) const
void restore_underlined_blobs(TO_BLOCK *block)
const TBOX & bounding_box() const
int direction(EDGEPT *point)
const int kMaxDisplacementsModes
const TBOX & bounding_box() const
ScrollView * create_to_win(ICOORD page_tr)
float length() const
find length
BaselineDetect(int debug_level, const FCOORD &page_skew, TO_BLOCK_LIST *blocks)
void make_spline_rows(TO_BLOCK *block, float gradient, BOOL8 testing_on)
int top_n_modes(int max_modes, GenericVector< tesseract::KDPairInc< float, int > > *modes) const
void set_xheight(int32_t height)
set char size
double PerpDisp(const FCOORD &direction) const
double StraightYAtX(double x) const
static double SpacingModelError(double perp_disp, double line_spacing, double line_offset)
BaselineRow(double line_size, TO_ROW *to_row)
const double kMaxBaselineError
void Add(const ICOORD &pt)
const double kMaxBlobSizeMultiple
EXTERN bool textord_restore_underlines
BaselineBlock(int debug_level, bool non_text, TO_BLOCK *block)
const double kMaxBaselineError
void set_parallel_line(float gradient, float new_c, float new_error)
void add(int32_t value, int32_t count)
T MedianOfCircularValues(T modulus, GenericVector< T > *v)
void bounding_box(ICOORD &bottom_left, ICOORD &top_right) const
get box
const double kFitHalfrangeFactor
float sqlength() const
find sq length
double rms(double m, double c) const
int IntCastRounded(double x)
void AdjustBaselineToParallel(int debug, const FCOORD &direction)
bool SufficientPointsForIndependentFit() const
const double kMaxSkewDeviation
double BaselineAngle() const
double Fit(ICOORD *pt1, ICOORD *pt2)
double AdjustBaselineToGrid(int debug, const FCOORD &direction, double line_spacing, double line_offset)
int blob_x_order(const void *item1, const void *item2)
void SetupOldLineParameters(TO_ROW *row) const
void UpdateRange(const T1 &x, T2 *lower_bound, T2 *upper_bound)
void ComputeBaselineSplinesAndXheights(const ICOORD &page_tr, bool enable_splines, bool remove_noise, bool show_final_rows, Textord *textord)
double skew_angle() const
void compute_block_xheight(TO_BLOCK *block, float gradient)
void add(double x, double y)
void plot_blob_list(ScrollView *win, BLOBNBOX_LIST *list, ScrollView::Color body_colour, ScrollView::Color child_colour)
void pre_associate_blobs(ICOORD page_tr, TO_BLOCK *block, FCOORD rotation, BOOL8 testing_on)
void separate_underlines(TO_BLOCK *block, float gradient, FCOORD rotation, BOOL8 testing_on)