1 /**********************************************************************
2 * File: oldbasel.cpp (Formerly oldbl.c)
3 * Description: A re-implementation of the old baseline algorithm.
4 * Author: Ray Smith
5 *
6 * (C) Copyright 1993, Hewlett-Packard Ltd.
7 ** Licensed under the Apache License, Version 2.0 (the "License");
8 ** you may not use this file except in compliance with the License.
9 ** You may obtain a copy of the License at
10 ** http://www.apache.org/licenses/LICENSE-2.0
11 ** Unless required by applicable law or agreed to in writing, software
12 ** distributed under the License is distributed on an "AS IS" BASIS,
13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 ** See the License for the specific language governing permissions and
15 ** limitations under the License.
16 *
17 **********************************************************************/
18
19 // Include automatically generated configuration file if running autoconf.
20 #ifdef HAVE_CONFIG_H
21 # include "config_auto.h"
22 #endif
23
24 #include "oldbasel.h"
25
26 #include "ccstruct.h"
27 #include "detlinefit.h"
28 #include "drawtord.h"
29 #include "makerow.h"
30 #include "quadlsq.h"
31 #include "statistc.h"
32 #include "textord.h"
33 #include "tprintf.h"
34
35 #include <cmath>
36 #include <vector> // for std::vector
37
38 #include <algorithm>
39
40 namespace tesseract {
41
42 static BOOL_VAR(textord_really_old_xheight, false, "Use original wiseowl xheight");
43 BOOL_VAR(textord_oldbl_debug, false, "Debug old baseline generation");
44 static BOOL_VAR(textord_debug_baselines, false, "Debug baseline generation");
45 static BOOL_VAR(textord_oldbl_paradef, true, "Use para default mechanism");
46 static BOOL_VAR(textord_oldbl_split_splines, true, "Split stepped splines");
47 static BOOL_VAR(textord_oldbl_merge_parts, true, "Merge suspect partitions");
48 static BOOL_VAR(oldbl_corrfix, true, "Improve correlation of heights");
49 static BOOL_VAR(oldbl_xhfix, false, "Fix bug in modes threshold for xheights");
50 static BOOL_VAR(textord_ocropus_mode, false, "Make baselines for ocropus");
51 static double_VAR(oldbl_xhfract, 0.4, "Fraction of est allowed in calc");
52 static INT_VAR(oldbl_holed_losscount, 10, "Max lost before fallback line used");
53 static double_VAR(oldbl_dot_error_size, 1.26, "Max aspect ratio of a dot");
54 static double_VAR(textord_oldbl_jumplimit, 0.15, "X fraction for new partition");
55
56 #define TURNLIMIT 1 /*min size for turning point */
57 #define X_HEIGHT_FRACTION 0.7 /*x-height/caps height */
58 #define DESCENDER_FRACTION 0.5 /*descender/x-height */
59 #define MIN_ASC_FRACTION 0.20 /*min size of ascenders */
60 #define MIN_DESC_FRACTION 0.25 /*min size of descenders */
61 #define MINASCRISE 2.0 /*min ascender/desc step */
62 #define MAXHEIGHTVARIANCE 0.15 /*accepted variation in x-height */
63 #define MAXHEIGHT 300 /*max blob height */
64 #define MAXOVERLAP 0.1 /*max 10% missed overlap */
65 #define MAXBADRUN 2 /*max non best for failed */
66 #define HEIGHTBUCKETS 200 /* Num of buckets */
67 #define MODENUM 10
68 #define MAXPARTS 6
69 #define SPLINESIZE 23
70
71 #define ABS(x) ((x) < 0 ? (-(x)) : (x))
72
73 /**********************************************************************
74 * make_old_baselines
75 *
76 * Top level function to make baselines the old way.
77 **********************************************************************/
78
make_old_baselines(TO_BLOCK * block,bool testing_on,float gradient)79 void Textord::make_old_baselines(TO_BLOCK *block, // block to do
80 bool testing_on, // correct orientation
81 float gradient) {
82 QSPLINE *prev_baseline; // baseline of previous row
83 TO_ROW *row; // current row
84 TO_ROW_IT row_it = block->get_rows();
85 BLOBNBOX_IT blob_it;
86
87 prev_baseline = nullptr; // nothing yet
88 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
89 row = row_it.data();
90 find_textlines(block, row, 2, nullptr);
91 if (row->xheight <= 0 && prev_baseline != nullptr) {
92 find_textlines(block, row, 2, prev_baseline);
93 }
94 if (row->xheight > 0) { // was a good one
95 prev_baseline = &row->baseline;
96 } else {
97 prev_baseline = nullptr;
98 blob_it.set_to_list(row->blob_list());
99 if (textord_debug_baselines) {
100 tprintf("Row baseline generation failed on row at (%d,%d)\n",
101 blob_it.data()->bounding_box().left(), blob_it.data()->bounding_box().bottom());
102 }
103 }
104 }
105 correlate_lines(block, gradient);
106 block->block->set_xheight(block->xheight);
107 }
108
109 /**********************************************************************
110 * correlate_lines
111 *
112 * Correlate the x-heights and ascender heights of a block to fill-in
113 * the ascender height and descender height for rows without one.
114 * Also fix baselines of rows without a decent fit.
115 **********************************************************************/
116
correlate_lines(TO_BLOCK * block,float gradient)117 void Textord::correlate_lines(TO_BLOCK *block, float gradient) {
118 int rowcount; /*no of rows to do */
119 int rowindex; /*no of row */
120 // iterator
121 TO_ROW_IT row_it = block->get_rows();
122
123 rowcount = row_it.length();
124 if (rowcount == 0) {
125 // default value
126 block->xheight = block->line_size;
127 return; /*none to do */
128 }
129 // array of ptrs
130 std::vector<TO_ROW *> rows(rowcount);
131 rowindex = 0;
132 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
133 // make array
134 rows[rowindex++] = row_it.data();
135 }
136
137 /*try to fix bad lines */
138 correlate_neighbours(block, &rows[0], rowcount);
139
140 if (textord_really_old_xheight || textord_old_xheight) {
141 block->xheight = static_cast<float>(correlate_with_stats(&rows[0], rowcount, block));
142 if (block->xheight <= 0) {
143 block->xheight = block->line_size * tesseract::CCStruct::kXHeightFraction;
144 }
145 if (block->xheight < textord_min_xheight) {
146 block->xheight = (float)textord_min_xheight;
147 }
148 } else {
149 compute_block_xheight(block, gradient);
150 }
151 }
152
153 /**********************************************************************
154 * correlate_neighbours
155 *
156 * Try to fix rows that had a bad spline fit by using neighbours.
157 **********************************************************************/
158
correlate_neighbours(TO_BLOCK * block,TO_ROW ** rows,int rowcount)159 void Textord::correlate_neighbours(TO_BLOCK *block, // block rows are in.
160 TO_ROW **rows, // rows of block.
161 int rowcount) { // no of rows to do.
162 TO_ROW *row; /*current row */
163 int rowindex; /*no of row */
164 int otherrow; /*second row */
165 int upperrow; /*row above to use */
166 int lowerrow; /*row below to use */
167 float biggest;
168
169 for (rowindex = 0; rowindex < rowcount; rowindex++) {
170 row = rows[rowindex]; /*current row */
171 if (row->xheight < 0) {
172 /*quadratic failed */
173 for (otherrow = rowindex - 2;
174 otherrow >= 0 && (rows[otherrow]->xheight < 0.0 ||
175 !row->baseline.overlap(&rows[otherrow]->baseline, MAXOVERLAP));
176 otherrow--) {
177 ;
178 }
179 upperrow = otherrow; /*decent row above */
180 for (otherrow = rowindex + 1;
181 otherrow < rowcount && (rows[otherrow]->xheight < 0.0 ||
182 !row->baseline.overlap(&rows[otherrow]->baseline, MAXOVERLAP));
183 otherrow++) {
184 ;
185 }
186 lowerrow = otherrow; /*decent row below */
187 if (upperrow >= 0) {
188 find_textlines(block, row, 2, &rows[upperrow]->baseline);
189 }
190 if (row->xheight < 0 && lowerrow < rowcount) {
191 find_textlines(block, row, 2, &rows[lowerrow]->baseline);
192 }
193 if (row->xheight < 0) {
194 if (upperrow >= 0) {
195 find_textlines(block, row, 1, &rows[upperrow]->baseline);
196 } else if (lowerrow < rowcount) {
197 find_textlines(block, row, 1, &rows[lowerrow]->baseline);
198 }
199 }
200 }
201 }
202
203 for (biggest = 0.0f, rowindex = 0; rowindex < rowcount; rowindex++) {
204 row = rows[rowindex]; /*current row */
205 if (row->xheight < 0) { /*linear failed */
206 /*make do */
207 row->xheight = -row->xheight;
208 }
209 biggest = std::max(biggest, row->xheight);
210 }
211 }
212
213 /**********************************************************************
214 * correlate_with_stats
215 *
216 * correlate the x-heights and ascender heights of a block to fill-in
217 * the ascender height and descender height for rows without one.
218 **********************************************************************/
219
correlate_with_stats(TO_ROW ** rows,int rowcount,TO_BLOCK * block)220 int Textord::correlate_with_stats(TO_ROW **rows, // rows of block.
221 int rowcount, // no of rows to do.
222 TO_BLOCK *block) {
223 TO_ROW *row; /*current row */
224 int rowindex; /*no of row */
225 float lineheight; /*mean x-height */
226 float ascheight; /*average ascenders */
227 float minascheight; /*min allowed ascheight */
228 int xcount; /*no of samples for xheight */
229 float fullheight; /*mean top height */
230 int fullcount; /*no of samples */
231 float descheight; /*mean descender drop */
232 float mindescheight; /*min allowed descheight */
233 int desccount; /*no of samples */
234
235 /*no samples */
236 xcount = fullcount = desccount = 0;
237 lineheight = ascheight = fullheight = descheight = 0.0;
238 for (rowindex = 0; rowindex < rowcount; rowindex++) {
239 row = rows[rowindex]; /*current row */
240 if (row->ascrise > 0.0) { /*got ascenders? */
241 lineheight += row->xheight; /*average x-heights */
242 ascheight += row->ascrise; /*average ascenders */
243 xcount++;
244 } else {
245 fullheight += row->xheight; /*assume full height */
246 fullcount++;
247 }
248 if (row->descdrop < 0.0) { /*got descenders? */
249 /*average descenders */
250 descheight += row->descdrop;
251 desccount++;
252 }
253 }
254
255 if (xcount > 0 && (!oldbl_corrfix || xcount >= fullcount)) {
256 lineheight /= xcount; /*average x-height */
257 /*average caps height */
258 fullheight = lineheight + ascheight / xcount;
259 /*must be decent size */
260 if (fullheight < lineheight * (1 + MIN_ASC_FRACTION)) {
261 fullheight = lineheight * (1 + MIN_ASC_FRACTION);
262 }
263 } else {
264 fullheight /= fullcount; /*average max height */
265 /*guess x-height */
266 lineheight = fullheight * X_HEIGHT_FRACTION;
267 }
268 if (desccount > 0 && (!oldbl_corrfix || desccount >= rowcount / 2)) {
269 descheight /= desccount; /*average descenders */
270 } else {
271 /*guess descenders */
272 descheight = -lineheight * DESCENDER_FRACTION;
273 }
274
275 if (lineheight > 0.0f) {
276 block->block->set_cell_over_xheight((fullheight - descheight) / lineheight);
277 }
278
279 minascheight = lineheight * MIN_ASC_FRACTION;
280 mindescheight = -lineheight * MIN_DESC_FRACTION;
281 for (rowindex = 0; rowindex < rowcount; rowindex++) {
282 row = rows[rowindex]; /*do each row */
283 row->all_caps = false;
284 if (row->ascrise / row->xheight < MIN_ASC_FRACTION) {
285 /*no ascenders */
286 if (row->xheight >= lineheight * (1 - MAXHEIGHTVARIANCE) &&
287 row->xheight <= lineheight * (1 + MAXHEIGHTVARIANCE)) {
288 row->ascrise = fullheight - lineheight;
289 /*set to average */
290 row->xheight = lineheight;
291
292 } else if (row->xheight >= fullheight * (1 - MAXHEIGHTVARIANCE) &&
293 row->xheight <= fullheight * (1 + MAXHEIGHTVARIANCE)) {
294 row->ascrise = row->xheight - lineheight;
295 /*set to average */
296 row->xheight = lineheight;
297 row->all_caps = true;
298 } else {
299 row->ascrise = (fullheight - lineheight) * row->xheight / fullheight;
300 /*scale it */
301 row->xheight -= row->ascrise;
302 row->all_caps = true;
303 }
304 if (row->ascrise < minascheight) {
305 row->ascrise = row->xheight * ((1.0 - X_HEIGHT_FRACTION) / X_HEIGHT_FRACTION);
306 }
307 }
308 if (row->descdrop > mindescheight) {
309 if (row->xheight >= lineheight * (1 - MAXHEIGHTVARIANCE) &&
310 row->xheight <= lineheight * (1 + MAXHEIGHTVARIANCE)) {
311 /*set to average */
312 row->descdrop = descheight;
313 } else {
314 row->descdrop = -row->xheight * DESCENDER_FRACTION;
315 }
316 }
317 }
318 return static_cast<int>(lineheight); // block xheight
319 }
320
321 /**********************************************************************
322 * find_textlines
323 *
324 * Compute the baseline for the given row.
325 **********************************************************************/
326
find_textlines(TO_BLOCK * block,TO_ROW * row,int degree,QSPLINE * spline)327 void Textord::find_textlines(TO_BLOCK *block, // block row is in
328 TO_ROW *row, // row to do
329 int degree, // required approximation
330 QSPLINE *spline) { // starting spline
331 int partcount; /*no of partitions of */
332 bool holed_line = false; // lost too many blobs
333 int bestpart; /*biggest partition */
334 int partsizes[MAXPARTS]; /*no in each partition */
335 int lineheight; /*guessed x-height */
336 float jumplimit; /*allowed delta change */
337 int blobcount; /*no of blobs on line */
338 int pointcount; /*no of coords */
339 int xstarts[SPLINESIZE + 1]; // segment boundaries
340 int segments; // no of segments
341
342 // no of blobs in row
343 blobcount = row->blob_list()->length();
344 // partition no of each blob
345 std::vector<char> partids(blobcount);
346 // useful sample points
347 std::vector<int> xcoords(blobcount);
348 // useful sample points
349 std::vector<int> ycoords(blobcount);
350 // edges of blob rectangles
351 std::vector<TBOX> blobcoords(blobcount);
352 // diffs from 1st approx
353 std::vector<float> ydiffs(blobcount);
354
355 lineheight = get_blob_coords(row, static_cast<int>(block->line_size), &blobcoords[0], holed_line,
356 blobcount);
357 /*limit for line change */
358 jumplimit = lineheight * textord_oldbl_jumplimit;
359 if (jumplimit < MINASCRISE) {
360 jumplimit = MINASCRISE;
361 }
362
363 if (textord_oldbl_debug) {
364 tprintf("\nInput height=%g, Estimate x-height=%d pixels, jumplimit=%.2f\n", block->line_size,
365 lineheight, jumplimit);
366 }
367 if (holed_line) {
368 make_holed_baseline(&blobcoords[0], blobcount, spline, &row->baseline, row->line_m());
369 } else {
370 make_first_baseline(&blobcoords[0], blobcount, &xcoords[0], &ycoords[0], spline, &row->baseline,
371 jumplimit);
372 }
373 #ifndef GRAPHICS_DISABLED
374 if (textord_show_final_rows) {
375 row->baseline.plot(to_win, ScrollView::GOLDENROD);
376 }
377 #endif
378 if (blobcount > 1) {
379 bestpart = partition_line(&blobcoords[0], blobcount, &partcount, &partids[0], partsizes,
380 &row->baseline, jumplimit, &ydiffs[0]);
381 pointcount = partition_coords(&blobcoords[0], blobcount, &partids[0], bestpart, &xcoords[0],
382 &ycoords[0]);
383 segments = segment_spline(&blobcoords[0], blobcount, &xcoords[0], &ycoords[0], degree,
384 pointcount, xstarts);
385 if (!holed_line) {
386 do {
387 row->baseline = QSPLINE(xstarts, segments, &xcoords[0], &ycoords[0], pointcount, degree);
388 } while (textord_oldbl_split_splines &&
389 split_stepped_spline(&row->baseline, jumplimit / 2, &xcoords[0], xstarts, segments));
390 }
391 find_lesser_parts(row, &blobcoords[0], blobcount, &partids[0], partsizes, partcount, bestpart);
392
393 } else {
394 row->xheight = -1.0f; /*failed */
395 row->descdrop = 0.0f;
396 row->ascrise = 0.0f;
397 }
398 row->baseline.extrapolate(row->line_m(), block->block->pdblk.bounding_box().left(),
399 block->block->pdblk.bounding_box().right());
400
401 if (textord_really_old_xheight) {
402 old_first_xheight(row, &blobcoords[0], lineheight, blobcount, &row->baseline, jumplimit);
403 } else if (textord_old_xheight) {
404 make_first_xheight(row, &blobcoords[0], lineheight, static_cast<int>(block->line_size),
405 blobcount, &row->baseline, jumplimit);
406 } else {
407 compute_row_xheight(row, block->block->classify_rotation(), row->line_m(), block->line_size);
408 }
409 }
410
411 /**********************************************************************
412 * get_blob_coords
413 *
414 * Fill the blobcoords array with the coordinates of the blobs
415 * in the row. The return value is the first guess at the line height.
416 **********************************************************************/
417
get_blob_coords(TO_ROW * row,int32_t lineheight,TBOX * blobcoords,bool & holed_line,int & outcount)418 int get_blob_coords( // get boxes
419 TO_ROW *row, // row to use
420 int32_t lineheight, // block level
421 TBOX *blobcoords, // output boxes
422 bool &holed_line, // lost a lot of blobs
423 int &outcount // no of real blobs
424 ) {
425 // blobs
426 BLOBNBOX_IT blob_it = row->blob_list();
427 int blobindex; /*no along text line */
428 int losscount; // lost blobs
429 int maxlosscount; // greatest lost blobs
430 /*height stat collection */
431 STATS heightstat(0, MAXHEIGHT);
432
433 if (blob_it.empty()) {
434 return 0; // none
435 }
436 maxlosscount = 0;
437 losscount = 0;
438 blob_it.mark_cycle_pt();
439 blobindex = 0;
440 do {
441 blobcoords[blobindex] = box_next_pre_chopped(&blob_it);
442 if (blobcoords[blobindex].height() > lineheight * 0.25) {
443 heightstat.add(blobcoords[blobindex].height(), 1);
444 }
445 if (blobindex == 0 || blobcoords[blobindex].height() > lineheight * 0.25 ||
446 blob_it.cycled_list()) {
447 blobindex++; /*no of merged blobs */
448 losscount = 0;
449 } else {
450 if (blobcoords[blobindex].height() < blobcoords[blobindex].width() * oldbl_dot_error_size &&
451 blobcoords[blobindex].width() < blobcoords[blobindex].height() * oldbl_dot_error_size) {
452 // counts as dot
453 blobindex++;
454 losscount = 0;
455 } else {
456 losscount++; // lost it
457 if (losscount > maxlosscount) {
458 // remember max
459 maxlosscount = losscount;
460 }
461 }
462 }
463 } while (!blob_it.cycled_list());
464
465 holed_line = maxlosscount > oldbl_holed_losscount;
466 outcount = blobindex; /*total blobs */
467
468 if (heightstat.get_total() > 1) {
469 /*guess x-height */
470 return static_cast<int>(heightstat.ile(0.25));
471 } else {
472 return blobcoords[0].height();
473 }
474 }
475
476 /**********************************************************************
477 * make_first_baseline
478 *
479 * Make the first estimate at a baseline, either by shifting
480 * a supplied previous spline, or by doing a piecewise linear
481 * approximation using all the blobs.
482 **********************************************************************/
483
make_first_baseline(TBOX blobcoords[],int blobcount,int xcoords[],int ycoords[],QSPLINE * spline,QSPLINE * baseline,float jumplimit)484 void make_first_baseline( // initial approximation
485 TBOX blobcoords[], /*blob bounding boxes */
486 int blobcount, /*no of blobcoords */
487 int xcoords[], /*coords for spline */
488 int ycoords[], /*approximator */
489 QSPLINE *spline, /*initial spline */
490 QSPLINE *baseline, /*output spline */
491 float jumplimit /*guess half descenders */
492 ) {
493 int leftedge; /*left edge of line */
494 int rightedge; /*right edge of line */
495 int blobindex; /*current blob */
496 int segment; /*current segment */
497 float prevy, thisy, nexty; /*3 y coords */
498 float y1, y2, y3; /*3 smooth blobs */
499 float maxmax, minmin; /*absolute limits */
500 int x2 = 0; /*right edge of old y3 */
501 int ycount; /*no of ycoords in use */
502 float yturns[SPLINESIZE]; /*y coords of turn pts */
503 int xturns[SPLINESIZE]; /*xcoords of turn pts */
504 int xstarts[SPLINESIZE + 1];
505 int segments; // no of segments
506 ICOORD shift; // shift of spline
507
508 prevy = 0;
509 /*left edge of row */
510 leftedge = blobcoords[0].left();
511 /*right edge of line */
512 rightedge = blobcoords[blobcount - 1].right();
513 if (spline == nullptr /*no given spline */
514 || spline->segments < 3 /*or trivial */
515 /*or too non-overlap */
516 || spline->xcoords[1] > leftedge + MAXOVERLAP * (rightedge - leftedge) ||
517 spline->xcoords[spline->segments - 1] < rightedge - MAXOVERLAP * (rightedge - leftedge)) {
518 if (textord_oldbl_paradef) {
519 return; // use default
520 }
521 xstarts[0] = blobcoords[0].left() - 1;
522 for (blobindex = 0; blobindex < blobcount; blobindex++) {
523 xcoords[blobindex] = (blobcoords[blobindex].left() + blobcoords[blobindex].right()) / 2;
524 ycoords[blobindex] = blobcoords[blobindex].bottom();
525 }
526 xstarts[1] = blobcoords[blobcount - 1].right() + 1;
527 segments = 1; /*no of segments */
528
529 /*linear */
530 *baseline = QSPLINE(xstarts, segments, xcoords, ycoords, blobcount, 1);
531
532 if (blobcount >= 3) {
533 y1 = y2 = y3 = 0.0f;
534 ycount = 0;
535 segment = 0; /*no of segments */
536 maxmax = minmin = 0.0f;
537 thisy = ycoords[0] - baseline->y(xcoords[0]);
538 nexty = ycoords[1] - baseline->y(xcoords[1]);
539 for (blobindex = 2; blobindex < blobcount; blobindex++) {
540 prevy = thisy; /*shift ycoords */
541 thisy = nexty;
542 nexty = ycoords[blobindex] - baseline->y(xcoords[blobindex]);
543 /*middle of smooth y */
544 if (ABS(thisy - prevy) < jumplimit && ABS(thisy - nexty) < jumplimit) {
545 y1 = y2; /*shift window */
546 y2 = y3;
547 y3 = thisy; /*middle point */
548 ycount++;
549 /*local max */
550 if (ycount >= 3 && ((y1 < y2 && y2 >= y3)
551 /*local min */
552 || (y1 > y2 && y2 <= y3))) {
553 if (segment < SPLINESIZE - 2) {
554 /*turning pt */
555 xturns[segment] = x2;
556 yturns[segment] = y2;
557 segment++; /*no of spline segs */
558 }
559 }
560 if (ycount == 1) {
561 maxmax = minmin = y3; /*initialise limits */
562 } else {
563 if (y3 > maxmax) {
564 maxmax = y3; /*biggest max */
565 }
566 if (y3 < minmin) {
567 minmin = y3; /*smallest min */
568 }
569 }
570 /*possible turning pt */
571 x2 = blobcoords[blobindex - 1].right();
572 }
573 }
574
575 jumplimit *= 1.2f;
576 /*must be wavy */
577 if (maxmax - minmin > jumplimit) {
578 ycount = segment; /*no of segments */
579 for (blobindex = 0, segment = 1; blobindex < ycount; blobindex++) {
580 if (yturns[blobindex] > minmin + jumplimit || yturns[blobindex] < maxmax - jumplimit) {
581 /*significant peak */
582 if (segment == 1 || yturns[blobindex] > prevy + jumplimit ||
583 yturns[blobindex] < prevy - jumplimit) {
584 /*different to previous */
585 xstarts[segment] = xturns[blobindex];
586 segment++;
587 prevy = yturns[blobindex];
588 }
589 /*bigger max */
590 else if ((prevy > minmin + jumplimit && yturns[blobindex] > prevy)
591 /*smaller min */
592 || (prevy < maxmax - jumplimit && yturns[blobindex] < prevy)) {
593 xstarts[segment - 1] = xturns[blobindex];
594 /*improved previous */
595 prevy = yturns[blobindex];
596 }
597 }
598 }
599 xstarts[segment] = blobcoords[blobcount - 1].right() + 1;
600 segments = segment; /*no of segments */
601 /*linear */
602 *baseline = QSPLINE(xstarts, segments, xcoords, ycoords, blobcount, 1);
603 }
604 }
605 } else {
606 *baseline = *spline; /*copy it */
607 shift =
608 ICOORD(0, static_cast<int16_t>(blobcoords[0].bottom() - spline->y(blobcoords[0].right())));
609 baseline->move(shift);
610 }
611 }
612
613 /**********************************************************************
614 * make_holed_baseline
615 *
616 * Make the first estimate at a baseline, either by shifting
617 * a supplied previous spline, or by doing a piecewise linear
618 * approximation using all the blobs.
619 **********************************************************************/
620
make_holed_baseline(TBOX blobcoords[],int blobcount,QSPLINE * spline,QSPLINE * baseline,float gradient)621 void make_holed_baseline( // initial approximation
622 TBOX blobcoords[], /*blob bounding boxes */
623 int blobcount, /*no of blobcoords */
624 QSPLINE *spline, /*initial spline */
625 QSPLINE *baseline, /*output spline */
626 float gradient // of line
627 ) {
628 int leftedge; /*left edge of line */
629 int rightedge; /*right edge of line */
630 int blobindex; /*current blob */
631 float x; // centre of row
632 ICOORD shift; // shift of spline
633
634 tesseract::DetLineFit lms; // straight baseline
635 int32_t xstarts[2]; // straight line
636 double coeffs[3];
637 float c; // line parameter
638
639 /*left edge of row */
640 leftedge = blobcoords[0].left();
641 /*right edge of line */
642 rightedge = blobcoords[blobcount - 1].right();
643 for (blobindex = 0; blobindex < blobcount; blobindex++) {
644 lms.Add(ICOORD((blobcoords[blobindex].left() + blobcoords[blobindex].right()) / 2,
645 blobcoords[blobindex].bottom()));
646 }
647 lms.ConstrainedFit(gradient, &c);
648 xstarts[0] = leftedge;
649 xstarts[1] = rightedge;
650 coeffs[0] = 0;
651 coeffs[1] = gradient;
652 coeffs[2] = c;
653 *baseline = QSPLINE(1, xstarts, coeffs);
654 if (spline != nullptr /*no given spline */
655 && spline->segments >= 3 /*or trivial */
656 /*or too non-overlap */
657 && spline->xcoords[1] <= leftedge + MAXOVERLAP * (rightedge - leftedge) &&
658 spline->xcoords[spline->segments - 1] >= rightedge - MAXOVERLAP * (rightedge - leftedge)) {
659 *baseline = *spline; /*copy it */
660 x = (leftedge + rightedge) / 2.0;
661 shift = ICOORD(0, static_cast<int16_t>(gradient * x + c - spline->y(x)));
662 baseline->move(shift);
663 }
664 }
665
666 /**********************************************************************
667 * partition_line
668 *
669 * Partition a row of blobs into different groups of continuous
670 * y position. jumplimit specifies the max allowable limit on a jump
671 * before a new partition is started.
672 * The return value is the biggest partition
673 **********************************************************************/
674
partition_line(TBOX blobcoords[],int blobcount,int * numparts,char partids[],int partsizes[],QSPLINE * spline,float jumplimit,float ydiffs[])675 int partition_line( // partition blobs
676 TBOX blobcoords[], // bounding boxes
677 int blobcount, /*no of blobs on row */
678 int *numparts, /*number of partitions */
679 char partids[], /*partition no of each blob */
680 int partsizes[], /*no in each partition */
681 QSPLINE *spline, /*curve to fit to */
682 float jumplimit, /*allowed delta change */
683 float ydiffs[] /*diff from spline */
684 ) {
685 int blobindex; /*no along text line */
686 int bestpart; /*best new partition */
687 int biggestpart; /*part with most members */
688 float diff; /*difference from line */
689 int startx; /*index of start blob */
690 float partdiffs[MAXPARTS]; /*step between parts */
691
692 for (bestpart = 0; bestpart < MAXPARTS; bestpart++) {
693 partsizes[bestpart] = 0; /*zero them all */
694 }
695
696 startx = get_ydiffs(blobcoords, blobcount, spline, ydiffs);
697 *numparts = 1; /*1 partition */
698 bestpart = -1; /*first point */
699 float drift = 0.0f;
700 float last_delta = 0.0f;
701 for (blobindex = startx; blobindex < blobcount; blobindex++) {
702 /*do each blob in row */
703 diff = ydiffs[blobindex]; /*diff from line */
704 if (textord_oldbl_debug) {
705 tprintf("%d(%d,%d), ", blobindex, blobcoords[blobindex].left(),
706 blobcoords[blobindex].bottom());
707 }
708 bestpart =
709 choose_partition(diff, partdiffs, bestpart, jumplimit, &drift, &last_delta, numparts);
710 /*record partition */
711 partids[blobindex] = bestpart;
712 partsizes[bestpart]++; /*another in it */
713 }
714
715 bestpart = -1; /*first point */
716 drift = 0.0f;
717 last_delta = 0.0f;
718 partsizes[0]--; /*doing 1st pt again */
719 /*do each blob in row */
720 for (blobindex = startx; blobindex >= 0; blobindex--) {
721 diff = ydiffs[blobindex]; /*diff from line */
722 if (textord_oldbl_debug) {
723 tprintf("%d(%d,%d), ", blobindex, blobcoords[blobindex].left(),
724 blobcoords[blobindex].bottom());
725 }
726 bestpart =
727 choose_partition(diff, partdiffs, bestpart, jumplimit, &drift, &last_delta, numparts);
728 /*record partition */
729 partids[blobindex] = bestpart;
730 partsizes[bestpart]++; /*another in it */
731 }
732
733 for (biggestpart = 0, bestpart = 1; bestpart < *numparts; bestpart++) {
734 if (partsizes[bestpart] >= partsizes[biggestpart]) {
735 biggestpart = bestpart; /*new biggest */
736 }
737 }
738 if (textord_oldbl_merge_parts) {
739 merge_oldbl_parts(blobcoords, blobcount, partids, partsizes, biggestpart, jumplimit);
740 }
741 return biggestpart; /*biggest partition */
742 }
743
744 /**********************************************************************
745 * merge_oldbl_parts
746 *
747 * For any adjacent group of blobs in a different part, put them in the
748 * main part if they fit closely to neighbours in the main part.
749 **********************************************************************/
750
merge_oldbl_parts(TBOX blobcoords[],int blobcount,char partids[],int partsizes[],int biggestpart,float jumplimit)751 void merge_oldbl_parts( // partition blobs
752 TBOX blobcoords[], // bounding boxes
753 int blobcount, /*no of blobs on row */
754 char partids[], /*partition no of each blob */
755 int partsizes[], /*no in each partition */
756 int biggestpart, // major partition
757 float jumplimit /*allowed delta change */
758 ) {
759 bool found_one; // found a bestpart blob
760 bool close_one; // found was close enough
761 int blobindex; /*no along text line */
762 int prevpart; // previous iteration
763 int runlength; // no in this part
764 float diff; /*difference from line */
765 int startx; /*index of start blob */
766 int test_blob; // another index
767 FCOORD coord; // blob coordinate
768 float m, c; // fitted line
769 QLSQ stats; // line stuff
770
771 prevpart = biggestpart;
772 runlength = 0;
773 startx = 0;
774 for (blobindex = 0; blobindex < blobcount; blobindex++) {
775 if (partids[blobindex] != prevpart) {
776 // tprintf("Partition change at (%d,%d) from %d to %d
777 // after run of %d\n",
778 // blobcoords[blobindex].left(),blobcoords[blobindex].bottom(),
779 // prevpart,partids[blobindex],runlength);
780 if (prevpart != biggestpart && runlength > MAXBADRUN) {
781 stats.clear();
782 for (test_blob = startx; test_blob < blobindex; test_blob++) {
783 coord = FCOORD((blobcoords[test_blob].left() + blobcoords[test_blob].right()) / 2.0,
784 blobcoords[test_blob].bottom());
785 stats.add(coord.x(), coord.y());
786 }
787 stats.fit(1);
788 m = stats.get_b();
789 c = stats.get_c();
790 if (textord_oldbl_debug) {
791 tprintf("Fitted line y=%g x + %g\n", m, c);
792 }
793 found_one = false;
794 close_one = false;
795 for (test_blob = 1;
796 !found_one && (startx - test_blob >= 0 || blobindex + test_blob <= blobcount);
797 test_blob++) {
798 if (startx - test_blob >= 0 && partids[startx - test_blob] == biggestpart) {
799 found_one = true;
800 coord = FCOORD(
801 (blobcoords[startx - test_blob].left() + blobcoords[startx - test_blob].right()) /
802 2.0,
803 blobcoords[startx - test_blob].bottom());
804 diff = m * coord.x() + c - coord.y();
805 if (textord_oldbl_debug) {
806 tprintf("Diff of common blob to suspect part=%g at (%g,%g)\n", diff, coord.x(),
807 coord.y());
808 }
809 if (diff < jumplimit && -diff < jumplimit) {
810 close_one = true;
811 }
812 }
813 if (blobindex + test_blob <= blobcount &&
814 partids[blobindex + test_blob - 1] == biggestpart) {
815 found_one = true;
816 coord = FCOORD((blobcoords[blobindex + test_blob - 1].left() +
817 blobcoords[blobindex + test_blob - 1].right()) /
818 2.0,
819 blobcoords[blobindex + test_blob - 1].bottom());
820 diff = m * coord.x() + c - coord.y();
821 if (textord_oldbl_debug) {
822 tprintf("Diff of common blob to suspect part=%g at (%g,%g)\n", diff, coord.x(),
823 coord.y());
824 }
825 if (diff < jumplimit && -diff < jumplimit) {
826 close_one = true;
827 }
828 }
829 }
830 if (close_one) {
831 if (textord_oldbl_debug) {
832 tprintf(
833 "Merged %d blobs back into part %d from %d starting at "
834 "(%d,%d)\n",
835 runlength, biggestpart, prevpart, blobcoords[startx].left(),
836 blobcoords[startx].bottom());
837 }
838 // switch sides
839 partsizes[prevpart] -= runlength;
840 for (test_blob = startx; test_blob < blobindex; test_blob++) {
841 partids[test_blob] = biggestpart;
842 }
843 }
844 }
845 prevpart = partids[blobindex];
846 runlength = 1;
847 startx = blobindex;
848 } else {
849 runlength++;
850 }
851 }
852 }
853
854 /**********************************************************************
855 * get_ydiffs
856 *
857 * Get the differences between the blobs and the spline,
858 * putting them in ydiffs. The return value is the index
859 * of the blob in the middle of the "best behaved" region
860 **********************************************************************/
861
get_ydiffs(TBOX blobcoords[],int blobcount,QSPLINE * spline,float ydiffs[])862 int get_ydiffs( // evaluate differences
863 TBOX blobcoords[], // bounding boxes
864 int blobcount, /*no of blobs */
865 QSPLINE *spline, /*approximating spline */
866 float ydiffs[] /*output */
867 ) {
868 int blobindex; /*current blob */
869 int xcentre; /*xcoord */
870 int lastx; /*last xcentre */
871 float diffsum; /*sum of diffs */
872 float diff; /*current difference */
873 float drift; /*sum of spline steps */
874 float bestsum; /*smallest diffsum */
875 int bestindex; /*index of bestsum */
876
877 diffsum = 0.0f;
878 bestindex = 0;
879 bestsum = static_cast<float>(INT32_MAX);
880 drift = 0.0f;
881 lastx = blobcoords[0].left();
882 /*do each blob in row */
883 for (blobindex = 0; blobindex < blobcount; blobindex++) {
884 /*centre of blob */
885 xcentre = (blobcoords[blobindex].left() + blobcoords[blobindex].right()) >> 1;
886 // step functions in spline
887 drift += spline->step(lastx, xcentre);
888 lastx = xcentre;
889 diff = blobcoords[blobindex].bottom();
890 diff -= spline->y(xcentre);
891 diff += drift;
892 ydiffs[blobindex] = diff; /*store difference */
893 if (blobindex > 2) {
894 /*remove old one */
895 diffsum -= ABS(ydiffs[blobindex - 3]);
896 }
897 diffsum += ABS(diff); /*add new one */
898 if (blobindex >= 2 && diffsum < bestsum) {
899 bestsum = diffsum; /*find min sum */
900 bestindex = blobindex - 1; /*middle of set */
901 }
902 }
903 return bestindex;
904 }
905
906 /**********************************************************************
907 * choose_partition
908 *
909 * Choose a partition for the point and return the index.
910 **********************************************************************/
911
choose_partition(float diff,float partdiffs[],int lastpart,float jumplimit,float * drift,float * lastdelta,int * partcount)912 int choose_partition( // select partition
913 float diff, /*diff from spline */
914 float partdiffs[], /*diff on all parts */
915 int lastpart, /*last assigned partition */
916 float jumplimit, /*new part threshold */
917 float *drift, float *lastdelta, int *partcount /*no of partitions */
918 ) {
919 int partition; /*partition no */
920 int bestpart; /*best new partition */
921 float bestdelta; /*best gap from a part */
922 float delta; /*diff from part */
923
924 if (lastpart < 0) {
925 partdiffs[0] = diff;
926 lastpart = 0; /*first point */
927 *drift = 0.0f;
928 *lastdelta = 0.0f;
929 }
930 /*adjusted diff from part */
931 delta = diff - partdiffs[lastpart] - *drift;
932 if (textord_oldbl_debug) {
933 tprintf("Diff=%.2f, Delta=%.3f, Drift=%.3f, ", diff, delta, *drift);
934 }
935 if (ABS(delta) > jumplimit / 2) {
936 /*delta on part 0 */
937 bestdelta = diff - partdiffs[0] - *drift;
938 bestpart = 0; /*0 best so far */
939 for (partition = 1; partition < *partcount; partition++) {
940 delta = diff - partdiffs[partition] - *drift;
941 if (ABS(delta) < ABS(bestdelta)) {
942 bestdelta = delta;
943 bestpart = partition; /*part with nearest jump */
944 }
945 }
946 delta = bestdelta;
947 /*too far away */
948 if (ABS(bestdelta) > jumplimit && *partcount < MAXPARTS) { /*and spare part left */
949 bestpart = (*partcount)++; /*best was new one */
950 /*start new one */
951 partdiffs[bestpart] = diff - *drift;
952 delta = 0.0f;
953 }
954 } else {
955 bestpart = lastpart; /*best was last one */
956 }
957
958 if (bestpart == lastpart &&
959 (ABS(delta - *lastdelta) < jumplimit / 2 || ABS(delta) < jumplimit / 2)) {
960 /*smooth the drift */
961 *drift = (3 * *drift + delta) / 3;
962 }
963 *lastdelta = delta;
964
965 if (textord_oldbl_debug) {
966 tprintf("P=%d\n", bestpart);
967 }
968
969 return bestpart;
970 }
971
972 /**********************************************************************
973 * partition_coords
974 *
975 * Get the x,y coordinates of all points in the bestpart and put them
976 * in xcoords,ycoords. Return the number of points found.
977 **********************************************************************/
978
partition_coords(TBOX blobcoords[],int blobcount,char partids[],int bestpart,int xcoords[],int ycoords[])979 int partition_coords( // find relevant coords
980 TBOX blobcoords[], // bounding boxes
981 int blobcount, /*no of blobs in row */
982 char partids[], /*partition no of each blob */
983 int bestpart, /*best new partition */
984 int xcoords[], /*points to work on */
985 int ycoords[] /*points to work on */
986 ) {
987 int blobindex; /*no along text line */
988 int pointcount; /*no of points */
989
990 pointcount = 0;
991 for (blobindex = 0; blobindex < blobcount; blobindex++) {
992 if (partids[blobindex] == bestpart) {
993 /*centre of blob */
994 xcoords[pointcount] = (blobcoords[blobindex].left() + blobcoords[blobindex].right()) >> 1;
995 ycoords[pointcount++] = blobcoords[blobindex].bottom();
996 }
997 }
998 return pointcount; /*no of points found */
999 }
1000
1001 /**********************************************************************
1002 * segment_spline
1003 *
1004 * Segment the row at midpoints between maxima and minima of the x,y pairs.
1005 * The xstarts of the segments are returned and the number found.
1006 **********************************************************************/
1007
segment_spline(TBOX blobcoords[],int blobcount,int xcoords[],int ycoords[],int degree,int pointcount,int xstarts[])1008 int segment_spline( // make xstarts
1009 TBOX blobcoords[], // boundign boxes
1010 int blobcount, /*no of blobs in row */
1011 int xcoords[], /*points to work on */
1012 int ycoords[], /*points to work on */
1013 int degree, int pointcount, /*no of points */
1014 int xstarts[] // result
1015 ) {
1016 int ptindex; /*no along text line */
1017 int segment; /*partition no */
1018 int lastmin, lastmax; /*possible turn points */
1019 int turnpoints[SPLINESIZE]; /*good turning points */
1020 int turncount; /*no of turning points */
1021 int max_x; // max specified coord
1022
1023 xstarts[0] = xcoords[0] - 1; // leftmost defined pt
1024 max_x = xcoords[pointcount - 1] + 1;
1025 if (degree < 2) {
1026 pointcount = 0;
1027 }
1028 turncount = 0; /*no turning points yet */
1029 if (pointcount > 3) {
1030 ptindex = 1;
1031 lastmax = lastmin = 0; /*start with first one */
1032 while (ptindex < pointcount - 1 && turncount < SPLINESIZE - 1) {
1033 /*minimum */
1034 if (ycoords[ptindex - 1] > ycoords[ptindex] && ycoords[ptindex] <= ycoords[ptindex + 1]) {
1035 if (ycoords[ptindex] < ycoords[lastmax] - TURNLIMIT) {
1036 if (turncount == 0 || turnpoints[turncount - 1] != lastmax) {
1037 /*new max point */
1038 turnpoints[turncount++] = lastmax;
1039 }
1040 lastmin = ptindex; /*latest minimum */
1041 } else if (ycoords[ptindex] < ycoords[lastmin]) {
1042 lastmin = ptindex; /*lower minimum */
1043 }
1044 }
1045
1046 /*maximum */
1047 if (ycoords[ptindex - 1] < ycoords[ptindex] && ycoords[ptindex] >= ycoords[ptindex + 1]) {
1048 if (ycoords[ptindex] > ycoords[lastmin] + TURNLIMIT) {
1049 if (turncount == 0 || turnpoints[turncount - 1] != lastmin) {
1050 /*new min point */
1051 turnpoints[turncount++] = lastmin;
1052 }
1053 lastmax = ptindex; /*latest maximum */
1054 } else if (ycoords[ptindex] > ycoords[lastmax]) {
1055 lastmax = ptindex; /*higher maximum */
1056 }
1057 }
1058 ptindex++;
1059 }
1060 /*possible global min */
1061 if (ycoords[ptindex] < ycoords[lastmax] - TURNLIMIT &&
1062 (turncount == 0 || turnpoints[turncount - 1] != lastmax)) {
1063 if (turncount < SPLINESIZE - 1) {
1064 /*2 more turns */
1065 turnpoints[turncount++] = lastmax;
1066 }
1067 if (turncount < SPLINESIZE - 1) {
1068 turnpoints[turncount++] = ptindex;
1069 }
1070 } else if (ycoords[ptindex] > ycoords[lastmin] + TURNLIMIT
1071 /*possible global max */
1072 && (turncount == 0 || turnpoints[turncount - 1] != lastmin)) {
1073 if (turncount < SPLINESIZE - 1) {
1074 /*2 more turns */
1075 turnpoints[turncount++] = lastmin;
1076 }
1077 if (turncount < SPLINESIZE - 1) {
1078 turnpoints[turncount++] = ptindex;
1079 }
1080 } else if (turncount > 0 && turnpoints[turncount - 1] == lastmin &&
1081 turncount < SPLINESIZE - 1) {
1082 if (ycoords[ptindex] > ycoords[lastmax]) {
1083 turnpoints[turncount++] = ptindex;
1084 } else {
1085 turnpoints[turncount++] = lastmax;
1086 }
1087 } else if (turncount > 0 && turnpoints[turncount - 1] == lastmax &&
1088 turncount < SPLINESIZE - 1) {
1089 if (ycoords[ptindex] < ycoords[lastmin]) {
1090 turnpoints[turncount++] = ptindex;
1091 } else {
1092 turnpoints[turncount++] = lastmin;
1093 }
1094 }
1095 }
1096
1097 if (textord_oldbl_debug && turncount > 0) {
1098 tprintf("First turn is %d at (%d,%d)\n", turnpoints[0], xcoords[turnpoints[0]],
1099 ycoords[turnpoints[0]]);
1100 }
1101 for (segment = 1; segment < turncount; segment++) {
1102 /*centre y coord */
1103 lastmax = (ycoords[turnpoints[segment - 1]] + ycoords[turnpoints[segment]]) / 2;
1104
1105 /* fix alg so that it works with both rising and falling sections */
1106 if (ycoords[turnpoints[segment - 1]] < ycoords[turnpoints[segment]]) {
1107 /*find rising y centre */
1108 for (ptindex = turnpoints[segment - 1] + 1;
1109 ptindex < turnpoints[segment] && ycoords[ptindex + 1] <= lastmax; ptindex++) {
1110 ;
1111 }
1112 } else {
1113 /*find falling y centre */
1114 for (ptindex = turnpoints[segment - 1] + 1;
1115 ptindex < turnpoints[segment] && ycoords[ptindex + 1] >= lastmax; ptindex++) {
1116 ;
1117 }
1118 }
1119
1120 /*centre x */
1121 xstarts[segment] = (xcoords[ptindex - 1] + xcoords[ptindex] + xcoords[turnpoints[segment - 1]] +
1122 xcoords[turnpoints[segment]] + 2) /
1123 4;
1124 /*halfway between turns */
1125 if (textord_oldbl_debug) {
1126 tprintf("Turn %d is %d at (%d,%d), mid pt is %d@%d, final @%d\n", segment,
1127 turnpoints[segment], xcoords[turnpoints[segment]], ycoords[turnpoints[segment]],
1128 ptindex - 1, xcoords[ptindex - 1], xstarts[segment]);
1129 }
1130 }
1131
1132 xstarts[segment] = max_x;
1133 return segment; /*no of splines */
1134 }
1135
1136 /**********************************************************************
1137 * split_stepped_spline
1138 *
1139 * Re-segment the spline in cases where there is a big step function.
1140 * Return true if any were done.
1141 **********************************************************************/
1142
split_stepped_spline(QSPLINE * baseline,float jumplimit,int * xcoords,int * xstarts,int & segments)1143 bool split_stepped_spline( // make xstarts
1144 QSPLINE *baseline, // current shot
1145 float jumplimit, // max step function
1146 int *xcoords, /*points to work on */
1147 int *xstarts, // result
1148 int &segments // no of segments
1149 ) {
1150 bool doneany; // return value
1151 int segment; /*partition no */
1152 int startindex, centreindex, endindex;
1153 float leftcoord, rightcoord;
1154 int leftindex, rightindex;
1155 float step; // spline step
1156
1157 doneany = false;
1158 startindex = 0;
1159 for (segment = 1; segment < segments - 1; segment++) {
1160 step = baseline->step((xstarts[segment - 1] + xstarts[segment]) / 2.0,
1161 (xstarts[segment] + xstarts[segment + 1]) / 2.0);
1162 if (step < 0) {
1163 step = -step;
1164 }
1165 if (step > jumplimit) {
1166 while (xcoords[startindex] < xstarts[segment - 1]) {
1167 startindex++;
1168 }
1169 centreindex = startindex;
1170 while (xcoords[centreindex] < xstarts[segment]) {
1171 centreindex++;
1172 }
1173 endindex = centreindex;
1174 while (xcoords[endindex] < xstarts[segment + 1]) {
1175 endindex++;
1176 }
1177 if (segments >= SPLINESIZE) {
1178 if (textord_debug_baselines) {
1179 tprintf("Too many segments to resegment spline!!\n");
1180 }
1181 } else if (endindex - startindex >= textord_spline_medianwin * 3) {
1182 while (centreindex - startindex < textord_spline_medianwin * 3 / 2) {
1183 centreindex++;
1184 }
1185 while (endindex - centreindex < textord_spline_medianwin * 3 / 2) {
1186 centreindex--;
1187 }
1188 leftindex = (startindex + startindex + centreindex) / 3;
1189 rightindex = (centreindex + endindex + endindex) / 3;
1190 leftcoord = (xcoords[startindex] * 2 + xcoords[centreindex]) / 3.0;
1191 rightcoord = (xcoords[centreindex] + xcoords[endindex] * 2) / 3.0;
1192 while (xcoords[leftindex] > leftcoord &&
1193 leftindex - startindex > textord_spline_medianwin) {
1194 leftindex--;
1195 }
1196 while (xcoords[leftindex] < leftcoord &&
1197 centreindex - leftindex > textord_spline_medianwin / 2) {
1198 leftindex++;
1199 }
1200 if (xcoords[leftindex] - leftcoord > leftcoord - xcoords[leftindex - 1]) {
1201 leftindex--;
1202 }
1203 while (xcoords[rightindex] > rightcoord &&
1204 rightindex - centreindex > textord_spline_medianwin / 2) {
1205 rightindex--;
1206 }
1207 while (xcoords[rightindex] < rightcoord &&
1208 endindex - rightindex > textord_spline_medianwin) {
1209 rightindex++;
1210 }
1211 if (xcoords[rightindex] - rightcoord > rightcoord - xcoords[rightindex - 1]) {
1212 rightindex--;
1213 }
1214 if (textord_debug_baselines) {
1215 tprintf("Splitting spline at %d with step %g at (%d,%d)\n", xstarts[segment],
1216 baseline->step((xstarts[segment - 1] + xstarts[segment]) / 2.0,
1217 (xstarts[segment] + xstarts[segment + 1]) / 2.0),
1218 (xcoords[leftindex - 1] + xcoords[leftindex]) / 2,
1219 (xcoords[rightindex - 1] + xcoords[rightindex]) / 2);
1220 }
1221 insert_spline_point(xstarts, segment, (xcoords[leftindex - 1] + xcoords[leftindex]) / 2,
1222 (xcoords[rightindex - 1] + xcoords[rightindex]) / 2, segments);
1223 doneany = true;
1224 } else if (textord_debug_baselines) {
1225 tprintf("Resegmenting spline failed - insufficient pts (%d,%d,%d,%d)\n", startindex,
1226 centreindex, endindex, (int32_t)textord_spline_medianwin);
1227 }
1228 }
1229 // else tprintf("Spline step at %d is %g\n",
1230 // xstarts[segment],
1231 // baseline->step((xstarts[segment-1]+xstarts[segment])/2.0,
1232 // (xstarts[segment]+xstarts[segment+1])/2.0));
1233 }
1234 return doneany;
1235 }
1236
1237 /**********************************************************************
1238 * insert_spline_point
1239 *
1240 * Insert a new spline point and shuffle up the others.
1241 **********************************************************************/
1242
insert_spline_point(int xstarts[],int segment,int coord1,int coord2,int & segments)1243 void insert_spline_point( // get descenders
1244 int xstarts[], // starts to shuffle
1245 int segment, // insertion pt
1246 int coord1, // coords to add
1247 int coord2, int &segments // total segments
1248 ) {
1249 int index; // for shuffling
1250
1251 for (index = segments; index > segment; index--) {
1252 xstarts[index + 1] = xstarts[index];
1253 }
1254 segments++;
1255 xstarts[segment] = coord1;
1256 xstarts[segment + 1] = coord2;
1257 }
1258
1259 /**********************************************************************
1260 * find_lesser_parts
1261 *
1262 * Average the step from the spline for the other partitions
1263 * and find the commonest partition which has a descender.
1264 **********************************************************************/
1265
find_lesser_parts(TO_ROW * row,TBOX blobcoords[],int blobcount,char partids[],int partsizes[],int partcount,int bestpart)1266 void find_lesser_parts( // get descenders
1267 TO_ROW *row, // row to process
1268 TBOX blobcoords[], // bounding boxes
1269 int blobcount, /*no of blobs */
1270 char partids[], /*partition of each blob */
1271 int partsizes[], /*size of each part */
1272 int partcount, /*no of partitions */
1273 int bestpart /*biggest partition */
1274 ) {
1275 int blobindex; /*index of blob */
1276 int partition; /*current partition */
1277 int xcentre; /*centre of blob */
1278 int poscount; /*count of best up step */
1279 int negcount; /*count of best down step */
1280 float partsteps[MAXPARTS]; /*average step to part */
1281 float bestneg; /*best down step */
1282 int runlength; /*length of bad run */
1283 int biggestrun; /*biggest bad run */
1284
1285 biggestrun = 0;
1286 for (partition = 0; partition < partcount; partition++) {
1287 partsteps[partition] = 0.0; /*zero accumulators */
1288 }
1289 for (runlength = 0, blobindex = 0; blobindex < blobcount; blobindex++) {
1290 xcentre = (blobcoords[blobindex].left() + blobcoords[blobindex].right()) >> 1;
1291 /*in other parts */
1292 int part_id = static_cast<int>(static_cast<unsigned char>(partids[blobindex]));
1293 if (part_id != bestpart) {
1294 runlength++; /*run of non bests */
1295 if (runlength > biggestrun) {
1296 biggestrun = runlength;
1297 }
1298 partsteps[part_id] += blobcoords[blobindex].bottom() - row->baseline.y(xcentre);
1299 } else {
1300 runlength = 0;
1301 }
1302 }
1303 if (biggestrun > MAXBADRUN) {
1304 row->xheight = -1.0f; /*failed */
1305 } else {
1306 row->xheight = 1.0f; /*success */
1307 }
1308 poscount = negcount = 0;
1309 bestneg = 0.0; /*no step yet */
1310 for (partition = 0; partition < partcount; partition++) {
1311 if (partition != bestpart) {
1312 // by jetsoft divide by zero possible
1313 if (partsizes[partition] == 0) {
1314 partsteps[partition] = 0;
1315 } else {
1316 partsteps[partition] /= partsizes[partition];
1317 }
1318 //
1319
1320 if (partsteps[partition] >= MINASCRISE && partsizes[partition] > poscount) {
1321 poscount = partsizes[partition];
1322 }
1323 if (partsteps[partition] <= -MINASCRISE && partsizes[partition] > negcount) {
1324 /*ascender rise */
1325 bestneg = partsteps[partition];
1326 /*2nd most popular */
1327 negcount = partsizes[partition];
1328 }
1329 }
1330 }
1331 /*average x-height */
1332 partsteps[bestpart] /= blobcount;
1333 row->descdrop = bestneg;
1334 }
1335
1336 /**********************************************************************
1337 * old_first_xheight
1338 *
1339 * Makes an x-height spline by copying the baseline and shifting it.
1340 * It estimates the x-height across the line to use as the shift.
1341 * It also finds the ascender height if it can.
1342 **********************************************************************/
1343
old_first_xheight(TO_ROW * row,TBOX blobcoords[],int initialheight,int blobcount,QSPLINE * baseline,float jumplimit)1344 void old_first_xheight( // the wiseowl way
1345 TO_ROW *row, /*current row */
1346 TBOX blobcoords[], /*blob bounding boxes */
1347 int initialheight, // initial guess
1348 int blobcount, /*blobs in blobcoords */
1349 QSPLINE *baseline, /*established */
1350 float jumplimit /*min ascender height */
1351 ) {
1352 int blobindex; /*current blob */
1353 /*height statistics */
1354 STATS heightstat(0, MAXHEIGHT);
1355 int height; /*height of blob */
1356 int xcentre; /*centre of blob */
1357 int lineheight; /*approx xheight */
1358 float ascenders; /*ascender sum */
1359 int asccount; /*no of ascenders */
1360 float xsum; /*xheight sum */
1361 int xcount; /*xheight count */
1362 float diff; /*height difference */
1363
1364 if (blobcount > 1) {
1365 for (blobindex = 0; blobindex < blobcount; blobindex++) {
1366 xcentre = (blobcoords[blobindex].left() + blobcoords[blobindex].right()) / 2;
1367 /*height of blob */
1368 height = static_cast<int>(blobcoords[blobindex].top() - baseline->y(xcentre) + 0.5);
1369 if (height > initialheight * oldbl_xhfract && height > textord_min_xheight) {
1370 heightstat.add(height, 1);
1371 }
1372 }
1373 if (heightstat.get_total() > 3) {
1374 lineheight = static_cast<int>(heightstat.ile(0.25));
1375 if (lineheight <= 0) {
1376 lineheight = static_cast<int>(heightstat.ile(0.5));
1377 }
1378 } else {
1379 lineheight = initialheight;
1380 }
1381 } else {
1382 lineheight =
1383 static_cast<int>(blobcoords[0].top() -
1384 baseline->y((blobcoords[0].left() + blobcoords[0].right()) / 2) + 0.5);
1385 }
1386
1387 xsum = 0.0f;
1388 xcount = 0;
1389 for (ascenders = 0.0f, asccount = 0, blobindex = 0; blobindex < blobcount; blobindex++) {
1390 xcentre = (blobcoords[blobindex].left() + blobcoords[blobindex].right()) / 2;
1391 diff = blobcoords[blobindex].top() - baseline->y(xcentre);
1392 /*is it ascender */
1393 if (diff > lineheight + jumplimit) {
1394 ascenders += diff;
1395 asccount++; /*count ascenders */
1396 } else if (diff > lineheight - jumplimit) {
1397 xsum += diff; /*mean xheight */
1398 xcount++;
1399 }
1400 }
1401 if (xcount > 0) {
1402 xsum /= xcount; /*average xheight */
1403 } else {
1404 xsum = static_cast<float>(lineheight); /*guess it */
1405 }
1406 row->xheight *= xsum;
1407 if (asccount > 0) {
1408 row->ascrise = ascenders / asccount - xsum;
1409 } else {
1410 row->ascrise = 0.0f; /*had none */
1411 }
1412 if (row->xheight == 0) {
1413 row->xheight = -1.0f;
1414 }
1415 }
1416
1417 /**********************************************************************
1418 * make_first_xheight
1419 *
1420 * Makes an x-height spline by copying the baseline and shifting it.
1421 * It estimates the x-height across the line to use as the shift.
1422 * It also finds the ascender height if it can.
1423 **********************************************************************/
1424
make_first_xheight(TO_ROW * row,TBOX blobcoords[],int lineheight,int init_lineheight,int blobcount,QSPLINE * baseline,float jumplimit)1425 void make_first_xheight( // find xheight
1426 TO_ROW *row, /*current row */
1427 TBOX blobcoords[], /*blob bounding boxes */
1428 int lineheight, // initial guess
1429 int init_lineheight, // block level guess
1430 int blobcount, /*blobs in blobcoords */
1431 QSPLINE *baseline, /*established */
1432 float jumplimit /*min ascender height */
1433 ) {
1434 STATS heightstat(0, HEIGHTBUCKETS);
1435 int lefts[HEIGHTBUCKETS];
1436 int rights[HEIGHTBUCKETS];
1437 int modelist[MODENUM];
1438 int blobindex;
1439 int mode_count; // blobs to count in thr
1440 int sign_bit;
1441 int mode_threshold;
1442 const int kBaselineTouch = 2; // This really should change with resolution.
1443 const int kGoodStrength = 8; // Strength of baseline-touching heights.
1444 const float kMinHeight = 0.25; // Min fraction of lineheight to use.
1445
1446 sign_bit = row->xheight > 0 ? 1 : -1;
1447
1448 memset(lefts, 0, HEIGHTBUCKETS * sizeof(lefts[0]));
1449 memset(rights, 0, HEIGHTBUCKETS * sizeof(rights[0]));
1450 mode_count = 0;
1451 for (blobindex = 0; blobindex < blobcount; blobindex++) {
1452 int xcenter = (blobcoords[blobindex].left() + blobcoords[blobindex].right()) / 2;
1453 float base = baseline->y(xcenter);
1454 float bottomdiff = std::fabs(base - blobcoords[blobindex].bottom());
1455 int strength = textord_ocropus_mode && bottomdiff <= kBaselineTouch ? kGoodStrength : 1;
1456 int height = static_cast<int>(blobcoords[blobindex].top() - base + 0.5);
1457 if (blobcoords[blobindex].height() > init_lineheight * kMinHeight) {
1458 if (height > lineheight * oldbl_xhfract && height > textord_min_xheight) {
1459 heightstat.add(height, strength);
1460 if (height < HEIGHTBUCKETS) {
1461 if (xcenter > rights[height]) {
1462 rights[height] = xcenter;
1463 }
1464 if (xcenter > 0 && (lefts[height] == 0 || xcenter < lefts[height])) {
1465 lefts[height] = xcenter;
1466 }
1467 }
1468 }
1469 mode_count += strength;
1470 }
1471 }
1472
1473 mode_threshold = static_cast<int>(blobcount * 0.1);
1474 if (oldbl_dot_error_size > 1 || oldbl_xhfix) {
1475 mode_threshold = static_cast<int>(mode_count * 0.1);
1476 }
1477
1478 if (textord_oldbl_debug) {
1479 tprintf("blobcount=%d, mode_count=%d, mode_t=%d\n", blobcount, mode_count, mode_threshold);
1480 }
1481 find_top_modes(&heightstat, HEIGHTBUCKETS, modelist, MODENUM);
1482 if (textord_oldbl_debug) {
1483 for (blobindex = 0; blobindex < MODENUM; blobindex++) {
1484 tprintf("mode[%d]=%d ", blobindex, modelist[blobindex]);
1485 }
1486 tprintf("\n");
1487 }
1488 pick_x_height(row, modelist, lefts, rights, &heightstat, mode_threshold);
1489
1490 if (textord_oldbl_debug) {
1491 tprintf("Output xheight=%g\n", row->xheight);
1492 }
1493 if (row->xheight < 0 && textord_oldbl_debug) {
1494 tprintf("warning: Row Line height < 0; %4.2f\n", row->xheight);
1495 }
1496
1497 if (sign_bit < 0) {
1498 row->xheight = -row->xheight;
1499 }
1500 }
1501
1502 /**********************************************************************
1503 * find_top_modes
1504 *
1505 * Fill the input array with the indices of the top ten modes of the
1506 * input distribution.
1507 **********************************************************************/
1508
1509 const int kMinModeFactorOcropus = 32;
1510 const int kMinModeFactor = 12;
1511
find_top_modes(STATS * stats,int statnum,int modelist[],int modenum)1512 void find_top_modes( // get modes
1513 STATS *stats, // stats to hack
1514 int statnum, // no of piles
1515 int modelist[], int modenum // no of modes to get
1516 ) {
1517 int mode_count;
1518 int last_i = 0;
1519 int last_max = INT32_MAX;
1520 int i;
1521 int mode;
1522 int total_max = 0;
1523 int mode_factor = textord_ocropus_mode ? kMinModeFactorOcropus : kMinModeFactor;
1524
1525 for (mode_count = 0; mode_count < modenum; mode_count++) {
1526 mode = 0;
1527 for (i = 0; i < statnum; i++) {
1528 if (stats->pile_count(i) > stats->pile_count(mode)) {
1529 if ((stats->pile_count(i) < last_max) ||
1530 ((stats->pile_count(i) == last_max) && (i > last_i))) {
1531 mode = i;
1532 }
1533 }
1534 }
1535 last_i = mode;
1536 last_max = stats->pile_count(last_i);
1537 total_max += last_max;
1538 if (last_max <= total_max / mode_factor) {
1539 mode = 0;
1540 }
1541 modelist[mode_count] = mode;
1542 }
1543 }
1544
1545 /**********************************************************************
1546 * pick_x_height
1547 *
1548 * Choose based on the height modes the best x height value.
1549 **********************************************************************/
1550
pick_x_height(TO_ROW * row,int modelist[],int lefts[],int rights[],STATS * heightstat,int mode_threshold)1551 void pick_x_height(TO_ROW *row, // row to do
1552 int modelist[], int lefts[], int rights[], STATS *heightstat,
1553 int mode_threshold) {
1554 int x;
1555 int y;
1556 int z;
1557 float ratio;
1558 int found_one_bigger = false;
1559 int best_x_height = 0;
1560 int best_asc = 0;
1561 int num_in_best;
1562
1563 for (x = 0; x < MODENUM; x++) {
1564 for (y = 0; y < MODENUM; y++) {
1565 /* Check for two modes */
1566 if (modelist[x] && modelist[y] && heightstat->pile_count(modelist[x]) > mode_threshold &&
1567 (!textord_ocropus_mode || std::min(rights[modelist[x]], rights[modelist[y]]) >
1568 std::max(lefts[modelist[x]], lefts[modelist[y]]))) {
1569 ratio = static_cast<float>(modelist[y]) / static_cast<float>(modelist[x]);
1570 if (1.2 < ratio && ratio < 1.8) {
1571 /* Two modes found */
1572 best_x_height = modelist[x];
1573 num_in_best = heightstat->pile_count(modelist[x]);
1574
1575 /* Try to get one higher */
1576 do {
1577 found_one_bigger = false;
1578 for (z = 0; z < MODENUM; z++) {
1579 if (modelist[z] == best_x_height + 1 &&
1580 (!textord_ocropus_mode || std::min(rights[modelist[x]], rights[modelist[y]]) >
1581 std::max(lefts[modelist[x]], lefts[modelist[y]]))) {
1582 ratio = static_cast<float>(modelist[y]) / static_cast<float>(modelist[z]);
1583 if ((1.2 < ratio && ratio < 1.8) &&
1584 /* Should be half of best */
1585 heightstat->pile_count(modelist[z]) > num_in_best * 0.5) {
1586 best_x_height++;
1587 found_one_bigger = true;
1588 break;
1589 }
1590 }
1591 }
1592 } while (found_one_bigger);
1593
1594 /* try to get a higher ascender */
1595
1596 best_asc = modelist[y];
1597 num_in_best = heightstat->pile_count(modelist[y]);
1598
1599 /* Try to get one higher */
1600 do {
1601 found_one_bigger = false;
1602 for (z = 0; z < MODENUM; z++) {
1603 if (modelist[z] > best_asc &&
1604 (!textord_ocropus_mode || std::min(rights[modelist[x]], rights[modelist[y]]) >
1605 std::max(lefts[modelist[x]], lefts[modelist[y]]))) {
1606 ratio = static_cast<float>(modelist[z]) / static_cast<float>(best_x_height);
1607 if ((1.2 < ratio && ratio < 1.8) &&
1608 /* Should be half of best */
1609 heightstat->pile_count(modelist[z]) > num_in_best * 0.5) {
1610 best_asc = modelist[z];
1611 found_one_bigger = true;
1612 break;
1613 }
1614 }
1615 }
1616 } while (found_one_bigger);
1617
1618 row->xheight = static_cast<float>(best_x_height);
1619 row->ascrise = static_cast<float>(best_asc) - best_x_height;
1620 return;
1621 }
1622 }
1623 }
1624 }
1625
1626 best_x_height = modelist[0]; /* Single Mode found */
1627 num_in_best = heightstat->pile_count(best_x_height);
1628 do {
1629 /* Try to get one higher */
1630 found_one_bigger = false;
1631 for (z = 1; z < MODENUM; z++) {
1632 /* Should be half of best */
1633 if ((modelist[z] == best_x_height + 1) &&
1634 (heightstat->pile_count(modelist[z]) > num_in_best * 0.5)) {
1635 best_x_height++;
1636 found_one_bigger = true;
1637 break;
1638 }
1639 }
1640 } while (found_one_bigger);
1641
1642 row->ascrise = 0.0f;
1643 row->xheight = static_cast<float>(best_x_height);
1644 if (row->xheight == 0) {
1645 row->xheight = -1.0f;
1646 }
1647 }
1648
1649 } // namespace tesseract
1650