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_);
295 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
299 if (box.
bottom() < kDebugYCoord && box.
top() > kDebugYCoord) debug =
true;
303 double offset = direction * blob_pos;
306 tprintf(
"Displacement %g for blob at:", offset);
314 for (
int i = 0; i < perp_blob_dists.
size(); ++i) {
315 dist_stats.add(
IntCastRounded(perp_blob_dists[i] / disp_quant_factor_), 1);
320 for (
int i = 0; i < scaled_modes.
size(); ++i) {
321 tprintf(
"Top mode = %g * %d\n",
322 scaled_modes[i].key * disp_quant_factor_, scaled_modes[i].data);
325 for (
int i = 0; i < scaled_modes.
size(); ++i)
326 displacement_modes_.
push_back(disp_quant_factor_ * scaled_modes[i].key);
339 void BaselineRow::FitConstrainedIfBetter(
int debug,
341 double cheat_allowance,
342 double target_offset) {
343 double halfrange = fit_halfrange_ * direction.
length();
344 double min_dist = target_offset - halfrange;
345 double max_dist = target_offset + halfrange;
347 double new_error = fitter_.
ConstrainedFit(direction, min_dist, max_dist,
348 debug > 2, &line_pt);
350 new_error -= cheat_allowance;
352 double new_angle = direction.
angle();
354 tprintf(
"Constrained error = %g, original = %g",
355 new_error, baseline_error_);
356 tprintf(
" angles = %g, %g, delta=%g vs threshold %g\n",
357 old_angle, new_angle,
360 bool new_good_baseline = new_error <= max_baseline_error_ &&
367 if (new_error <= baseline_error_ ||
368 (!good_baseline_ && new_good_baseline) ||
370 baseline_error_ = new_error;
371 baseline_pt1_ = line_pt;
372 baseline_pt2_ = baseline_pt1_ +
direction;
373 good_baseline_ = new_good_baseline;
375 tprintf(
"Replacing with constrained baseline, good = %d\n",
378 }
else if (debug > 1) {
379 tprintf(
"Keeping old baseline\n");
385 double BaselineRow::PerpDistanceFromBaseline(
const FCOORD& pt)
const {
386 FCOORD baseline_vector(baseline_pt2_ - baseline_pt1_);
387 FCOORD offset_vector(pt - baseline_pt1_);
388 double distance = baseline_vector * offset_vector;
389 return sqrt(distance * distance / baseline_vector.sqlength());
393 void BaselineRow::ComputeBoundingBox() {
394 BLOBNBOX_IT it(blobs_);
396 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
397 box += it.data()->bounding_box();
404 : block_(block), debug_level_(debug_level), non_text_block_(non_text),
405 good_skew_angle_(false), skew_angle_(0.0),
406 line_spacing_(block->line_spacing), line_offset_(0.0), model_error_(0.0) {
407 TO_ROW_IT row_it(block_->
get_rows());
408 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
418 double line_offset) {
420 int multiple =
IntCastRounded((perp_disp - line_offset) / line_spacing);
421 double model_y = line_spacing * multiple + line_offset;
422 return fabs(perp_disp - model_y);
430 if (non_text_block_)
return false;
432 for (
int r = 0; r < rows_.size(); ++r) {
438 if (debug_level_ > 1)
442 if (!angles.
empty()) {
444 good_skew_angle_ =
true;
447 good_skew_angle_ =
false;
449 if (debug_level_ > 0) {
450 tprintf(
"Initial block skew angle = %g, good = %d\n",
451 skew_angle_, good_skew_angle_);
453 return good_skew_angle_;
459 if (non_text_block_)
return;
460 if (!good_skew_angle_) skew_angle_ = default_block_skew;
461 if (debug_level_ > 0)
462 tprintf(
"Adjusting block to skew angle %g\n", skew_angle_);
464 for (
int r = 0; r < rows_.size(); ++r) {
467 if (debug_level_ > 1)
470 if (rows_.size() < 3 || !ComputeLineSpacing())
477 line_spacing_, line_offset_);
478 for (
int r = 1; r < rows_.size(); ++r) {
480 line_spacing_, line_offset_);
481 if (error < best_error) {
487 double offset = line_offset_;
488 for (
int r = best_row + 1; r < rows_.size(); ++r) {
489 offset = rows_[r]->AdjustBaselineToGrid(debug_level_, direction,
490 line_spacing_, offset);
492 offset = line_offset_;
493 for (
int r = best_row - 1; r >= 0; --r) {
494 offset = rows_[r]->AdjustBaselineToGrid(debug_level_, direction,
495 line_spacing_, offset);
501 if (line_spacing_ > 0.0) {
504 if (min_spacing < block_->line_size)
511 TO_ROW_IT row_it(block_->
get_rows());
512 for (
int r = 0; r < rows_.size(); ++r, row_it.forward()) {
514 TO_ROW* to_row = row_it.data();
528 if (non_text_block_)
return;
532 FCOORD rotation(1.0f, 0.0f);
533 double gradient = tan(skew_angle_);
544 bool show_final_rows,
546 double gradient = tan(skew_angle_);
547 FCOORD rotation(1.0f, 0.0f);
549 if (enable_splines) {
554 TO_ROW_IT row_it = block_->
get_rows();
555 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
556 TO_ROW* row = row_it.data();
558 double coeffs[3] = { 0.0, row->
line_m(), row->
line_c() };
573 #ifndef GRAPHICS_DISABLED
574 if (non_text_block_)
return;
575 double gradient = tan(skew_angle_);
576 FCOORD rotation(1.0f, 0.0f);
580 TO_ROW_IT row_it = block_->
get_rows();
581 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
591 if (block_->
blobs.length() > 0)
592 tprintf(
"%d blobs discarded as noise\n", block_->
blobs.length());
598 if (non_text_block_)
return;
599 TO_ROW_IT row_it = block_->
get_rows();
600 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
601 row_it.data()->baseline.plot(pix_in);
611 bool BaselineBlock::ComputeLineSpacing() {
614 ComputeBaselinePositions(direction, &row_positions);
615 if (row_positions.
size() < 2)
return false;
616 EstimateLineSpacing();
617 RefineLineSpacing(row_positions);
620 int non_trivial_gaps = 0;
621 int fitting_gaps = 0;
622 for (
int i = 1; i < row_positions.
size(); ++i) {
623 double row_gap = fabs(row_positions[i - 1] - row_positions[i]);
624 if (row_gap > max_baseline_error) {
626 if (fabs(row_gap - line_spacing_) <= max_baseline_error)
630 if (debug_level_ > 0) {
631 tprintf(
"Spacing %g, in %d rows, %d gaps fitted out of %d non-trivial\n",
632 line_spacing_, row_positions.
size(), fitting_gaps,
646 void BaselineBlock::ComputeBaselinePositions(
const FCOORD& direction,
649 for (
int r = 0; r < rows_.size(); ++r) {
650 BaselineRow* row = rows_[r];
651 const TBOX& row_box = row->bounding_box();
652 float x_middle = (row_box.
left() + row_box.
right()) / 2.0f;
653 FCOORD row_pos(x_middle, static_cast<float>(row->StraightYAtX(x_middle)));
654 float offset = direction * row_pos;
661 void BaselineBlock::EstimateLineSpacing() {
663 for (
int r = 0; r < rows_.size(); ++r) {
664 BaselineRow* row = rows_[r];
666 if (fabs(row->BaselineAngle()) > M_PI * 0.25)
continue;
668 const TBOX& row_box = row->bounding_box();
670 for (r2 = r + 1; r2 < rows_.size() &&
673 if (r2 < rows_.size()) {
674 BaselineRow* row2 = rows_[r2];
676 if (fabs(row2->BaselineAngle()) > M_PI * 0.25)
continue;
677 float spacing = row->SpaceBetween(*row2);
683 if (!spacings.
empty()) {
685 if (debug_level_ > 1)
686 tprintf(
"Estimate of linespacing = %g\n", line_spacing_);
695 double spacings[3], offsets[3], errors[3];
697 errors[0] = FitLineSpacingModel(positions, line_spacing_,
698 &spacings[0], &offsets[0], &index_range);
699 if (index_range > 1) {
700 double spacing_plus = line_spacing_ / (1.0 + 1.0 / index_range);
702 errors[1] = FitLineSpacingModel(positions, spacing_plus,
703 &spacings[1], &offsets[1],
NULL);
704 double spacing_minus = line_spacing_ / (1.0 - 1.0 / index_range);
705 errors[2] = FitLineSpacingModel(positions, spacing_minus,
706 &spacings[2], &offsets[2],
NULL);
707 for (
int i = 1; i <= 2; ++i) {
708 if (errors[i] < errors[0]) {
709 spacings[0] = spacings[i];
710 offsets[0] = offsets[i];
711 errors[0] = errors[i];
715 if (spacings[0] > 0.0) {
716 line_spacing_ = spacings[0];
717 line_offset_ = offsets[0];
718 model_error_ = errors[0];
719 if (debug_level_ > 0) {
720 tprintf(
"Final linespacing model = %g + offset %g, error %g\n",
721 line_spacing_, line_offset_, model_error_);
731 double BaselineBlock::FitLineSpacingModel(
733 double* m_out,
double* c_out,
int* index_delta) {
734 if (m_in == 0.0f || positions.
size() < 2) {
737 if (index_delta !=
NULL) *index_delta = 0;
742 for (
int i = 0; i < positions.
size(); ++i)
743 offsets.
push_back(fmod(positions[i], m_in));
750 for (
int i = 0; i < positions.
size(); ++i) {
751 double y_pos = positions[i];
754 llsq.
add(row_index, y_pos);
760 for (
int i = 0; i < positions.
size(); ++i)
761 offsets.
push_back(fmod(positions[i], *m_out));
763 if (debug_level_ > 2) {
764 for (
int i = 0; i < offsets.
size(); ++i)
765 tprintf(
"%d: %g\n", i, offsets[i]);
768 if (debug_level_ > 1) {
769 tprintf(
"Median offset = %g, compared to mean of %g.\n",
770 *c_out, llsq.
c(*m_out));
773 if (index_delta !=
NULL)
774 *index_delta = max_index - min_index;
777 double rms_error = llsq.
rms(*m_out, llsq.
c(*m_out));
778 if (debug_level_ > 1) {
779 tprintf(
"Linespacing of y=%g x + %g improved to %g x + %g, rms=%g\n",
780 m_in, median_offset, *m_out, *c_out, rms_error);
787 TO_BLOCK_LIST* blocks)
788 : page_skew_(page_skew), debug_level_(debug_level), pix_debug_(
NULL),
789 debug_file_prefix_(
"") {
790 TO_BLOCK_IT it(blocks);
791 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
802 blocks_.push_back(
new BaselineBlock(debug_level_, non_text, to_block));
807 pixDestroy(&pix_debug_);
815 for (
int i = 0; i < blocks_.size(); ++i) {
817 if (debug_level_ > 0)
818 tprintf(
"Fitting initial baselines...\n");
824 double default_block_skew = page_skew_.
angle();
825 if (!block_skew_angles.
empty()) {
828 if (debug_level_ > 0) {
829 tprintf(
"Page skew angle = %g\n", default_block_skew);
833 for (
int i = 0; i < blocks_.size(); ++i) {
848 bool show_final_rows,
850 Pix* pix_spline = pix_debug_ ? pixConvertTo32(pix_debug_) :
NULL;
851 for (
int i = 0; i < blocks_.size(); ++i) {
858 if (show_final_rows) {
864 STRING outfile_name = debug_file_prefix_ +
"_spline.png";
865 pixWrite(outfile_name.
string(), pix_spline, IFF_PNG);
866 pixDestroy(&pix_spline);
871 pixDestroy(&pix_debug_);
872 pix_debug_ = pixClone(pixIn);
873 debug_file_prefix_ = output_path;
T MedianOfCircularValues(T modulus, GenericVector< T > *v)
ScrollView * create_to_win(ICOORD page_tr)
void DrawPixSpline(Pix *pix_in)
double BaselineAngle() const
void set_parallel_line(float gradient, float new_c, float new_error)
void compute_block_xheight(TO_BLOCK *block, float gradient)
BaselineDetect(int debug_level, const FCOORD &page_skew, TO_BLOCK_LIST *blocks)
int direction(EDGEPT *point)
void separate_underlines(TO_BLOCK *block, float gradient, FCOORD rotation, BOOL8 testing_on)
void restore_underlined_blobs(TO_BLOCK *block)
void FitBaselineSplines(bool enable_splines, bool show_final_rows, Textord *textord)
int blob_x_order(const void *item1, const void *item2)
BaselineBlock(int debug_level, bool non_text, TO_BLOCK *block)
const double kMaxSkewDeviation
double AdjustBaselineToGrid(int debug, const FCOORD &direction, double line_spacing, double line_offset)
bool SufficientPointsForIndependentFit() const
void plot_parallel_row(TO_ROW *row, float gradient, inT32 left, ScrollView::Color colour, FCOORD rotation)
void set_xheight(inT32 height)
set char size
void ParallelizeBaselines(double default_block_skew)
void DrawFinalRows(const ICOORD &page_tr)
void draw_meanlines(TO_BLOCK *block, float gradient, inT32 left, ScrollView::Color colour, FCOORD rotation)
void PrepareForSplineFitting(ICOORD page_tr, bool remove_noise)
void UpdateRange(const T1 &x, T2 *lower_bound, T2 *upper_bound)
double rms(double m, double c) const
double skew_angle() const
void SetDebugImage(Pix *pixIn, const STRING &output_path)
bool FitBaseline(bool use_box_bottoms)
void plot_blob_list(ScrollView *win, BLOBNBOX_LIST *list, ScrollView::Color body_colour, ScrollView::Color child_colour)
const int kMaxDisplacementsModes
void SetupOldLineParameters(TO_ROW *row) const
float angle() const
find angle
void set_line(float new_m, float new_c, float new_error)
void ComputeStraightBaselines(bool use_box_bottoms)
void pre_associate_blobs(ICOORD page_tr, TO_BLOCK *block, FCOORD rotation, BOOL8 testing_on)
void EstimateBaselinePosition()
FCOORD classify_rotation() const
const double kOffsetQuantizationFactor
static double SpacingModelError(double perp_disp, double line_spacing, double line_offset)
void ComputeBaselineSplinesAndXheights(const ICOORD &page_tr, bool enable_splines, bool remove_noise, bool show_final_rows, Textord *textord)
int baseline_position() const
int choose_nth_item(int target_index)
float length() const
find length
void bounding_box(ICOORD &bottom_left, ICOORD &top_right) const
get box
const double kMaxBaselineError
void add(double x, double y)
bool major_x_overlap(const TBOX &box) const
void compute_row_xheight(TO_ROW *row, const FCOORD &rotation, float gradient, int block_line_size)
EXTERN bool textord_restore_underlines
bool FitBaselinesAndFindSkew(bool use_box_bottoms)
void Add(const ICOORD &pt)
const double kMaxBaselineError
FCOORD mean_point() const
const double kMaxBlobSizeMultiple
void vigorous_noise_removal(TO_BLOCK *block)
int IntCastRounded(double x)
double Fit(ICOORD *pt1, ICOORD *pt2)
double PerpDisp(const FCOORD &direction) const
double StraightYAtX(double x) const
double SpaceBetween(const BaselineRow &other) const
const TBOX & bounding_box() const
const char * string() const
POLY_BLOCK * poly_block() const
double ConstrainedFit(const FCOORD &direction, double min_dist, double max_dist, bool debug, ICOORD *line_pt)
const double kMinFittingLinespacings
void make_spline_rows(TO_BLOCK *block, float gradient, BOOL8 testing_on)
const double kFitHalfrangeFactor
void AdjustBaselineToParallel(int debug, const FCOORD &direction)
void SetupBlockParameters() const
BaselineRow(double line_size, TO_ROW *to_row)