Tesseract  3.02
tesseract-ocr/textord/oldbasel.cpp
Go to the documentation of this file.
00001 /**********************************************************************
00002  * File:        oldbasel.cpp  (Formerly oldbl.c)
00003  * Description: A re-implementation of the old baseline algorithm.
00004  * Author:              Ray Smith
00005  * Created:             Wed Oct  6 09:41:48 BST 1993
00006  *
00007  * (C) Copyright 1993, Hewlett-Packard Ltd.
00008  ** Licensed under the Apache License, Version 2.0 (the "License");
00009  ** you may not use this file except in compliance with the License.
00010  ** You may obtain a copy of the License at
00011  ** http://www.apache.org/licenses/LICENSE-2.0
00012  ** Unless required by applicable law or agreed to in writing, software
00013  ** distributed under the License is distributed on an "AS IS" BASIS,
00014  ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00015  ** See the License for the specific language governing permissions and
00016  ** limitations under the License.
00017  *
00018  **********************************************************************/
00019 
00020 #include "mfcpch.h"
00021 #include "ccstruct.h"
00022 #include          "statistc.h"
00023 #include          "quadlsq.h"
00024 #include          "detlinefit.h"
00025 #include          "makerow.h"
00026 #include          "drawtord.h"
00027 #include          "oldbasel.h"
00028 #include          "textord.h"
00029 #include          "tprintf.h"
00030 
00031 // Include automatically generated configuration file if running autoconf.
00032 #ifdef HAVE_CONFIG_H
00033 #include "config_auto.h"
00034 #endif
00035 
00036 #define EXTERN
00037 
00038 EXTERN BOOL_VAR (textord_really_old_xheight, FALSE,
00039 "Use original wiseowl xheight");
00040 EXTERN BOOL_VAR (textord_oldbl_debug, FALSE, "Debug old baseline generation");
00041 EXTERN BOOL_VAR (textord_debug_baselines, FALSE, "Debug baseline generation");
00042 EXTERN BOOL_VAR (textord_oldbl_paradef, TRUE, "Use para default mechanism");
00043 EXTERN BOOL_VAR (textord_oldbl_split_splines, TRUE, "Split stepped splines");
00044 EXTERN BOOL_VAR (textord_oldbl_merge_parts, TRUE, "Merge suspect partitions");
00045 EXTERN BOOL_VAR (oldbl_corrfix, TRUE, "Improve correlation of heights");
00046 EXTERN BOOL_VAR (oldbl_xhfix, FALSE,
00047 "Fix bug in modes threshold for xheights");
00048 EXTERN BOOL_VAR(textord_ocropus_mode, FALSE, "Make baselines for ocropus");
00049 EXTERN double_VAR (oldbl_xhfract, 0.4, "Fraction of est allowed in calc");
00050 EXTERN INT_VAR (oldbl_holed_losscount, 10,
00051 "Max lost before fallback line used");
00052 EXTERN double_VAR (oldbl_dot_error_size, 1.26, "Max aspect ratio of a dot");
00053 EXTERN double_VAR (textord_oldbl_jumplimit, 0.15,
00054 "X fraction for new partition");
00055 
00056 #define TURNLIMIT          1     /*min size for turning point */
00057 #define X_HEIGHT_FRACTION  0.7   /*x-height/caps height */
00058 #define DESCENDER_FRACTION 0.5   /*descender/x-height */
00059 #define MIN_ASC_FRACTION   0.20  /*min size of ascenders */
00060 #define MIN_DESC_FRACTION  0.25  /*min size of descenders */
00061 #define MINASCRISE         2.0   /*min ascender/desc step */
00062 #define MAXHEIGHTVARIANCE  0.15  /*accepted variation in x-height */
00063 #define MAXHEIGHT          300   /*max blob height */
00064 #define MAXOVERLAP         0.1   /*max 10% missed overlap */
00065 #define MAXBADRUN          2     /*max non best for failed */
00066 #define HEIGHTBUCKETS      200   /* Num of buckets */
00067 #define DELTAHEIGHT        5.0   /* Small amount of diff */
00068 #define GOODHEIGHT         5
00069 #define MAXLOOPS           10
00070 #define MODENUM            10
00071 #define MAXPARTS      6
00072 #define SPLINESIZE      23
00073 
00074 #define ABS(x) ((x)<0 ? (-(x)) : (x))
00075 
00076 namespace tesseract {
00077 
00078 /**********************************************************************
00079  * make_old_baselines
00080  *
00081  * Top level function to make baselines the old way.
00082  **********************************************************************/
00083 
00084 void Textord::make_old_baselines(TO_BLOCK *block,   // block to do
00085                                  BOOL8 testing_on,  // correct orientation
00086                                  float gradient) {
00087   QSPLINE *prev_baseline;        // baseline of previous row
00088   TO_ROW *row;                   // current row
00089   TO_ROW_IT row_it = block->get_rows();
00090   BLOBNBOX_IT blob_it;
00091 
00092   prev_baseline = NULL;          // nothing yet
00093   for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
00094     row = row_it.data();
00095     find_textlines(block, row, 2, NULL);
00096     if (row->xheight <= 0 && prev_baseline != NULL)
00097       find_textlines(block, row, 2, prev_baseline);
00098     if (row->xheight > 0) {  // was a good one
00099       prev_baseline = &row->baseline;
00100     } else {
00101       prev_baseline = NULL;
00102       blob_it.set_to_list(row->blob_list());
00103       if (textord_debug_baselines)
00104         tprintf("Row baseline generation failed on row at (%d,%d)\n",
00105           blob_it.data()->bounding_box().left(),
00106           blob_it.data()->bounding_box().bottom());
00107     }
00108   }
00109   correlate_lines(block, gradient);
00110   block->block->set_xheight(block->xheight);
00111 }
00112 
00113 
00114 /**********************************************************************
00115  * correlate_lines
00116  *
00117  * Correlate the x-heights and ascender heights of a block to fill-in
00118  * the ascender height and descender height for rows without one.
00119  * Also fix baselines of rows without a decent fit.
00120  **********************************************************************/
00121 
00122 void Textord::correlate_lines(TO_BLOCK *block, float gradient) {
00123   TO_ROW **rows;                 //array of ptrs
00124   int rowcount;                  /*no of rows to do */
00125   register int rowindex;         /*no of row */
00126                                  //iterator
00127   TO_ROW_IT row_it = block->get_rows ();
00128 
00129   rowcount = row_it.length ();
00130   if (rowcount == 0) {
00131                                  //default value
00132     block->xheight = block->line_size;
00133     return;                      /*none to do */
00134   }
00135   rows = (TO_ROW **) alloc_mem (rowcount * sizeof (TO_ROW *));
00136   rowindex = 0;
00137   for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ())
00138                                  //make array
00139     rows[rowindex++] = row_it.data ();
00140 
00141                                  /*try to fix bad lines */
00142   correlate_neighbours(block, rows, rowcount);
00143 
00144   if (textord_really_old_xheight || textord_old_xheight) {
00145     block->xheight = (float) correlate_with_stats(rows, rowcount, block);
00146     if (block->xheight <= 0)
00147       block->xheight = block->line_size * tesseract::CCStruct::kXHeightFraction;
00148     if (block->xheight < textord_min_xheight)
00149       block->xheight = (float) textord_min_xheight;
00150   } else {
00151     compute_block_xheight(block, gradient);
00152   }
00153 
00154   free_mem(rows);
00155 }
00156 
00157 
00158 /**********************************************************************
00159  * correlate_neighbours
00160  *
00161  * Try to fix rows that had a bad spline fit by using neighbours.
00162  **********************************************************************/
00163 
00164 void Textord::correlate_neighbours(TO_BLOCK *block,  // block rows are in.
00165                                    TO_ROW **rows,    // rows of block.
00166                                    int rowcount) {   // no of rows to do.
00167   TO_ROW *row;                   /*current row */
00168   register int rowindex;         /*no of row */
00169   register int otherrow;         /*second row */
00170   int upperrow;                  /*row above to use */
00171   int lowerrow;                  /*row below to use */
00172   float biggest;
00173 
00174   for (rowindex = 0; rowindex < rowcount; rowindex++) {
00175     row = rows[rowindex];        /*current row */
00176     if (row->xheight < 0) {
00177                                  /*quadratic failed */
00178       for (otherrow = rowindex - 2;
00179         otherrow >= 0
00180         && (rows[otherrow]->xheight < 0.0
00181         || !row->baseline.overlap (&rows[otherrow]->baseline,
00182         MAXOVERLAP)); otherrow--);
00183       upperrow = otherrow;       /*decent row above */
00184       for (otherrow = rowindex + 1;
00185         otherrow < rowcount
00186         && (rows[otherrow]->xheight < 0.0
00187         || !row->baseline.overlap (&rows[otherrow]->baseline,
00188         MAXOVERLAP)); otherrow++);
00189       lowerrow = otherrow;       /*decent row below */
00190       if (upperrow >= 0)
00191         find_textlines(block, row, 2, &rows[upperrow]->baseline);
00192       if (row->xheight < 0 && lowerrow < rowcount)
00193         find_textlines(block, row, 2, &rows[lowerrow]->baseline);
00194       if (row->xheight < 0) {
00195         if (upperrow >= 0)
00196           find_textlines(block, row, 1, &rows[upperrow]->baseline);
00197         else if (lowerrow < rowcount)
00198           find_textlines(block, row, 1, &rows[lowerrow]->baseline);
00199       }
00200     }
00201   }
00202 
00203   for (biggest = 0.0f, rowindex = 0; rowindex < rowcount; rowindex++) {
00204     row = rows[rowindex];        /*current row */
00205     if (row->xheight < 0)        /*linear failed */
00206                                  /*make do */
00207         row->xheight = -row->xheight;
00208     biggest = MAX (biggest, row->xheight);
00209   }
00210 }
00211 
00212 
00213 /**********************************************************************
00214  * correlate_with_stats
00215  *
00216  * correlate the x-heights and ascender heights of a block to fill-in
00217  * the ascender height and descender height for rows without one.
00218  **********************************************************************/
00219 
00220 int Textord::correlate_with_stats(TO_ROW **rows,  // rows of block.
00221                                   int rowcount,   // no of rows to do.
00222                                   TO_BLOCK* block) {
00223   TO_ROW *row;                   /*current row */
00224   register int rowindex;         /*no of row */
00225   float lineheight;              /*mean x-height */
00226   float ascheight;               /*average ascenders */
00227   float minascheight;            /*min allowed ascheight */
00228   int xcount;                    /*no of samples for xheight */
00229   float fullheight;              /*mean top height */
00230   int fullcount;                 /*no of samples */
00231   float descheight;              /*mean descender drop */
00232   float mindescheight;           /*min allowed descheight */
00233   int desccount;                 /*no of samples */
00234   float xshift;                  /*shift in xheight */
00235 
00236                                  /*no samples */
00237   xcount = fullcount = desccount = 0;
00238   lineheight = ascheight = fullheight = descheight = 0.0;
00239   for (rowindex = 0; rowindex < rowcount; rowindex++) {
00240     row = rows[rowindex];        /*current row */
00241     if (row->ascrise > 0.0) {    /*got ascenders? */
00242       lineheight += row->xheight;/*average x-heights */
00243       ascheight += row->ascrise; /*average ascenders */
00244       xcount++;
00245     }
00246     else {
00247       fullheight += row->xheight;/*assume full height */
00248       fullcount++;
00249     }
00250     if (row->descdrop < 0.0) {   /*got descenders? */
00251                                  /*average descenders */
00252       descheight += row->descdrop;
00253       desccount++;
00254     }
00255   }
00256 
00257   if (xcount > 0 && (!oldbl_corrfix || xcount >= fullcount)) {
00258     lineheight /= xcount;        /*average x-height */
00259                                  /*average caps height */
00260     fullheight = lineheight + ascheight / xcount;
00261                                  /*must be decent size */
00262     if (fullheight < lineheight * (1 + MIN_ASC_FRACTION))
00263       fullheight = lineheight * (1 + MIN_ASC_FRACTION);
00264   }
00265   else {
00266     fullheight /= fullcount;     /*average max height */
00267                                  /*guess x-height */
00268     lineheight = fullheight * X_HEIGHT_FRACTION;
00269   }
00270   if (desccount > 0 && (!oldbl_corrfix || desccount >= rowcount / 2))
00271     descheight /= desccount;     /*average descenders */
00272   else
00273                                  /*guess descenders */
00274     descheight = -lineheight * DESCENDER_FRACTION;
00275 
00276   if (lineheight > 0.0f)
00277     block->block->set_cell_over_xheight((fullheight - descheight) / lineheight);
00278 
00279   minascheight = lineheight * MIN_ASC_FRACTION;
00280   mindescheight = -lineheight * MIN_DESC_FRACTION;
00281   for (rowindex = 0; rowindex < rowcount; rowindex++) {
00282     row = rows[rowindex];        /*do each row */
00283     row->all_caps = FALSE;
00284     if (row->ascrise / row->xheight < MIN_ASC_FRACTION) {
00285     /*no ascenders */
00286       if (row->xheight >= lineheight * (1 - MAXHEIGHTVARIANCE)
00287       && row->xheight <= lineheight * (1 + MAXHEIGHTVARIANCE)) {
00288         row->ascrise = fullheight - lineheight;
00289                                  /*shift in x */
00290         xshift = lineheight - row->xheight;
00291                                  /*set to average */
00292         row->xheight = lineheight;
00293 
00294       }
00295       else if (row->xheight >= fullheight * (1 - MAXHEIGHTVARIANCE)
00296       && row->xheight <= fullheight * (1 + MAXHEIGHTVARIANCE)) {
00297         row->ascrise = row->xheight - lineheight;
00298         xshift = -row->ascrise;  /*shift in x */
00299                                  /*set to average */
00300         row->xheight = lineheight;
00301         row->all_caps = TRUE;
00302       }
00303       else {
00304         row->ascrise = (fullheight - lineheight) * row->xheight
00305           / fullheight;
00306         xshift = -row->ascrise;  /*shift in x */
00307                                  /*scale it */
00308         row->xheight -= row->ascrise;
00309         row->all_caps = TRUE;
00310       }
00311       if (row->ascrise < minascheight)
00312         row->ascrise =
00313           row->xheight * ((1.0 - X_HEIGHT_FRACTION) / X_HEIGHT_FRACTION);
00314     }
00315     if (row->descdrop > mindescheight) {
00316       if (row->xheight >= lineheight * (1 - MAXHEIGHTVARIANCE)
00317         && row->xheight <= lineheight * (1 + MAXHEIGHTVARIANCE))
00318                                  /*set to average */
00319           row->descdrop = descheight;
00320       else
00321         row->descdrop = -row->xheight * DESCENDER_FRACTION;
00322     }
00323   }
00324   return (int) lineheight;       //block xheight
00325 }
00326 
00327 
00328 /**********************************************************************
00329  * find_textlines
00330  *
00331  * Compute the baseline for the given row.
00332  **********************************************************************/
00333 
00334 void Textord::find_textlines(TO_BLOCK *block,  // block row is in
00335                              TO_ROW *row,      // row to do
00336                              int degree,       // required approximation
00337                              QSPLINE *spline) {  // starting spline
00338   int partcount;                 /*no of partitions of */
00339   BOOL8 holed_line = FALSE;      //lost too many blobs
00340   int bestpart;                  /*biggest partition */
00341   char *partids;                 /*partition no of each blob */
00342   int partsizes[MAXPARTS];       /*no in each partition */
00343   int lineheight;                /*guessed x-height */
00344   float jumplimit;               /*allowed delta change */
00345   int *xcoords;                  /*useful sample points */
00346   int *ycoords;                  /*useful sample points */
00347   TBOX *blobcoords;               /*edges of blob rectangles */
00348   int blobcount;                 /*no of blobs on line */
00349   float *ydiffs;                 /*diffs from 1st approx */
00350   int pointcount;                /*no of coords */
00351   int xstarts[SPLINESIZE + 1];   //segment boundaries
00352   int segments;                  //no of segments
00353 
00354                                  //no of blobs in row
00355   blobcount = row->blob_list ()->length ();
00356   partids = (char *) alloc_mem (blobcount * sizeof (char));
00357   xcoords = (int *) alloc_mem (blobcount * sizeof (int));
00358   ycoords = (int *) alloc_mem (blobcount * sizeof (int));
00359   blobcoords = (TBOX *) alloc_mem (blobcount * sizeof (TBOX));
00360   ydiffs = (float *) alloc_mem (blobcount * sizeof (float));
00361 
00362   lineheight = get_blob_coords (row, (int) block->line_size, blobcoords,
00363     holed_line, blobcount);
00364                                  /*limit for line change */
00365   jumplimit = lineheight * textord_oldbl_jumplimit;
00366   if (jumplimit < MINASCRISE)
00367     jumplimit = MINASCRISE;
00368 
00369   if (textord_oldbl_debug) {
00370     tprintf
00371       ("\nInput height=%g, Estimate x-height=%d pixels, jumplimit=%.2f\n",
00372       block->line_size, lineheight, jumplimit);
00373   }
00374   if (holed_line)
00375     make_holed_baseline (blobcoords, blobcount, spline, &row->baseline,
00376       row->line_m ());
00377   else
00378     make_first_baseline (blobcoords, blobcount,
00379       xcoords, ycoords, spline, &row->baseline, jumplimit);
00380 #ifndef GRAPHICS_DISABLED
00381   if (textord_show_final_rows)
00382     row->baseline.plot (to_win, ScrollView::GOLDENROD);
00383 #endif
00384   if (blobcount > 1) {
00385     bestpart = partition_line (blobcoords, blobcount,
00386       &partcount, partids, partsizes,
00387       &row->baseline, jumplimit, ydiffs);
00388     pointcount = partition_coords (blobcoords, blobcount,
00389       partids, bestpart, xcoords, ycoords);
00390     segments = segment_spline (blobcoords, blobcount,
00391       xcoords, ycoords,
00392       degree, pointcount, xstarts);
00393     if (!holed_line) {
00394       do {
00395         row->baseline = QSPLINE (xstarts, segments,
00396           xcoords, ycoords, pointcount, degree);
00397       }
00398       while (textord_oldbl_split_splines
00399         && split_stepped_spline (&row->baseline, jumplimit / 2,
00400         xcoords, xstarts, segments));
00401     }
00402     find_lesser_parts(row,
00403                       blobcoords,
00404                       blobcount,
00405                       partids,
00406                       partsizes,
00407                       partcount,
00408                       bestpart);
00409 
00410   }
00411   else {
00412     row->xheight = -1.0f;        /*failed */
00413     row->descdrop = 0.0f;
00414     row->ascrise = 0.0f;
00415   }
00416   row->baseline.extrapolate (row->line_m (),
00417     block->block->bounding_box ().left (),
00418     block->block->bounding_box ().right ());
00419 
00420   if (textord_really_old_xheight) {
00421     old_first_xheight (row, blobcoords, lineheight,
00422       blobcount, &row->baseline, jumplimit);
00423   } else if (textord_old_xheight) {
00424     make_first_xheight (row, blobcoords, lineheight, (int) block->line_size,
00425                         blobcount, &row->baseline, jumplimit);
00426   } else {
00427     compute_row_xheight(row, block->block->classify_rotation(),
00428                         row->line_m(), block->line_size);
00429   }
00430   free_mem(partids);
00431   free_mem(xcoords);
00432   free_mem(ycoords);
00433   free_mem(blobcoords);
00434   free_mem(ydiffs);
00435 }
00436 
00437 }  // namespace tesseract.
00438 
00439 
00440 /**********************************************************************
00441  * get_blob_coords
00442  *
00443  * Fill the blobcoords array with the coordinates of the blobs
00444  * in the row. The return value is the first guess at the line height.
00445  **********************************************************************/
00446 
00447 int get_blob_coords(                    //get boxes
00448                     TO_ROW *row,        //row to use
00449                     inT32 lineheight,   //block level
00450                     TBOX *blobcoords,    //ouput boxes
00451                     BOOL8 &holed_line,  //lost a lot of blobs
00452                     int &outcount       //no of real blobs
00453                    ) {
00454                                  //blobs
00455   BLOBNBOX_IT blob_it = row->blob_list ();
00456   register int blobindex;        /*no along text line */
00457   int losscount;                 //lost blobs
00458   int maxlosscount;              //greatest lost blobs
00459                                  /*height stat collection */
00460   STATS heightstat (0, MAXHEIGHT);
00461 
00462   if (blob_it.empty ())
00463     return 0;                    //none
00464   maxlosscount = 0;
00465   losscount = 0;
00466   blob_it.mark_cycle_pt ();
00467   blobindex = 0;
00468   do {
00469     blobcoords[blobindex] = box_next_pre_chopped (&blob_it);
00470     if (blobcoords[blobindex].height () > lineheight * 0.25)
00471       heightstat.add (blobcoords[blobindex].height (), 1);
00472     if (blobindex == 0
00473       || blobcoords[blobindex].height () > lineheight * 0.25
00474     || blob_it.cycled_list ()) {
00475       blobindex++;               /*no of merged blobs */
00476       losscount = 0;
00477     }
00478     else {
00479       if (blobcoords[blobindex].height ()
00480         < blobcoords[blobindex].width () * oldbl_dot_error_size
00481         && blobcoords[blobindex].width ()
00482       < blobcoords[blobindex].height () * oldbl_dot_error_size) {
00483                                  //counts as dot
00484         blobindex++;
00485         losscount = 0;
00486       }
00487       else {
00488         losscount++;             //lost it
00489         if (losscount > maxlosscount)
00490                                  //remember max
00491             maxlosscount = losscount;
00492       }
00493     }
00494   }
00495   while (!blob_it.cycled_list ());
00496 
00497   holed_line = maxlosscount > oldbl_holed_losscount;
00498   outcount = blobindex;          /*total blobs */
00499 
00500   if (heightstat.get_total () > 1)
00501                                  /*guess x-height */
00502     return (int) heightstat.ile (0.25);
00503   else
00504     return blobcoords[0].height ();
00505 }
00506 
00507 
00508 /**********************************************************************
00509  * make_first_baseline
00510  *
00511  * Make the first estimate at a baseline, either by shifting
00512  * a supplied previous spline, or by doing a piecewise linear
00513  * approximation using all the blobs.
00514  **********************************************************************/
00515 
00516 void
00517 make_first_baseline (            //initial approximation
00518 TBOX blobcoords[],                /*blob bounding boxes */
00519 int blobcount,                   /*no of blobcoords */
00520 int xcoords[],                   /*coords for spline */
00521 int ycoords[],                   /*approximator */
00522 QSPLINE * spline,                /*initial spline */
00523 QSPLINE * baseline,              /*output spline */
00524 float jumplimit                  /*guess half descenders */
00525 ) {
00526   int leftedge;                  /*left edge of line */
00527   int rightedge;                 /*right edge of line */
00528   int blobindex;                 /*current blob */
00529   int segment;                   /*current segment */
00530   float prevy, thisy, nexty;     /*3 y coords */
00531   float y1, y2, y3;              /*3 smooth blobs */
00532   float maxmax, minmin;          /*absolute limits */
00533   int x2 = 0;                    /*right edge of old y3 */
00534   int ycount;                    /*no of ycoords in use */
00535   float yturns[SPLINESIZE];      /*y coords of turn pts */
00536   int xturns[SPLINESIZE];        /*xcoords of turn pts */
00537   int xstarts[SPLINESIZE + 1];
00538   int segments;                  //no of segments
00539   ICOORD shift;                  //shift of spline
00540 
00541   prevy = 0;
00542                                  /*left edge of row */
00543   leftedge = blobcoords[0].left ();
00544                                  /*right edge of line */
00545   rightedge = blobcoords[blobcount - 1].right ();
00546   if (spline == NULL             /*no given spline */
00547     || spline->segments < 3      /*or trivial */
00548                                  /*or too non-overlap */
00549     || spline->xcoords[1] > leftedge + MAXOVERLAP * (rightedge - leftedge)
00550     || spline->xcoords[spline->segments - 1] < rightedge
00551   - MAXOVERLAP * (rightedge - leftedge)) {
00552     if (textord_oldbl_paradef)
00553       return;                    //use default
00554     xstarts[0] = blobcoords[0].left () - 1;
00555     for (blobindex = 0; blobindex < blobcount; blobindex++) {
00556       xcoords[blobindex] = (blobcoords[blobindex].left ()
00557         + blobcoords[blobindex].right ()) / 2;
00558       ycoords[blobindex] = blobcoords[blobindex].bottom ();
00559     }
00560     xstarts[1] = blobcoords[blobcount - 1].right () + 1;
00561     segments = 1;                /*no of segments */
00562 
00563                                  /*linear */
00564     *baseline = QSPLINE (xstarts, segments, xcoords, ycoords, blobcount, 1);
00565 
00566     if (blobcount >= 3) {
00567       y1 = y2 = y3 = 0.0f;
00568       ycount = 0;
00569       segment = 0;               /*no of segments */
00570       maxmax = minmin = 0.0f;
00571       thisy = ycoords[0] - baseline->y (xcoords[0]);
00572       nexty = ycoords[1] - baseline->y (xcoords[1]);
00573       for (blobindex = 2; blobindex < blobcount; blobindex++) {
00574         prevy = thisy;           /*shift ycoords */
00575         thisy = nexty;
00576         nexty = ycoords[blobindex] - baseline->y (xcoords[blobindex]);
00577                                  /*middle of smooth y */
00578         if (ABS (thisy - prevy) < jumplimit && ABS (thisy - nexty) < jumplimit) {
00579           y1 = y2;               /*shift window */
00580           y2 = y3;
00581           y3 = thisy;            /*middle point */
00582           ycount++;
00583                                  /*local max */
00584           if (ycount >= 3 && ((y1 < y2 && y2 >= y3)
00585                                  /*local min */
00586           || (y1 > y2 && y2 <= y3))) {
00587             if (segment < SPLINESIZE - 2) {
00588                                  /*turning pt */
00589               xturns[segment] = x2;
00590               yturns[segment] = y2;
00591               segment++;         /*no of spline segs */
00592             }
00593           }
00594           if (ycount == 1) {
00595             maxmax = minmin = y3;/*initialise limits */
00596           }
00597           else {
00598             if (y3 > maxmax)
00599               maxmax = y3;       /*biggest max */
00600             if (y3 < minmin)
00601               minmin = y3;       /*smallest min */
00602           }
00603                                  /*possible turning pt */
00604           x2 = blobcoords[blobindex - 1].right ();
00605         }
00606       }
00607 
00608       jumplimit *= 1.2;
00609                                  /*must be wavy */
00610       if (maxmax - minmin > jumplimit) {
00611         ycount = segment;        /*no of segments */
00612         for (blobindex = 0, segment = 1; blobindex < ycount;
00613         blobindex++) {
00614           if (yturns[blobindex] > minmin + jumplimit
00615           || yturns[blobindex] < maxmax - jumplimit) {
00616                                  /*significant peak */
00617             if (segment == 1
00618               || yturns[blobindex] > prevy + jumplimit
00619             || yturns[blobindex] < prevy - jumplimit) {
00620                                  /*different to previous */
00621               xstarts[segment] = xturns[blobindex];
00622               segment++;
00623               prevy = yturns[blobindex];
00624             }
00625                                  /*bigger max */
00626             else if ((prevy > minmin + jumplimit && yturns[blobindex] > prevy)
00627                                  /*smaller min */
00628             || (prevy < maxmax - jumplimit && yturns[blobindex] < prevy)) {
00629               xstarts[segment - 1] = xturns[blobindex];
00630                                  /*improved previous */
00631               prevy = yturns[blobindex];
00632             }
00633           }
00634         }
00635         xstarts[segment] = blobcoords[blobcount - 1].right () + 1;
00636         segments = segment;      /*no of segments */
00637                                  /*linear */
00638         *baseline = QSPLINE (xstarts, segments, xcoords, ycoords, blobcount, 1);
00639       }
00640     }
00641   }
00642   else {
00643     *baseline = *spline;         /*copy it */
00644     shift = ICOORD (0, (inT16) (blobcoords[0].bottom ()
00645       - spline->y (blobcoords[0].right ())));
00646     baseline->move (shift);
00647   }
00648 }
00649 
00650 
00651 /**********************************************************************
00652  * make_holed_baseline
00653  *
00654  * Make the first estimate at a baseline, either by shifting
00655  * a supplied previous spline, or by doing a piecewise linear
00656  * approximation using all the blobs.
00657  **********************************************************************/
00658 
00659 void
00660 make_holed_baseline (            //initial approximation
00661 TBOX blobcoords[],                /*blob bounding boxes */
00662 int blobcount,                   /*no of blobcoords */
00663 QSPLINE * spline,                /*initial spline */
00664 QSPLINE * baseline,              /*output spline */
00665 float gradient                   //of line
00666 ) {
00667   int leftedge;                  /*left edge of line */
00668   int rightedge;                 /*right edge of line */
00669   int blobindex;                 /*current blob */
00670   float x;                       //centre of row
00671   ICOORD shift;                  //shift of spline
00672 
00673   tesseract::DetLineFit lms;  // straight baseline
00674   inT32 xstarts[2];              //straight line
00675   double coeffs[3];
00676   float c;                       //line parameter
00677 
00678                                  /*left edge of row */
00679   leftedge = blobcoords[0].left ();
00680                                  /*right edge of line */
00681   rightedge = blobcoords[blobcount - 1].right();
00682   for (blobindex = 0; blobindex < blobcount; blobindex++) {
00683     lms.Add(ICOORD((blobcoords[blobindex].left() +
00684                     blobcoords[blobindex].right()) / 2,
00685                    blobcoords[blobindex].bottom()));
00686   }
00687   lms.ConstrainedFit(gradient, &c);
00688   xstarts[0] = leftedge;
00689   xstarts[1] = rightedge;
00690   coeffs[0] = 0;
00691   coeffs[1] = gradient;
00692   coeffs[2] = c;
00693   *baseline = QSPLINE (1, xstarts, coeffs);
00694   if (spline != NULL             /*no given spline */
00695     && spline->segments >= 3     /*or trivial */
00696                                  /*or too non-overlap */
00697     && spline->xcoords[1] <= leftedge + MAXOVERLAP * (rightedge - leftedge)
00698     && spline->xcoords[spline->segments - 1] >= rightedge
00699   - MAXOVERLAP * (rightedge - leftedge)) {
00700     *baseline = *spline;         /*copy it */
00701     x = (leftedge + rightedge) / 2.0;
00702     shift = ICOORD (0, (inT16) (gradient * x + c - spline->y (x)));
00703     baseline->move (shift);
00704   }
00705 }
00706 
00707 
00708 /**********************************************************************
00709  * partition_line
00710  *
00711  * Partition a row of blobs into different groups of continuous
00712  * y position. jumplimit specifies the max allowable limit on a jump
00713  * before a new partition is started.
00714  * The return value is the biggest partition
00715  **********************************************************************/
00716 
00717 int
00718 partition_line (                 //partition blobs
00719 TBOX blobcoords[],                //bounding boxes
00720 int blobcount,                   /*no of blobs on row */
00721 int *numparts,                   /*number of partitions */
00722 char partids[],                  /*partition no of each blob */
00723 int partsizes[],                 /*no in each partition */
00724 QSPLINE * spline,                /*curve to fit to */
00725 float jumplimit,                 /*allowed delta change */
00726 float ydiffs[]                   /*diff from spline */
00727 ) {
00728   register int blobindex;        /*no along text line */
00729   int bestpart;                  /*best new partition */
00730   int biggestpart;               /*part with most members */
00731   float diff;                    /*difference from line */
00732   int startx;                    /*index of start blob */
00733   float partdiffs[MAXPARTS];     /*step between parts */
00734 
00735   for (bestpart = 0; bestpart < MAXPARTS; bestpart++)
00736     partsizes[bestpart] = 0;     /*zero them all */
00737 
00738   startx = get_ydiffs (blobcoords, blobcount, spline, ydiffs);
00739   *numparts = 1;                 /*1 partition */
00740   bestpart = -1;                 /*first point */
00741   float drift = 0.0f;
00742   float last_delta = 0.0f;
00743   for (blobindex = startx; blobindex < blobcount; blobindex++) {
00744   /*do each blob in row */
00745     diff = ydiffs[blobindex];    /*diff from line */
00746     if (textord_oldbl_debug) {
00747       tprintf ("%d(%d,%d), ", blobindex,
00748         blobcoords[blobindex].left (),
00749         blobcoords[blobindex].bottom ());
00750     }
00751     bestpart = choose_partition(diff, partdiffs, bestpart, jumplimit,
00752                                 &drift, &last_delta, numparts);
00753                                  /*record partition */
00754     partids[blobindex] = bestpart;
00755     partsizes[bestpart]++;       /*another in it */
00756   }
00757 
00758   bestpart = -1;                 /*first point */
00759   drift = 0.0f;
00760   last_delta = 0.0f;
00761   partsizes[0]--;                /*doing 1st pt again */
00762                                  /*do each blob in row */
00763   for (blobindex = startx; blobindex >= 0; blobindex--) {
00764     diff = ydiffs[blobindex];    /*diff from line */
00765     if (textord_oldbl_debug) {
00766       tprintf ("%d(%d,%d), ", blobindex,
00767         blobcoords[blobindex].left (),
00768         blobcoords[blobindex].bottom ());
00769     }
00770     bestpart = choose_partition(diff, partdiffs, bestpart, jumplimit,
00771                                 &drift, &last_delta, numparts);
00772                                  /*record partition */
00773     partids[blobindex] = bestpart;
00774     partsizes[bestpart]++;       /*another in it */
00775   }
00776 
00777   for (biggestpart = 0, bestpart = 1; bestpart < *numparts; bestpart++)
00778     if (partsizes[bestpart] >= partsizes[biggestpart])
00779       biggestpart = bestpart;    /*new biggest */
00780   if (textord_oldbl_merge_parts)
00781     merge_oldbl_parts(blobcoords,
00782                       blobcount,
00783                       partids,
00784                       partsizes,
00785                       biggestpart,
00786                       jumplimit);
00787   return biggestpart;            /*biggest partition */
00788 }
00789 
00790 
00791 /**********************************************************************
00792  * merge_oldbl_parts
00793  *
00794  * For any adjacent group of blobs in a different part, put them in the
00795  * main part if they fit closely to neighbours in the main part.
00796  **********************************************************************/
00797 
00798 void
00799 merge_oldbl_parts (              //partition blobs
00800 TBOX blobcoords[],                //bounding boxes
00801 int blobcount,                   /*no of blobs on row */
00802 char partids[],                  /*partition no of each blob */
00803 int partsizes[],                 /*no in each partition */
00804 int biggestpart,                 //major partition
00805 float jumplimit                  /*allowed delta change */
00806 ) {
00807   BOOL8 found_one;               //found a bestpart blob
00808   BOOL8 close_one;               //found was close enough
00809   register int blobindex;        /*no along text line */
00810   int prevpart;                  //previous iteration
00811   int runlength;                 //no in this part
00812   float diff;                    /*difference from line */
00813   int startx;                    /*index of start blob */
00814   int test_blob;                 //another index
00815   FCOORD coord;                  //blob coordinate
00816   float m, c;                    //fitted line
00817   QLSQ stats;                    //line stuff
00818 
00819   prevpart = biggestpart;
00820   runlength = 0;
00821   startx = 0;
00822   for (blobindex = 0; blobindex < blobcount; blobindex++) {
00823     if (partids[blobindex] != prevpart) {
00824       //                      tprintf("Partition change at (%d,%d) from %d to %d after run of %d\n",
00825       //                              blobcoords[blobindex].left(),blobcoords[blobindex].bottom(),
00826       //                              prevpart,partids[blobindex],runlength);
00827       if (prevpart != biggestpart && runlength > MAXBADRUN) {
00828         stats.clear ();
00829         for (test_blob = startx; test_blob < blobindex; test_blob++) {
00830           coord = FCOORD ((blobcoords[test_blob].left ()
00831             + blobcoords[test_blob].right ()) / 2.0,
00832             blobcoords[test_blob].bottom ());
00833           stats.add (coord.x (), coord.y ());
00834         }
00835         stats.fit (1);
00836         m = stats.get_b ();
00837         c = stats.get_c ();
00838         if (textord_oldbl_debug)
00839           tprintf ("Fitted line y=%g x + %g\n", m, c);
00840         found_one = FALSE;
00841         close_one = FALSE;
00842         for (test_blob = 1; !found_one
00843           && (startx - test_blob >= 0
00844         || blobindex + test_blob <= blobcount); test_blob++) {
00845           if (startx - test_blob >= 0
00846           && partids[startx - test_blob] == biggestpart) {
00847             found_one = TRUE;
00848             coord = FCOORD ((blobcoords[startx - test_blob].left ()
00849               + blobcoords[startx -
00850               test_blob].right ()) /
00851               2.0,
00852               blobcoords[startx -
00853               test_blob].bottom ());
00854             diff = m * coord.x () + c - coord.y ();
00855             if (textord_oldbl_debug)
00856               tprintf
00857                 ("Diff of common blob to suspect part=%g at (%g,%g)\n",
00858                 diff, coord.x (), coord.y ());
00859             if (diff < jumplimit && -diff < jumplimit)
00860               close_one = TRUE;
00861           }
00862           if (blobindex + test_blob <= blobcount
00863           && partids[blobindex + test_blob - 1] == biggestpart) {
00864             found_one = TRUE;
00865             coord =
00866               FCOORD ((blobcoords[blobindex + test_blob - 1].
00867               left () + blobcoords[blobindex + test_blob -
00868               1].right ()) / 2.0,
00869               blobcoords[blobindex + test_blob -
00870               1].bottom ());
00871             diff = m * coord.x () + c - coord.y ();
00872             if (textord_oldbl_debug)
00873               tprintf
00874                 ("Diff of common blob to suspect part=%g at (%g,%g)\n",
00875                 diff, coord.x (), coord.y ());
00876             if (diff < jumplimit && -diff < jumplimit)
00877               close_one = TRUE;
00878           }
00879         }
00880         if (close_one) {
00881           if (textord_oldbl_debug)
00882             tprintf
00883               ("Merged %d blobs back into part %d from %d starting at (%d,%d)\n",
00884               runlength, biggestpart, prevpart,
00885               blobcoords[startx].left (),
00886               blobcoords[startx].bottom ());
00887                                  //switch sides
00888           partsizes[prevpart] -= runlength;
00889           for (test_blob = startx; test_blob < blobindex; test_blob++)
00890             partids[test_blob] = biggestpart;
00891         }
00892       }
00893       prevpart = partids[blobindex];
00894       runlength = 1;
00895       startx = blobindex;
00896     }
00897     else
00898       runlength++;
00899   }
00900 }
00901 
00902 
00903 /**********************************************************************
00904  * get_ydiffs
00905  *
00906  * Get the differences between the blobs and the spline,
00907  * putting them in ydiffs.  The return value is the index
00908  * of the blob in the middle of the "best behaved" region
00909  **********************************************************************/
00910 
00911 int
00912 get_ydiffs (                     //evaluate differences
00913 TBOX blobcoords[],                //bounding boxes
00914 int blobcount,                   /*no of blobs */
00915 QSPLINE * spline,                /*approximating spline */
00916 float ydiffs[]                   /*output */
00917 ) {
00918   register int blobindex;        /*current blob */
00919   int xcentre;                   /*xcoord */
00920   int lastx;                     /*last xcentre */
00921   float diffsum;                 /*sum of diffs */
00922   float diff;                    /*current difference */
00923   float drift;                   /*sum of spline steps */
00924   float bestsum;                 /*smallest diffsum */
00925   int bestindex;                 /*index of bestsum */
00926 
00927   diffsum = 0.0f;
00928   bestindex = 0;
00929   bestsum = (float) MAX_INT32;
00930   drift = 0.0f;
00931   lastx = blobcoords[0].left ();
00932                                  /*do each blob in row */
00933   for (blobindex = 0; blobindex < blobcount; blobindex++) {
00934                                  /*centre of blob */
00935     xcentre = (blobcoords[blobindex].left () + blobcoords[blobindex].right ()) >> 1;
00936                                  //step functions in spline
00937     drift += spline->step (lastx, xcentre);
00938     lastx = xcentre;
00939     diff = blobcoords[blobindex].bottom ();
00940     diff -= spline->y (xcentre);
00941     diff += drift;
00942     ydiffs[blobindex] = diff;    /*store difference */
00943     if (blobindex > 2)
00944                                  /*remove old one */
00945       diffsum -= ABS (ydiffs[blobindex - 3]);
00946     diffsum += ABS (diff);       /*add new one */
00947     if (blobindex >= 2 && diffsum < bestsum) {
00948       bestsum = diffsum;         /*find min sum */
00949       bestindex = blobindex - 1; /*middle of set */
00950     }
00951   }
00952   return bestindex;
00953 }
00954 
00955 
00956 /**********************************************************************
00957  * choose_partition
00958  *
00959  * Choose a partition for the point and return the index.
00960  **********************************************************************/
00961 
00962 int
00963 choose_partition (               //select partition
00964 register float diff,             /*diff from spline */
00965 float partdiffs[],               /*diff on all parts */
00966 int lastpart,                    /*last assigned partition */
00967 float jumplimit,                 /*new part threshold */
00968 float* drift,
00969 float* lastdelta,
00970 int *partcount                   /*no of partitions */
00971 ) {
00972   register int partition;        /*partition no */
00973   int bestpart;                  /*best new partition */
00974   float bestdelta;               /*best gap from a part */
00975   float delta;                   /*diff from part */
00976 
00977   if (lastpart < 0) {
00978     partdiffs[0] = diff;
00979     lastpart = 0;                /*first point */
00980     *drift = 0.0f;
00981     *lastdelta = 0.0f;
00982   }
00983                                  /*adjusted diff from part */
00984   delta = diff - partdiffs[lastpart] - *drift;
00985   if (textord_oldbl_debug) {
00986     tprintf ("Diff=%.2f, Delta=%.3f, Drift=%.3f, ", diff, delta, *drift);
00987   }
00988   if (ABS (delta) > jumplimit / 2) {
00989                                  /*delta on part 0 */
00990     bestdelta = diff - partdiffs[0] - *drift;
00991     bestpart = 0;                /*0 best so far */
00992     for (partition = 1; partition < *partcount; partition++) {
00993       delta = diff - partdiffs[partition] - *drift;
00994       if (ABS (delta) < ABS (bestdelta)) {
00995         bestdelta = delta;
00996         bestpart = partition;    /*part with nearest jump */
00997       }
00998     }
00999     delta = bestdelta;
01000                                  /*too far away */
01001     if (ABS (bestdelta) > jumplimit
01002     && *partcount < MAXPARTS) {  /*and spare part left */
01003       bestpart = (*partcount)++; /*best was new one */
01004                                  /*start new one */
01005       partdiffs[bestpart] = diff - *drift;
01006       delta = 0.0f;
01007     }
01008   }
01009   else {
01010     bestpart = lastpart;         /*best was last one */
01011   }
01012 
01013   if (bestpart == lastpart
01014     && (ABS (delta - *lastdelta) < jumplimit / 2
01015     || ABS (delta) < jumplimit / 2))
01016                                  /*smooth the drift */
01017     *drift = (3 * *drift + delta) / 3;
01018   *lastdelta = delta;
01019 
01020   if (textord_oldbl_debug) {
01021     tprintf ("P=%d\n", bestpart);
01022   }
01023 
01024   return bestpart;
01025 }
01026 
01027 
01029 //partitions and gives all the rest partid 0*/
01030 //
01031 //merge_partitions(partids,partcount,blobcount,bestpart)
01032 //register char              *partids;                     /*partition numbers*/
01033 //int                        partcount;                    /*no of partitions*/
01034 //int                        blobcount;                    /*no of blobs*/
01035 //int                        bestpart;                     /*best partition*/
01036 //{
01037 //   register int            blobindex;                    /*no along text line*/
01038 //   int                     runlength;                    /*run of same partition*/
01039 //   int                     bestrun;                      /*biggest runlength*/
01040 //
01041 //   bestrun=0;                                            /*no runs yet*/
01042 //   runlength=1;
01043 //   for (blobindex=1;blobindex<blobcount;blobindex++)
01044 //   {  if (partids[blobindex]!=partids[blobindex-1])
01045 //      {  if (runlength>bestrun)
01046 //            bestrun=runlength;                           /*find biggest run*/
01047 //         runlength=1;                                    /*new run*/
01048 //      }
01049 //      else
01050 //      {  runlength++;
01051 //      }
01052 //   }
01053 //   if (runlength>bestrun)
01054 //      bestrun=runlength;
01055 //
01056 //   for (blobindex=0;blobindex<blobcount;blobindex++)
01057 //   {  if (blobindex<1
01058 //      || partids[blobindex]!=partids[blobindex-1])
01059 //      {  if ((blobindex+1>=blobcount
01060 //         || partids[blobindex]!=partids[blobindex+1])
01061 //                                                         /*loner*/
01062 //         && (bestrun>2 || partids[blobindex]!=bestpart))
01063 //         {  partids[blobindex]=partcount;                /*discard loner*/
01064 //         }
01065 //         else if (blobindex+1<blobcount
01066 //         && partids[blobindex]==partids[blobindex+1]
01067 //                                                         /*pair*/
01068 //         && (blobindex+2>=blobcount
01069 //         || partids[blobindex]!=partids[blobindex+2])
01070 //         && (bestrun>3 || partids[blobindex]!=bestpart))
01071 //         {  partids[blobindex]=partcount;                /*discard both*/
01072 //            partids[blobindex+1]=partcount;
01073 //         }
01074 //      }
01075 //   }
01076 //   for (blobindex=0;blobindex<blobcount;blobindex++)
01077 //   {  if (partids[blobindex]<partcount)
01078 //         partids[blobindex]=0;                           /*all others together*/
01079 //   }
01080 //}
01081 
01082 /**********************************************************************
01083  * partition_coords
01084  *
01085  * Get the x,y coordinates of all points in the bestpart and put them
01086  * in xcoords,ycoords. Return the number of points found.
01087  **********************************************************************/
01088 
01089 int
01090 partition_coords (               //find relevant coords
01091 TBOX blobcoords[],                //bounding boxes
01092 int blobcount,                   /*no of blobs in row */
01093 char partids[],                  /*partition no of each blob */
01094 int bestpart,                    /*best new partition */
01095 int xcoords[],                   /*points to work on */
01096 int ycoords[]                    /*points to work on */
01097 ) {
01098   register int blobindex;        /*no along text line */
01099   int pointcount;                /*no of points */
01100 
01101   pointcount = 0;
01102   for (blobindex = 0; blobindex < blobcount; blobindex++) {
01103     if (partids[blobindex] == bestpart) {
01104                                  /*centre of blob */
01105       xcoords[pointcount] = (blobcoords[blobindex].left () + blobcoords[blobindex].right ()) >> 1;
01106       ycoords[pointcount++] = blobcoords[blobindex].bottom ();
01107     }
01108   }
01109   return pointcount;             /*no of points found */
01110 }
01111 
01112 
01113 /**********************************************************************
01114  * segment_spline
01115  *
01116  * Segment the row at midpoints between maxima and minima of the x,y pairs.
01117  * The xstarts of the segments are returned and the number found.
01118  **********************************************************************/
01119 
01120 int
01121 segment_spline (                 //make xstarts
01122 TBOX blobcoords[],                //boundign boxes
01123 int blobcount,                   /*no of blobs in row */
01124 int xcoords[],                   /*points to work on */
01125 int ycoords[],                   /*points to work on */
01126 int degree, int pointcount,      /*no of points */
01127 int xstarts[]                    //result
01128 ) {
01129   register int ptindex;          /*no along text line */
01130   register int segment;          /*partition no */
01131   int lastmin, lastmax;          /*possible turn points */
01132   int turnpoints[SPLINESIZE];    /*good turning points */
01133   int turncount;                 /*no of turning points */
01134   int max_x;                     //max specified coord
01135 
01136   xstarts[0] = xcoords[0] - 1;   //leftmost defined pt
01137   max_x = xcoords[pointcount - 1] + 1;
01138   if (degree < 2)
01139     pointcount = 0;
01140   turncount = 0;                 /*no turning points yet */
01141   if (pointcount > 3) {
01142     ptindex = 1;
01143     lastmax = lastmin = 0;       /*start with first one */
01144     while (ptindex < pointcount - 1 && turncount < SPLINESIZE - 1) {
01145                                  /*minimum */
01146       if (ycoords[ptindex - 1] > ycoords[ptindex] && ycoords[ptindex] <= ycoords[ptindex + 1]) {
01147         if (ycoords[ptindex] < ycoords[lastmax] - TURNLIMIT) {
01148           if (turncount == 0 || turnpoints[turncount - 1] != lastmax)
01149                                  /*new max point */
01150             turnpoints[turncount++] = lastmax;
01151           lastmin = ptindex;     /*latest minimum */
01152         }
01153         else if (ycoords[ptindex] < ycoords[lastmin]) {
01154           lastmin = ptindex;     /*lower minimum */
01155         }
01156       }
01157 
01158                                  /*maximum */
01159       if (ycoords[ptindex - 1] < ycoords[ptindex] && ycoords[ptindex] >= ycoords[ptindex + 1]) {
01160         if (ycoords[ptindex] > ycoords[lastmin] + TURNLIMIT) {
01161           if (turncount == 0 || turnpoints[turncount - 1] != lastmin)
01162                                  /*new min point */
01163             turnpoints[turncount++] = lastmin;
01164           lastmax = ptindex;     /*latest maximum */
01165         }
01166         else if (ycoords[ptindex] > ycoords[lastmax]) {
01167           lastmax = ptindex;     /*higher maximum */
01168         }
01169       }
01170       ptindex++;
01171     }
01172                                  /*possible global min */
01173     if (ycoords[ptindex] < ycoords[lastmax] - TURNLIMIT
01174     && (turncount == 0 || turnpoints[turncount - 1] != lastmax)) {
01175       if (turncount < SPLINESIZE - 1)
01176                                  /*2 more turns */
01177         turnpoints[turncount++] = lastmax;
01178       if (turncount < SPLINESIZE - 1)
01179         turnpoints[turncount++] = ptindex;
01180     }
01181     else if (ycoords[ptindex] > ycoords[lastmin] + TURNLIMIT
01182       /*possible global max */
01183     && (turncount == 0 || turnpoints[turncount - 1] != lastmin)) {
01184       if (turncount < SPLINESIZE - 1)
01185                                  /*2 more turns */
01186         turnpoints[turncount++] = lastmin;
01187       if (turncount < SPLINESIZE - 1)
01188         turnpoints[turncount++] = ptindex;
01189     }
01190     else if (turncount > 0 && turnpoints[turncount - 1] == lastmin
01191     && turncount < SPLINESIZE - 1) {
01192       if (ycoords[ptindex] > ycoords[lastmax])
01193         turnpoints[turncount++] = ptindex;
01194       else
01195         turnpoints[turncount++] = lastmax;
01196     }
01197     else if (turncount > 0 && turnpoints[turncount - 1] == lastmax
01198     && turncount < SPLINESIZE - 1) {
01199       if (ycoords[ptindex] < ycoords[lastmin])
01200         turnpoints[turncount++] = ptindex;
01201       else
01202         turnpoints[turncount++] = lastmin;
01203     }
01204   }
01205 
01206   if (textord_oldbl_debug && turncount > 0)
01207     tprintf ("First turn is %d at (%d,%d)\n",
01208       turnpoints[0], xcoords[turnpoints[0]], ycoords[turnpoints[0]]);
01209   for (segment = 1; segment < turncount; segment++) {
01210                                  /*centre y coord */
01211     lastmax = (ycoords[turnpoints[segment - 1]] + ycoords[turnpoints[segment]]) / 2;
01212 
01213     /* fix alg so that it works with both rising and falling sections */
01214     if (ycoords[turnpoints[segment - 1]] < ycoords[turnpoints[segment]])
01215                                  /*find rising y centre */
01216       for (ptindex = turnpoints[segment - 1] + 1; ptindex < turnpoints[segment] && ycoords[ptindex + 1] <= lastmax; ptindex++);
01217     else
01218                                  /*find falling y centre */
01219       for (ptindex = turnpoints[segment - 1] + 1; ptindex < turnpoints[segment] && ycoords[ptindex + 1] >= lastmax; ptindex++);
01220 
01221                                  /*centre x */
01222     xstarts[segment] = (xcoords[ptindex - 1] + xcoords[ptindex]
01223       + xcoords[turnpoints[segment - 1]]
01224       + xcoords[turnpoints[segment]] + 2) / 4;
01225     /*halfway between turns */
01226     if (textord_oldbl_debug)
01227       tprintf ("Turn %d is %d at (%d,%d), mid pt is %d@%d, final @%d\n",
01228         segment, turnpoints[segment],
01229         xcoords[turnpoints[segment]], ycoords[turnpoints[segment]],
01230         ptindex - 1, xcoords[ptindex - 1], xstarts[segment]);
01231   }
01232 
01233   xstarts[segment] = max_x;
01234   return segment;                /*no of splines */
01235 }
01236 
01237 
01238 /**********************************************************************
01239  * split_stepped_spline
01240  *
01241  * Re-segment the spline in cases where there is a big step function.
01242  * Return TRUE if any were done.
01243  **********************************************************************/
01244 
01245 BOOL8
01246 split_stepped_spline (           //make xstarts
01247 QSPLINE * baseline,              //current shot
01248 float jumplimit,                 //max step fuction
01249 int xcoords[],                   /*points to work on */
01250 int xstarts[],                   //result
01251 int &segments                    //no of segments
01252 ) {
01253   BOOL8 doneany;                 //return value
01254   register int segment;          /*partition no */
01255   int startindex, centreindex, endindex;
01256   float leftcoord, rightcoord;
01257   int leftindex, rightindex;
01258   float step;                    //spline step
01259 
01260   doneany = FALSE;
01261   startindex = 0;
01262   for (segment = 1; segment < segments - 1; segment++) {
01263     step = baseline->step ((xstarts[segment - 1] + xstarts[segment]) / 2.0,
01264       (xstarts[segment] + xstarts[segment + 1]) / 2.0);
01265     if (step < 0)
01266       step = -step;
01267     if (step > jumplimit) {
01268       while (xcoords[startindex] < xstarts[segment - 1])
01269         startindex++;
01270       centreindex = startindex;
01271       while (xcoords[centreindex] < xstarts[segment])
01272         centreindex++;
01273       endindex = centreindex;
01274       while (xcoords[endindex] < xstarts[segment + 1])
01275         endindex++;
01276       if (segments >= SPLINESIZE) {
01277         if (textord_debug_baselines)
01278           tprintf ("Too many segments to resegment spline!!\n");
01279       }
01280       else if (endindex - startindex >= textord_spline_medianwin * 3) {
01281         while (centreindex - startindex <
01282           textord_spline_medianwin * 3 / 2)
01283           centreindex++;
01284         while (endindex - centreindex <
01285           textord_spline_medianwin * 3 / 2)
01286           centreindex--;
01287         leftindex = (startindex + startindex + centreindex) / 3;
01288         rightindex = (centreindex + endindex + endindex) / 3;
01289         leftcoord =
01290           (xcoords[startindex] * 2 + xcoords[centreindex]) / 3.0;
01291         rightcoord =
01292           (xcoords[centreindex] + xcoords[endindex] * 2) / 3.0;
01293         while (xcoords[leftindex] > leftcoord
01294           && leftindex - startindex > textord_spline_medianwin)
01295           leftindex--;
01296         while (xcoords[leftindex] < leftcoord
01297           && centreindex - leftindex >
01298           textord_spline_medianwin / 2)
01299           leftindex++;
01300         if (xcoords[leftindex] - leftcoord >
01301           leftcoord - xcoords[leftindex - 1])
01302           leftindex--;
01303         while (xcoords[rightindex] > rightcoord
01304           && rightindex - centreindex >
01305           textord_spline_medianwin / 2)
01306           rightindex--;
01307         while (xcoords[rightindex] < rightcoord
01308           && endindex - rightindex > textord_spline_medianwin)
01309           rightindex++;
01310         if (xcoords[rightindex] - rightcoord >
01311           rightcoord - xcoords[rightindex - 1])
01312           rightindex--;
01313         if (textord_debug_baselines)
01314           tprintf ("Splitting spline at %d with step %g at (%d,%d)\n",
01315             xstarts[segment],
01316             baseline->
01317             step ((xstarts[segment - 1] +
01318             xstarts[segment]) / 2.0,
01319             (xstarts[segment] +
01320             xstarts[segment + 1]) / 2.0),
01321             (xcoords[leftindex - 1] + xcoords[leftindex]) / 2,
01322             (xcoords[rightindex - 1] + xcoords[rightindex]) / 2);
01323         insert_spline_point (xstarts, segment,
01324           (xcoords[leftindex - 1] +
01325           xcoords[leftindex]) / 2,
01326           (xcoords[rightindex - 1] +
01327           xcoords[rightindex]) / 2, segments);
01328         doneany = TRUE;
01329       }
01330       else if (textord_debug_baselines) {
01331         tprintf
01332           ("Resegmenting spline failed - insufficient pts (%d,%d,%d,%d)\n",
01333           startindex, centreindex, endindex,
01334           (inT32) textord_spline_medianwin);
01335       }
01336     }
01337     //              else tprintf("Spline step at %d is %g\n",
01338     //                      xstarts[segment],
01339     //                      baseline->step((xstarts[segment-1]+xstarts[segment])/2.0,
01340     //                      (xstarts[segment]+xstarts[segment+1])/2.0));
01341   }
01342   return doneany;
01343 }
01344 
01345 
01346 /**********************************************************************
01347  * insert_spline_point
01348  *
01349  * Insert a new spline point and shuffle up the others.
01350  **********************************************************************/
01351 
01352 void
01353 insert_spline_point (            //get descenders
01354 int xstarts[],                   //starts to shuffle
01355 int segment,                     //insertion pt
01356 int coord1,                      //coords to add
01357 int coord2, int &segments        //total segments
01358 ) {
01359   int index;                     //for shuffling
01360 
01361   for (index = segments; index > segment; index--)
01362     xstarts[index + 1] = xstarts[index];
01363   segments++;
01364   xstarts[segment] = coord1;
01365   xstarts[segment + 1] = coord2;
01366 }
01367 
01368 
01369 /**********************************************************************
01370  * find_lesser_parts
01371  *
01372  * Average the step from the spline for the other partitions
01373  * and find the commonest partition which has a descender.
01374  **********************************************************************/
01375 
01376 void
01377 find_lesser_parts (              //get descenders
01378 TO_ROW * row,                    //row to process
01379 TBOX blobcoords[],                //bounding boxes
01380 int blobcount,                   /*no of blobs */
01381 char partids[],                  /*partition of each blob */
01382 int partsizes[],                 /*size of each part */
01383 int partcount,                   /*no of partitions */
01384 int bestpart                     /*biggest partition */
01385 ) {
01386   register int blobindex;        /*index of blob */
01387   register int partition;        /*current partition */
01388   int xcentre;                   /*centre of blob */
01389   int poscount;                  /*count of best up step */
01390   int negcount;                  /*count of best down step */
01391   float partsteps[MAXPARTS];     /*average step to part */
01392   float bestpos;                 /*best up step */
01393   float bestneg;                 /*best down step */
01394   int runlength;                 /*length of bad run */
01395   int biggestrun;                /*biggest bad run */
01396 
01397   biggestrun = 0;
01398   for (partition = 0; partition < partcount; partition++)
01399     partsteps[partition] = 0.0;  /*zero accumulators */
01400   for (runlength = 0, blobindex = 0; blobindex < blobcount; blobindex++) {
01401     xcentre = (blobcoords[blobindex].left ()
01402       + blobcoords[blobindex].right ()) >> 1;
01403                                  /*in other parts */
01404     if (partids[blobindex] != bestpart) {
01405       runlength++;               /*run of non bests */
01406       if (runlength > biggestrun)
01407         biggestrun = runlength;
01408       partsteps[partids[blobindex]] += blobcoords[blobindex].bottom ()
01409         - row->baseline.y (xcentre);
01410     }
01411     else
01412       runlength = 0;
01413   }
01414   if (biggestrun > MAXBADRUN)
01415     row->xheight = -1.0f;        /*failed */
01416   else
01417     row->xheight = 1.0f;         /*success */
01418   poscount = negcount = 0;
01419   bestpos = bestneg = 0.0;       /*no step yet */
01420   for (partition = 0; partition < partcount; partition++) {
01421     if (partition != bestpart) {
01422 
01423         //by jetsoft divide by zero possible
01424                 if (partsizes[partition]==0)
01425                 partsteps[partition]=0;
01426        else
01427                 partsteps[partition] /= partsizes[partition];
01428         //
01429 
01430 
01431       if (partsteps[partition] >= MINASCRISE
01432       && partsizes[partition] > poscount) {
01433                                  /*ascender rise */
01434         bestpos = partsteps[partition];
01435                                  /*2nd most popular */
01436         poscount = partsizes[partition];
01437       }
01438       if (partsteps[partition] <= -MINASCRISE
01439       && partsizes[partition] > negcount) {
01440                                  /*ascender rise */
01441         bestneg = partsteps[partition];
01442                                  /*2nd most popular */
01443         negcount = partsizes[partition];
01444       }
01445     }
01446   }
01447                                  /*average x-height */
01448   partsteps[bestpart] /= blobcount;
01449   row->descdrop = bestneg;
01450 }
01451 
01452 
01453 /**********************************************************************
01454  * old_first_xheight
01455  *
01456  * Makes an x-height spline by copying the baseline and shifting it.
01457  * It estimates the x-height across the line to use as the shift.
01458  * It also finds the ascender height if it can.
01459  **********************************************************************/
01460 
01461 void
01462 old_first_xheight (              //the wiseowl way
01463 TO_ROW * row,                    /*current row */
01464 TBOX blobcoords[],                /*blob bounding boxes */
01465 int initialheight,               //initial guess
01466 int blobcount,                   /*blobs in blobcoords */
01467 QSPLINE * baseline,              /*established */
01468 float jumplimit                  /*min ascender height */
01469 ) {
01470   register int blobindex;        /*current blob */
01471                                  /*height statistics */
01472   STATS heightstat (0, MAXHEIGHT);
01473   int height;                    /*height of blob */
01474   int xcentre;                   /*centre of blob */
01475   int lineheight;                /*approx xheight */
01476   float ascenders;               /*ascender sum */
01477   int asccount;                  /*no of ascenders */
01478   float xsum;                    /*xheight sum */
01479   int xcount;                    /*xheight count */
01480   register float diff;           /*height difference */
01481 
01482   if (blobcount > 1) {
01483     for (blobindex = 0; blobindex < blobcount; blobindex++) {
01484       xcentre = (blobcoords[blobindex].left ()
01485         + blobcoords[blobindex].right ()) / 2;
01486                                  /*height of blob */
01487       height = (int) (blobcoords[blobindex].top () - baseline->y (xcentre) + 0.5);
01488       if (height > initialheight * oldbl_xhfract
01489         && height > textord_min_xheight)
01490         heightstat.add (height, 1);
01491     }
01492     if (heightstat.get_total () > 3) {
01493       lineheight = (int) heightstat.ile (0.25);
01494       if (lineheight <= 0)
01495         lineheight = (int) heightstat.ile (0.5);
01496     }
01497     else
01498       lineheight = initialheight;
01499   }
01500   else {
01501     lineheight = (int) (blobcoords[0].top ()
01502       - baseline->y ((blobcoords[0].left ()
01503       + blobcoords[0].right ()) / 2) +
01504       0.5);
01505   }
01506 
01507   xsum = 0.0f;
01508   xcount = 0;
01509   for (ascenders = 0.0f, asccount = 0, blobindex = 0; blobindex < blobcount;
01510   blobindex++) {
01511     xcentre = (blobcoords[blobindex].left ()
01512       + blobcoords[blobindex].right ()) / 2;
01513     diff = blobcoords[blobindex].top () - baseline->y (xcentre);
01514                                  /*is it ascender */
01515     if (diff > lineheight + jumplimit) {
01516       ascenders += diff;
01517       asccount++;                /*count ascenders */
01518     }
01519     else if (diff > lineheight - jumplimit) {
01520       xsum += diff;              /*mean xheight */
01521       xcount++;
01522     }
01523   }
01524   if (xcount > 0)
01525     xsum /= xcount;              /*average xheight */
01526   else
01527     xsum = (float) lineheight;   /*guess it */
01528   row->xheight *= xsum;
01529   if (asccount > 0)
01530     row->ascrise = ascenders / asccount - xsum;
01531   else
01532     row->ascrise = 0.0f;         /*had none */
01533   if (row->xheight == 0)
01534     row->xheight = -1.0f;
01535 }
01536 
01537 
01538 /**********************************************************************
01539  * make_first_xheight
01540  *
01541  * Makes an x-height spline by copying the baseline and shifting it.
01542  * It estimates the x-height across the line to use as the shift.
01543  * It also finds the ascender height if it can.
01544  **********************************************************************/
01545 
01546 void
01547 make_first_xheight (             //find xheight
01548 TO_ROW * row,                    /*current row */
01549 TBOX blobcoords[],                /*blob bounding boxes */
01550 int lineheight,                  //initial guess
01551 int init_lineheight,             //block level guess
01552 int blobcount,                   /*blobs in blobcoords */
01553 QSPLINE * baseline,              /*established */
01554 float jumplimit                  /*min ascender height */
01555 ) {
01556   STATS heightstat (0, HEIGHTBUCKETS);
01557   int lefts[HEIGHTBUCKETS];
01558   int rights[HEIGHTBUCKETS];
01559   int modelist[MODENUM];
01560   int blobindex;
01561   int mode_count;                //blobs to count in thr
01562   int sign_bit;
01563   int mode_threshold;
01564   const int kBaselineTouch = 2;  // This really should change with resolution.
01565   const int kGoodStrength = 8;  // Strength of baseline-touching heights.
01566   const float kMinHeight = 0.25;  // Min fraction of lineheight to use.
01567 
01568   sign_bit = row->xheight > 0 ? 1 : -1;
01569 
01570   memset(lefts, 0, HEIGHTBUCKETS * sizeof(lefts[0]));
01571   memset(rights, 0, HEIGHTBUCKETS * sizeof(rights[0]));
01572   mode_count = 0;
01573   for (blobindex = 0; blobindex < blobcount; blobindex++) {
01574     int xcenter = (blobcoords[blobindex].left () +
01575         blobcoords[blobindex].right ()) / 2;
01576     float base = baseline->y(xcenter);
01577     float bottomdiff = fabs(base - blobcoords[blobindex].bottom());
01578     int strength = textord_ocropus_mode &&
01579                    bottomdiff <= kBaselineTouch ? kGoodStrength : 1;
01580     int height = static_cast<int>(blobcoords[blobindex].top () - base + 0.5);
01581     if (blobcoords[blobindex].height () > init_lineheight * kMinHeight) {
01582       if (height > lineheight * oldbl_xhfract
01583         && height > textord_min_xheight) {
01584         heightstat.add (height, strength);
01585         if (height < HEIGHTBUCKETS) {
01586           if (xcenter > rights[height])
01587             rights[height] = xcenter;
01588           if (xcenter > 0 && (lefts[height] == 0 || xcenter < lefts[height]))
01589             lefts[height] = xcenter;
01590         }
01591       }
01592       mode_count += strength;
01593     }
01594   }
01595 
01596   mode_threshold = (int) (blobcount * 0.1);
01597   if (oldbl_dot_error_size > 1 || oldbl_xhfix)
01598     mode_threshold = (int) (mode_count * 0.1);
01599 
01600   if (textord_oldbl_debug) {
01601     tprintf ("blobcount=%d, mode_count=%d, mode_t=%d\n",
01602       blobcount, mode_count, mode_threshold);
01603   }
01604   find_top_modes(&heightstat, HEIGHTBUCKETS, modelist, MODENUM);
01605   if (textord_oldbl_debug) {
01606     for (blobindex = 0; blobindex < MODENUM; blobindex++)
01607       tprintf ("mode[%d]=%d ", blobindex, modelist[blobindex]);
01608     tprintf ("\n");
01609   }
01610   pick_x_height(row, modelist, lefts, rights, &heightstat, mode_threshold);
01611 
01612   if (textord_oldbl_debug)
01613     tprintf ("Output xheight=%g\n", row->xheight);
01614   if (row->xheight < 0 && textord_oldbl_debug)
01615     tprintf ("warning: Row Line height < 0; %4.2f\n", row->xheight);
01616 
01617   if (sign_bit < 0)
01618     row->xheight = -row->xheight;
01619 }
01620 
01621 /**********************************************************************
01622  * find_top_modes
01623  *
01624  * Fill the input array with the indices of the top ten modes of the
01625  * input distribution.
01626  **********************************************************************/
01627 
01628 const int kMinModeFactorOcropus = 32;
01629 const int kMinModeFactor = 12;
01630 
01631 void
01632 find_top_modes (                 //get modes
01633 STATS * stats,                   //stats to hack
01634 int statnum,                     //no of piles
01635 int modelist[], int modenum      //no of modes to get
01636 ) {
01637   int mode_count;
01638   int last_i = 0;
01639   int last_max = MAX_INT32;
01640   int i;
01641   int mode;
01642   int total_max = 0;
01643   int mode_factor = textord_ocropus_mode ?
01644                     kMinModeFactorOcropus : kMinModeFactor;
01645 
01646   for (mode_count = 0; mode_count < modenum; mode_count++) {
01647     mode = 0;
01648     for (i = 0; i < statnum; i++) {
01649       if (stats->pile_count (i) > stats->pile_count (mode)) {
01650         if ((stats->pile_count (i) < last_max) ||
01651         ((stats->pile_count (i) == last_max) && (i > last_i))) {
01652           mode = i;
01653         }
01654       }
01655     }
01656     last_i = mode;
01657     last_max = stats->pile_count (last_i);
01658     total_max += last_max;
01659     if (last_max <= total_max / mode_factor)
01660       mode = 0;
01661     modelist[mode_count] = mode;
01662   }
01663 }
01664 
01665 
01666 /**********************************************************************
01667  * pick_x_height
01668  *
01669  * Choose based on the height modes the best x height value.
01670  **********************************************************************/
01671 
01672 void pick_x_height(TO_ROW * row,                    //row to do
01673                    int modelist[],
01674                    int lefts[], int rights[],
01675                    STATS * heightstat,
01676                    int mode_threshold) {
01677   int x;
01678   int y;
01679   int z;
01680   float ratio;
01681   int found_one_bigger = FALSE;
01682   int best_x_height = 0;
01683   int best_asc = 0;
01684   int num_in_best;
01685 
01686   for (x = 0; x < MODENUM; x++) {
01687     for (y = 0; y < MODENUM; y++) {
01688       /* Check for two modes */
01689       if (modelist[x] && modelist[y] &&
01690           heightstat->pile_count (modelist[x]) > mode_threshold &&
01691           (!textord_ocropus_mode ||
01692            MIN(rights[modelist[x]], rights[modelist[y]]) >
01693            MAX(lefts[modelist[x]], lefts[modelist[y]]))) {
01694         ratio = (float) modelist[y] / (float) modelist[x];
01695         if (1.2 < ratio && ratio < 1.8) {
01696           /* Two modes found */
01697           best_x_height = modelist[x];
01698           num_in_best = heightstat->pile_count (modelist[x]);
01699 
01700           /* Try to get one higher */
01701           do {
01702             found_one_bigger = FALSE;
01703             for (z = 0; z < MODENUM; z++) {
01704               if (modelist[z] == best_x_height + 1 &&
01705                   (!textord_ocropus_mode ||
01706                     MIN(rights[modelist[x]], rights[modelist[y]]) >
01707                     MAX(lefts[modelist[x]], lefts[modelist[y]]))) {
01708                 ratio = (float) modelist[y] / (float) modelist[z];
01709                 if ((1.2 < ratio && ratio < 1.8) &&
01710                                /* Should be half of best */
01711                     heightstat->pile_count (modelist[z]) >
01712                     num_in_best * 0.5) {
01713                   best_x_height++;
01714                   found_one_bigger = TRUE;
01715                   break;
01716                 }
01717               }
01718             }
01719           }
01720           while (found_one_bigger);
01721 
01722           /* try to get a higher ascender */
01723 
01724           best_asc = modelist[y];
01725           num_in_best = heightstat->pile_count (modelist[y]);
01726 
01727           /* Try to get one higher */
01728           do {
01729             found_one_bigger = FALSE;
01730             for (z = 0; z < MODENUM; z++) {
01731               if (modelist[z] > best_asc &&
01732                   (!textord_ocropus_mode ||
01733                     MIN(rights[modelist[x]], rights[modelist[y]]) >
01734                     MAX(lefts[modelist[x]], lefts[modelist[y]]))) {
01735                 ratio = (float) modelist[z] / (float) best_x_height;
01736                 if ((1.2 < ratio && ratio < 1.8) &&
01737                                /* Should be half of best */
01738                     heightstat->pile_count (modelist[z]) >
01739                     num_in_best * 0.5) {
01740                   best_asc = modelist[z];
01741                   found_one_bigger = TRUE;
01742                   break;
01743                 }
01744               }
01745             }
01746           }
01747           while (found_one_bigger);
01748 
01749           row->xheight = (float) best_x_height;
01750           row->ascrise = (float) best_asc - best_x_height;
01751           return;
01752         }
01753       }
01754     }
01755   }
01756 
01757   best_x_height = modelist[0];   /* Single Mode found */
01758   num_in_best = heightstat->pile_count (best_x_height);
01759   do {
01760                                  /* Try to get one higher */
01761     found_one_bigger = FALSE;
01762     for (z = 1; z < MODENUM; z++) {
01763       /* Should be half of best */
01764       if ((modelist[z] == best_x_height + 1) &&
01765       (heightstat->pile_count (modelist[z]) > num_in_best * 0.5)) {
01766         best_x_height++;
01767         found_one_bigger = TRUE;
01768         break;
01769       }
01770     }
01771   }
01772   while (found_one_bigger);
01773 
01774   row->ascrise = 0.0f;
01775   row->xheight = (float) best_x_height;
01776   if (row->xheight == 0)
01777     row->xheight = -1.0f;
01778 }