21 #define _USE_MATH_DEFINES 25 #include "config_auto.h" 33 #include "allheaders.h" 68 : blobs_(to_row->blob_list()),
69 baseline_pt1_(0.0f, 0.0f), baseline_pt2_(0.0f, 0.0f),
70 baseline_error_(0.0), good_baseline_(false) {
84 row->
set_line(gradient, para_c, baseline_error_);
90 tprintf(
"Baseline (%g,%g)->(%g,%g), angle=%g, intercept=%g\n",
91 baseline_pt1_.
x(), baseline_pt1_.
y(),
92 baseline_pt2_.
x(), baseline_pt2_.
y(),
94 tprintf(
"Quant factor=%g, error=%g, good=%d, box:",
95 disp_quant_factor_, baseline_error_, good_baseline_);
96 bounding_box_.
print();
101 FCOORD baseline_dir(baseline_pt2_ - baseline_pt1_);
102 double angle = baseline_dir.
angle();
105 return fmod(angle + M_PI * 1.5, M_PI) - M_PI * 0.5;
112 float x = (std::max(bounding_box_.
left(), other.bounding_box_.
left()) +
113 std::min(bounding_box_.
right(), other.bounding_box_.
right())) / 2.0f;
118 return PerpDistanceFromBaseline(pt) + other.PerpDistanceFromBaseline(pt);
124 float middle_x = (bounding_box_.
left() + bounding_box_.
right()) / 2.0f;
132 double denominator = baseline_pt2_.
x() - baseline_pt1_.
x();
133 if (denominator == 0.0)
134 return (baseline_pt1_.
y() + baseline_pt2_.
y()) / 2.0;
135 return baseline_pt1_.
y() +
136 (x - baseline_pt1_.
x()) * (baseline_pt2_.
y() - baseline_pt1_.
y()) /
149 BLOBNBOX_IT blob_it(blobs_);
151 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
155 int x_middle = (box.
left() + box.
right()) / 2;
157 if (box.
bottom() < kDebugYCoord && box.
top() > kDebugYCoord) {
158 tprintf(
"Box bottom = %d, baseline pos=%d for box at:",
168 baseline_error_ = fitter_.
Fit(&pt1, &pt2);
171 if (baseline_error_ > max_baseline_error_ &&
177 if (error < baseline_error_ / 2.0) {
178 baseline_error_ = error;
186 debug = bounding_box_.
bottom() < kDebugYCoord &&
187 bounding_box_.
top() > kDebugYCoord
194 good_baseline_ =
false;
195 FitConstrainedIfBetter(debug,
direction, 0.0, target_offset);
201 if (fabs(angle) > M_PI * 0.25) {
204 baseline_pt2_ = baseline_pt1_ +
FCOORD(1.0f, llsq.
m());
207 double c = llsq.
c(m);
208 baseline_error_ = llsq.
rms(m, c);
209 good_baseline_ =
false;
211 return good_baseline_;
219 if (displacement_modes_.
empty())
222 if (bounding_box_.
bottom() < kDebugYCoord &&
223 bounding_box_.
top() > kDebugYCoord && debug < 3)
226 FitConstrainedIfBetter(debug,
direction, 0.0, displacement_modes_[0]);
234 double line_offset) {
235 if (blobs_->empty()) {
238 bounding_box_.
print();
243 double best_error = 0.0;
245 for (
int i = 0; i < displacement_modes_.
size(); ++i) {
246 double blob_y = displacement_modes_[i];
250 tprintf(
"Mode at %g has error %g from model \n", blob_y, error);
252 if (best_index < 0 || error < best_error) {
259 double model_margin = max_baseline_error_ - best_error;
260 if (best_index >= 0 && model_margin > 0.0) {
264 double shift = displacement_modes_[best_index] - perp_disp;
265 if (fabs(shift) > max_baseline_error_) {
267 tprintf(
"Attempting linespacing model fit with mode %g to row at:",
268 displacement_modes_[best_index]);
269 bounding_box_.
print();
271 FitConstrainedIfBetter(debug,
direction, model_margin,
272 displacement_modes_[best_index]);
273 }
else if (debug > 1) {
274 tprintf(
"Linespacing model only moves current line by %g for row at:",
276 bounding_box_.
print();
278 }
else if (debug > 1) {
279 tprintf(
"Linespacing model not close enough to any mode for row at:");
280 bounding_box_.
print();
293 double min_dist = FLT_MAX;
294 double max_dist = -FLT_MAX;
295 BLOBNBOX_IT blob_it(blobs_);
299 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
303 if (box.
bottom() < kDebugYCoord && box.
top() > kDebugYCoord) debug =
true;
311 tprintf(
"Displacement %g for blob at:", offset);
320 for (
int i = 0; i < perp_blob_dists.
size(); ++i) {
321 dist_stats.add(
IntCastRounded(perp_blob_dists[i] / disp_quant_factor_), 1);
327 for (
int i = 0; i < scaled_modes.
size(); ++i) {
328 tprintf(
"Top mode = %g * %d\n",
329 scaled_modes[i].key * disp_quant_factor_, scaled_modes[i].data);
333 for (
int i = 0; i < scaled_modes.
size(); ++i)
334 displacement_modes_.
push_back(disp_quant_factor_ * scaled_modes[i].key);
347 void BaselineRow::FitConstrainedIfBetter(
int debug,
349 double cheat_allowance,
350 double target_offset) {
351 double halfrange = fit_halfrange_ *
direction.length();
352 double min_dist = target_offset - halfrange;
353 double max_dist = target_offset + halfrange;
356 debug > 2, &line_pt);
358 new_error -= cheat_allowance;
362 tprintf(
"Constrained error = %g, original = %g",
363 new_error, baseline_error_);
364 tprintf(
" angles = %g, %g, delta=%g vs threshold %g\n",
365 old_angle, new_angle,
368 bool new_good_baseline = new_error <= max_baseline_error_ &&
375 if (new_error <= baseline_error_ ||
376 (!good_baseline_ && new_good_baseline) ||
378 baseline_error_ = new_error;
379 baseline_pt1_ = line_pt;
380 baseline_pt2_ = baseline_pt1_ +
direction;
381 good_baseline_ = new_good_baseline;
383 tprintf(
"Replacing with constrained baseline, good = %d\n",
386 }
else if (debug > 1) {
387 tprintf(
"Keeping old baseline\n");
393 double BaselineRow::PerpDistanceFromBaseline(
const FCOORD& pt)
const {
394 FCOORD baseline_vector(baseline_pt2_ - baseline_pt1_);
395 FCOORD offset_vector(pt - baseline_pt1_);
396 double distance = baseline_vector * offset_vector;
397 return sqrt(distance * distance / baseline_vector.sqlength());
401 void BaselineRow::ComputeBoundingBox() {
402 BLOBNBOX_IT it(blobs_);
404 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
405 box += it.data()->bounding_box();
412 : block_(block), debug_level_(debug_level), non_text_block_(non_text),
413 good_skew_angle_(false), skew_angle_(0.0),
414 line_spacing_(block->line_spacing), line_offset_(0.0), model_error_(0.0) {
415 TO_ROW_IT row_it(block_->
get_rows());
416 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
426 double line_offset) {
428 int multiple =
IntCastRounded((perp_disp - line_offset) / line_spacing);
429 double model_y = line_spacing * multiple + line_offset;
430 return fabs(perp_disp - model_y);
438 if (non_text_block_)
return false;
440 for (
int r = 0; r < rows_.size(); ++r) {
446 if (debug_level_ > 1)
450 if (!angles.
empty()) {
452 good_skew_angle_ =
true;
455 good_skew_angle_ =
false;
457 if (debug_level_ > 0) {
458 tprintf(
"Initial block skew angle = %g, good = %d\n",
459 skew_angle_, good_skew_angle_);
461 return good_skew_angle_;
467 if (non_text_block_)
return;
468 if (!good_skew_angle_) skew_angle_ = default_block_skew;
469 if (debug_level_ > 0)
470 tprintf(
"Adjusting block to skew angle %g\n", skew_angle_);
472 for (
int r = 0; r < rows_.size(); ++r) {
475 if (debug_level_ > 1)
478 if (rows_.size() < 3 || !ComputeLineSpacing())
485 line_spacing_, line_offset_);
486 for (
int r = 1; r < rows_.size(); ++r) {
488 line_spacing_, line_offset_);
489 if (error < best_error) {
495 double offset = line_offset_;
496 for (
int r = best_row + 1; r < rows_.size(); ++r) {
497 offset = rows_[r]->AdjustBaselineToGrid(debug_level_,
direction,
498 line_spacing_, offset);
500 offset = line_offset_;
501 for (
int r = best_row - 1; r >= 0; --r) {
502 offset = rows_[r]->AdjustBaselineToGrid(debug_level_,
direction,
503 line_spacing_, offset);
509 if (line_spacing_ > 0.0) {
511 float min_spacing = std::min(block_->
line_spacing, static_cast<float>(line_spacing_));
512 if (min_spacing < block_->line_size)
519 TO_ROW_IT row_it(block_->
get_rows());
520 for (
int r = 0; r < rows_.size(); ++r, row_it.forward()) {
522 TO_ROW* to_row = row_it.data();
536 if (non_text_block_)
return;
540 FCOORD rotation(1.0f, 0.0f);
541 double gradient = tan(skew_angle_);
552 bool show_final_rows,
554 double gradient = tan(skew_angle_);
555 FCOORD rotation(1.0f, 0.0f);
557 if (enable_splines) {
562 TO_ROW_IT row_it = block_->
get_rows();
563 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
564 TO_ROW* row = row_it.data();
565 int32_t xstarts[2] = { block_box.
left(), block_box.
right() };
566 double coeffs[3] = { 0.0, row->
line_m(), row->
line_c() };
581 #ifndef GRAPHICS_DISABLED 582 if (non_text_block_)
return;
583 double gradient = tan(skew_angle_);
584 FCOORD rotation(1.0f, 0.0f);
588 TO_ROW_IT row_it = block_->
get_rows();
589 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
599 if (block_->
blobs.length() > 0)
600 tprintf(
"%d blobs discarded as noise\n", block_->
blobs.length());
606 if (non_text_block_)
return;
607 TO_ROW_IT row_it = block_->
get_rows();
608 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
609 row_it.data()->baseline.plot(pix_in);
619 bool BaselineBlock::ComputeLineSpacing() {
622 ComputeBaselinePositions(
direction, &row_positions);
623 if (row_positions.
size() < 2)
return false;
624 EstimateLineSpacing();
625 RefineLineSpacing(row_positions);
628 int non_trivial_gaps = 0;
629 int fitting_gaps = 0;
630 for (
int i = 1; i < row_positions.
size(); ++i) {
631 double row_gap = fabs(row_positions[i - 1] - row_positions[i]);
632 if (row_gap > max_baseline_error) {
634 if (fabs(row_gap - line_spacing_) <= max_baseline_error)
638 if (debug_level_ > 0) {
639 tprintf(
"Spacing %g, in %d rows, %d gaps fitted out of %d non-trivial\n",
640 line_spacing_, row_positions.
size(), fitting_gaps,
657 for (
int r = 0; r < rows_.size(); ++r) {
658 BaselineRow* row = rows_[r];
659 const TBOX& row_box = row->bounding_box();
660 float x_middle = (row_box.
left() + row_box.
right()) / 2.0f;
661 FCOORD row_pos(x_middle, static_cast<float>(row->StraightYAtX(x_middle)));
669 void BaselineBlock::EstimateLineSpacing() {
671 for (
int r = 0; r < rows_.size(); ++r) {
672 BaselineRow* row = rows_[r];
674 if (fabs(row->BaselineAngle()) > M_PI * 0.25)
continue;
676 const TBOX& row_box = row->bounding_box();
678 for (r2 = r + 1; r2 < rows_.size() &&
681 if (r2 < rows_.size()) {
682 BaselineRow* row2 = rows_[r2];
684 if (fabs(row2->BaselineAngle()) > M_PI * 0.25)
continue;
685 float spacing = row->SpaceBetween(*row2);
691 if (!spacings.
empty()) {
693 if (debug_level_ > 1)
694 tprintf(
"Estimate of linespacing = %g\n", line_spacing_);
703 double spacings[3], offsets[3], errors[3];
705 errors[0] = FitLineSpacingModel(positions, line_spacing_,
706 &spacings[0], &offsets[0], &index_range);
707 if (index_range > 1) {
708 double spacing_plus = line_spacing_ / (1.0 + 1.0 / index_range);
710 errors[1] = FitLineSpacingModel(positions, spacing_plus,
711 &spacings[1], &offsets[1],
nullptr);
712 double spacing_minus = line_spacing_ / (1.0 - 1.0 / index_range);
713 errors[2] = FitLineSpacingModel(positions, spacing_minus,
714 &spacings[2], &offsets[2],
nullptr);
715 for (
int i = 1; i <= 2; ++i) {
716 if (errors[i] < errors[0]) {
717 spacings[0] = spacings[i];
718 offsets[0] = offsets[i];
719 errors[0] = errors[i];
723 if (spacings[0] > 0.0) {
724 line_spacing_ = spacings[0];
725 line_offset_ = offsets[0];
726 model_error_ = errors[0];
727 if (debug_level_ > 0) {
728 tprintf(
"Final linespacing model = %g + offset %g, error %g\n",
729 line_spacing_, line_offset_, model_error_);
739 double BaselineBlock::FitLineSpacingModel(
741 double* m_out,
double* c_out,
int* index_delta) {
742 if (m_in == 0.0f || positions.
size() < 2) {
745 if (index_delta !=
nullptr) *index_delta = 0;
750 for (
int i = 0; i < positions.
size(); ++i)
751 offsets.
push_back(fmod(positions[i], m_in));
756 int min_index = INT32_MAX;
757 int max_index = -INT32_MAX;
758 for (
int i = 0; i < positions.
size(); ++i) {
759 double y_pos = positions[i];
762 llsq.
add(row_index, y_pos);
768 for (
int i = 0; i < positions.
size(); ++i)
769 offsets.
push_back(fmod(positions[i], *m_out));
771 if (debug_level_ > 2) {
772 for (
int i = 0; i < offsets.
size(); ++i)
773 tprintf(
"%d: %g\n", i, offsets[i]);
776 if (debug_level_ > 1) {
777 tprintf(
"Median offset = %g, compared to mean of %g.\n",
778 *c_out, llsq.
c(*m_out));
781 if (index_delta !=
nullptr)
782 *index_delta = max_index - min_index;
785 double rms_error = llsq.
rms(*m_out, llsq.
c(*m_out));
786 if (debug_level_ > 1) {
787 tprintf(
"Linespacing of y=%g x + %g improved to %g x + %g, rms=%g\n",
788 m_in, median_offset, *m_out, *c_out, rms_error);
794 TO_BLOCK_LIST* blocks)
795 : page_skew_(page_skew), debug_level_(debug_level) {
796 TO_BLOCK_IT it(blocks);
797 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
807 bool non_text = pb !=
nullptr && !pb->
IsText();
808 blocks_.push_back(
new BaselineBlock(debug_level_, non_text, to_block));
817 for (
int i = 0; i < blocks_.size(); ++i) {
819 if (debug_level_ > 0)
820 tprintf(
"Fitting initial baselines...\n");
826 double default_block_skew = page_skew_.
angle();
827 if (!block_skew_angles.
empty()) {
830 if (debug_level_ > 0) {
831 tprintf(
"Page skew angle = %g\n", default_block_skew);
835 for (
int i = 0; i < blocks_.size(); ++i) {
850 bool show_final_rows,
852 for (
int i = 0; i < blocks_.size(); ++i) {
857 if (show_final_rows) {
BaselineBlock(int debug_level, bool non_text, TO_BLOCK *block)
const double kMaxBaselineError
bool FitBaselinesAndFindSkew(bool use_box_bottoms)
void PrepareForSplineFitting(ICOORD page_tr, bool remove_noise)
void pre_associate_blobs(ICOORD page_tr, TO_BLOCK *block, FCOORD rotation, bool testing_on)
void DrawPixSpline(Pix *pix_in)
bool FitBaseline(bool use_box_bottoms)
const int kMaxDisplacementsModes
void compute_row_xheight(TO_ROW *row, const FCOORD &rotation, float gradient, int block_line_size)
void restore_underlined_blobs(TO_BLOCK *block)
void SetupOldLineParameters(TO_ROW *row) const
const double kFitHalfrangeFactor
float angle() const
find angle
int direction(EDGEPT *point)
double PerpDisp(const FCOORD &direction) const
const double kOffsetQuantizationFactor
const double kMinFittingLinespacings
void plot_blob_list(ScrollView *win, BLOBNBOX_LIST *list, ScrollView::Color body_colour, ScrollView::Color child_colour)
double BaselineAngle() const
void draw_meanlines(TO_BLOCK *block, float gradient, int32_t left, ScrollView::Color colour, FCOORD rotation)
const double kMaxBaselineError
void compute_block_xheight(TO_BLOCK *block, float gradient)
int blob_x_order(const void *item1, const void *item2)
double SpaceBetween(const BaselineRow &other) const
void SetupBlockParameters() const
void set_line(float new_m, float new_c, float new_error)
void add(double x, double y)
bool SufficientPointsForIndependentFit() const
FCOORD mean_point() const
void Add(const ICOORD &pt)
void set_parallel_line(float gradient, float new_c, float new_error)
void AdjustBaselineToParallel(int debug, const FCOORD &direction)
BaselineRow(double line_size, TO_ROW *to_row)
void EstimateBaselinePosition()
bool major_x_overlap(const TBOX &box) const
static double SpacingModelError(double perp_disp, double line_spacing, double line_offset)
int IntCastRounded(double x)
FCOORD classify_rotation() const
double AdjustBaselineToGrid(int debug, const FCOORD &direction, double line_spacing, double line_offset)
double skew_angle() const
POLY_BLOCK * poly_block() const
void separate_underlines(TO_BLOCK *block, float gradient, FCOORD rotation, bool testing_on)
int choose_nth_item(int target_index)
double Fit(ICOORD *pt1, ICOORD *pt2)
void FitBaselineSplines(bool enable_splines, bool show_final_rows, Textord *textord)
DLLSYM void tprintf(const char *format,...)
void plot_parallel_row(TO_ROW *row, float gradient, int32_t left, ScrollView::Color colour, FCOORD rotation)
EXTERN bool textord_restore_underlines
BaselineDetect(int debug_level, const FCOORD &page_skew, TO_BLOCK_LIST *blocks)
const double kMaxSkewDeviation
void make_spline_rows(TO_BLOCK *block, float gradient, bool testing_on)
void ComputeStraightBaselines(bool use_box_bottoms)
double rms(double m, double c) const
void set_xheight(int32_t height)
set char size
void bounding_box(ICOORD &bottom_left, ICOORD &top_right) const
get box
void vigorous_noise_removal(TO_BLOCK *block)
const TBOX & bounding_box() const
void ParallelizeBaselines(double default_block_skew)
void DrawFinalRows(const ICOORD &page_tr)
int baseline_position() const
double StraightYAtX(double x) const
void ComputeBaselineSplinesAndXheights(const ICOORD &page_tr, bool enable_splines, bool remove_noise, bool show_final_rows, Textord *textord)
ScrollView * create_to_win(ICOORD page_tr)
T MedianOfCircularValues(T modulus, GenericVector< T > *v)
double ConstrainedFit(const FCOORD &direction, double min_dist, double max_dist, bool debug, ICOORD *line_pt)
const double kMaxBlobSizeMultiple
void UpdateRange(const T1 &x, T2 *lower_bound, T2 *upper_bound)