Tesseract  3.02
tesseract-ocr/ccstruct/ocrblock.cpp
Go to the documentation of this file.
00001 /**********************************************************************
00002  * File:        ocrblock.cpp  (Formerly block.c)
00003  * Description: BLOCK member functions and iterator functions.
00004  * Author:              Ray Smith
00005  * Created:             Fri Mar 15 09:41:28 GMT 1991
00006  *
00007  * (C) Copyright 1991, 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 <stdlib.h>
00022 #include "blckerr.h"
00023 #include "ocrblock.h"
00024 #include "stepblob.h"
00025 #include "tprintf.h"
00026 
00027 #define BLOCK_LABEL_HEIGHT  150  //char height of block id
00028 
00029 ELISTIZE (BLOCK)
00035 BLOCK::BLOCK(const char *name,                //< filename
00036              BOOL8 prop,                      //< proportional
00037              inT16 kern,                      //< kerning
00038              inT16 space,                     //< spacing
00039              inT16 xmin,                      //< bottom left
00040              inT16 ymin, inT16 xmax,          //< top right
00041              inT16 ymax)
00042   : PDBLK (xmin, ymin, xmax, ymax),
00043     filename(name),
00044     re_rotation_(1.0f, 0.0f),
00045     classify_rotation_(1.0f, 0.0f),
00046     skew_(1.0f, 0.0f) {
00047   ICOORDELT_IT left_it = &leftside;
00048   ICOORDELT_IT right_it = &rightside;
00049 
00050   proportional = prop;
00051   right_to_left_ = false;
00052   kerning = kern;
00053   spacing = space;
00054   font_class = -1;               //not assigned
00055   cell_over_xheight_ = 2.0f;
00056   hand_poly = NULL;
00057   left_it.set_to_list (&leftside);
00058   right_it.set_to_list (&rightside);
00059                                  //make default box
00060   left_it.add_to_end (new ICOORDELT (xmin, ymin));
00061   left_it.add_to_end (new ICOORDELT (xmin, ymax));
00062   right_it.add_to_end (new ICOORDELT (xmax, ymin));
00063   right_it.add_to_end (new ICOORDELT (xmax, ymax));
00064 }
00065 
00072 int decreasing_top_order(  //
00073                          const void *row1,
00074                          const void *row2) {
00075   return (*(ROW **) row2)->bounding_box ().top () -
00076     (*(ROW **) row1)->bounding_box ().top ();
00077 }
00078 
00079 
00085 void BLOCK::rotate(const FCOORD& rotation) {
00086   poly_block()->rotate(rotation);
00087   box = *poly_block()->bounding_box();
00088 }
00089 
00096 void BLOCK::reflect_polygon_in_y_axis() {
00097   poly_block()->reflect_in_y_axis();
00098   box = *poly_block()->bounding_box();
00099 }
00100 
00107 void BLOCK::sort_rows() {  // order on "top"
00108   ROW_IT row_it(&rows);
00109 
00110   row_it.sort (decreasing_top_order);
00111 }
00112 
00113 
00121 void BLOCK::compress() {  // squash it up
00122   #define           ROW_SPACING 5
00123 
00124   ROW_IT row_it(&rows);
00125   ROW *row;
00126   ICOORD row_spacing (0, ROW_SPACING);
00127 
00128   ICOORDELT_IT icoordelt_it;
00129 
00130   sort_rows();
00131 
00132   box = TBOX (box.topleft (), box.topleft ());
00133   box.move_bottom_edge (ROW_SPACING);
00134   for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
00135     row = row_it.data ();
00136     row->move (box.botleft () - row_spacing -
00137       row->bounding_box ().topleft ());
00138     box += row->bounding_box ();
00139   }
00140 
00141   leftside.clear ();
00142   icoordelt_it.set_to_list (&leftside);
00143   icoordelt_it.add_to_end (new ICOORDELT (box.left (), box.bottom ()));
00144   icoordelt_it.add_to_end (new ICOORDELT (box.left (), box.top ()));
00145   rightside.clear ();
00146   icoordelt_it.set_to_list (&rightside);
00147   icoordelt_it.add_to_end (new ICOORDELT (box.right (), box.bottom ()));
00148   icoordelt_it.add_to_end (new ICOORDELT (box.right (), box.top ()));
00149 }
00150 
00151 
00159 void BLOCK::check_pitch() {  // check prop
00160   //      tprintf("Missing FFT fixed pitch stuff!\n");
00161   pitch = -1;
00162 }
00163 
00164 
00171 void BLOCK::compress(                  // squash it up
00172                      const ICOORD vec  // and move
00173                     ) {
00174   box.move (vec);
00175   compress();
00176 }
00177 
00178 
00185 void BLOCK::print(            //print list of sides
00186                   FILE *,     //< file to print on
00187                   BOOL8 dump  //< print full detail
00188                  ) {
00189   ICOORDELT_IT it = &leftside;   //iterator
00190 
00191   box.print ();
00192   tprintf ("Proportional= %s\n", proportional ? "TRUE" : "FALSE");
00193   tprintf ("Kerning= %d\n", kerning);
00194   tprintf ("Spacing= %d\n", spacing);
00195   tprintf ("Fixed_pitch=%d\n", pitch);
00196   tprintf ("Filename= %s\n", filename.string ());
00197 
00198   if (dump) {
00199     tprintf ("Left side coords are:\n");
00200     for (it.mark_cycle_pt (); !it.cycled_list (); it.forward ())
00201       tprintf ("(%d,%d) ", it.data ()->x (), it.data ()->y ());
00202     tprintf ("\n");
00203     tprintf ("Right side coords are:\n");
00204     it.set_to_list (&rightside);
00205     for (it.mark_cycle_pt (); !it.cycled_list (); it.forward ())
00206       tprintf ("(%d,%d) ", it.data ()->x (), it.data ()->y ());
00207     tprintf ("\n");
00208   }
00209 }
00210 
00217 BLOCK & BLOCK::operator= (       //assignment
00218 const BLOCK & source             //from this
00219 ) {
00220   this->ELIST_LINK::operator= (source);
00221   this->PDBLK::operator= (source);
00222   proportional = source.proportional;
00223   kerning = source.kerning;
00224   spacing = source.spacing;
00225   filename = source.filename;    //STRINGs assign ok
00226   if (!rows.empty ())
00227     rows.clear ();
00228   re_rotation_ = source.re_rotation_;
00229   classify_rotation_ = source.classify_rotation_;
00230   skew_ = source.skew_;
00231   return *this;
00232 }
00233 
00234 // This function is for finding the approximate (horizontal) distance from
00235 // the x-coordinate of the left edge of a symbol to the left edge of the
00236 // text block which contains it.  We are passed:
00237 //   segments - output of PB_LINE_IT::get_line() which contains x-coordinate
00238 //       intervals for the scan line going through the symbol's y-coordinate.
00239 //       Each element of segments is of the form (x()=start_x, y()=length).
00240 //   x - the x coordinate of the symbol we're interested in.
00241 //   margin - return value, the distance from x,y to the left margin of the
00242 //       block containing it.
00243 // If all segments were to the right of x, we return false and 0.
00244 bool LeftMargin(ICOORDELT_LIST *segments, int x, int *margin) {
00245   bool found = false;
00246   *margin = 0;
00247   if (segments->empty())
00248     return found;
00249   ICOORDELT_IT seg_it(segments);
00250   for (seg_it.mark_cycle_pt(); !seg_it.cycled_list(); seg_it.forward()) {
00251     int cur_margin = x - seg_it.data()->x();
00252     if (cur_margin >= 0) {
00253       if (!found) {
00254         *margin = cur_margin;
00255       } else if (cur_margin < *margin) {
00256         *margin = cur_margin;
00257       }
00258       found = true;
00259     }
00260   }
00261   return found;
00262 }
00263 
00264 // This function is for finding the approximate (horizontal) distance from
00265 // the x-coordinate of the right edge of a symbol to the right edge of the
00266 // text block which contains it.  We are passed:
00267 //   segments - output of PB_LINE_IT::get_line() which contains x-coordinate
00268 //       intervals for the scan line going through the symbol's y-coordinate.
00269 //       Each element of segments is of the form (x()=start_x, y()=length).
00270 //   x - the x coordinate of the symbol we're interested in.
00271 //   margin - return value, the distance from x,y to the right margin of the
00272 //       block containing it.
00273 // If all segments were to the left of x, we return false and 0.
00274 bool RightMargin(ICOORDELT_LIST *segments, int x, int *margin) {
00275   bool found = false;
00276   *margin = 0;
00277   if (segments->empty())
00278     return found;
00279   ICOORDELT_IT seg_it(segments);
00280   for (seg_it.mark_cycle_pt(); !seg_it.cycled_list(); seg_it.forward()) {
00281     int cur_margin = seg_it.data()->x() + seg_it.data()->y() - x;
00282     if (cur_margin >= 0) {
00283       if (!found) {
00284         *margin = cur_margin;
00285       } else if (cur_margin < *margin) {
00286         *margin = cur_margin;
00287       }
00288       found = true;
00289     }
00290   }
00291   return found;
00292 }
00293 
00294 // Compute the distance from the left and right ends of each row to the
00295 // left and right edges of the block's polyblock.  Illustration:
00296 //  ____________________________   _______________________
00297 //  |  Howdy neighbor!         |  |rectangular blocks look|
00298 //  |  This text is  written to|  |more like stacked pizza|
00299 //  |illustrate how useful poly-  |boxes.                 |
00300 //  |blobs  are   in -----------  ------   The    polyblob|
00301 //  |dealing    with|     _________     |for a BLOCK  rec-|
00302 //  |harder   layout|   /===========\   |ords the possibly|
00303 //  |issues.        |    |  _    _  |   |skewed    pseudo-|
00304 //  |  You  see this|    | |_| \|_| |   |rectangular      |
00305 //  |text is  flowed|    |      }   |   |boundary     that|
00306 //  |around  a  mid-|     \   ____  |   |forms the  ideal-|
00307 //  |cloumn portrait._____ \       /  __|ized  text margin|
00308 //  |  Polyblobs     exist| \    /   |from which we should|
00309 //  |to account for insets|  |   |   |measure    paragraph|
00310 //  |which make  otherwise|  -----   |indentation.        |
00311 //  -----------------------          ----------------------
00312 //
00313 // If we identify a drop-cap, we measure the left margin for the lines
00314 // below the first line relative to one space past the drop cap.  The
00315 // first line's margin and those past the drop cap area are measured
00316 // relative to the enclosing polyblock.
00317 //
00318 // TODO(rays): Before this will work well, we'll need to adjust the
00319 //             polyblob tighter around the text near images, as in:
00320 //             UNLV_AUTO:mag.3G0  page 2
00321 //             UNLV_AUTO:mag.3G4  page 16
00322 void BLOCK::compute_row_margins() {
00323   if (row_list()->empty() || row_list()->singleton()) {
00324     return;
00325   }
00326 
00327   // If Layout analysis was not called, default to this.
00328   POLY_BLOCK rect_block(bounding_box(), PT_FLOWING_TEXT);
00329   POLY_BLOCK *pblock = &rect_block;
00330   if (poly_block() != NULL) {
00331     pblock = poly_block();
00332   }
00333 
00334   // Step One: Determine if there is a drop-cap.
00335   //           TODO(eger): Fix up drop cap code for RTL languages.
00336   ROW_IT r_it(row_list());
00337   ROW *first_row = r_it.data();
00338   ROW *second_row = r_it.data_relative(1);
00339 
00340   // initialize the bottom of a fictitious drop cap far above the first line.
00341   int drop_cap_bottom = first_row->bounding_box().top() +
00342                         first_row->bounding_box().height();
00343   int drop_cap_right = first_row->bounding_box().left();
00344   int mid_second_line = second_row->bounding_box().top() -
00345                         second_row->bounding_box().height() / 2;
00346   WERD_IT werd_it(r_it.data()->word_list());  // words of line one
00347   if (!werd_it.empty()) {
00348     C_BLOB_IT cblob_it(werd_it.data()->cblob_list());
00349     for (cblob_it.mark_cycle_pt(); !cblob_it.cycled_list();
00350          cblob_it.forward()) {
00351       TBOX bbox = cblob_it.data()->bounding_box();
00352       if (bbox.bottom() <= mid_second_line) {
00353         // we found a real drop cap
00354         first_row->set_has_drop_cap(true);
00355         if (drop_cap_bottom >  bbox.bottom())
00356           drop_cap_bottom = bbox.bottom();
00357         if (drop_cap_right < bbox.right())
00358           drop_cap_right = bbox.right();
00359       }
00360     }
00361   }
00362 
00363   // Step Two: Calculate the margin from the text of each row to the block
00364   //           (or drop-cap) boundaries.
00365   PB_LINE_IT lines(pblock);
00366   r_it.set_to_list(row_list());
00367   for (r_it.mark_cycle_pt(); !r_it.cycled_list(); r_it.forward()) {
00368     ROW *row = r_it.data();
00369     TBOX row_box = row->bounding_box();
00370     int left_y = row->base_line(row_box.left()) + row->x_height();
00371     int left_margin;
00372     ICOORDELT_LIST *segments = lines.get_line(left_y);
00373     LeftMargin(segments, row_box.left(), &left_margin);
00374     delete segments;
00375 
00376     if (row_box.top() >= drop_cap_bottom) {
00377       int drop_cap_distance = row_box.left() - row->space() - drop_cap_right;
00378       if (drop_cap_distance < 0)
00379         drop_cap_distance = 0;
00380       if (drop_cap_distance < left_margin)
00381         left_margin = drop_cap_distance;
00382     }
00383 
00384     int right_y = row->base_line(row_box.right()) + row->x_height();
00385     int right_margin;
00386     segments = lines.get_line(right_y);
00387     RightMargin(segments, row_box.right(), &right_margin);
00388     delete segments;
00389     row->set_lmargin(left_margin);
00390     row->set_rmargin(right_margin);
00391   }
00392 }
00393 
00394 /**********************************************************************
00395  * PrintSegmentationStats
00396  *
00397  * Prints segmentation stats for the given block list.
00398  **********************************************************************/
00399 
00400 void PrintSegmentationStats(BLOCK_LIST* block_list) {
00401   int num_blocks = 0;
00402   int num_rows = 0;
00403   int num_words = 0;
00404   int num_blobs = 0;
00405   BLOCK_IT block_it(block_list);
00406   for (block_it.mark_cycle_pt(); !block_it.cycled_list(); block_it.forward()) {
00407     BLOCK* block = block_it.data();
00408     ++num_blocks;
00409     ROW_IT row_it(block->row_list());
00410     for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
00411       ++num_rows;
00412       ROW* row = row_it.data();
00413       // Iterate over all werds in the row.
00414       WERD_IT werd_it(row->word_list());
00415       for (werd_it.mark_cycle_pt(); !werd_it.cycled_list(); werd_it.forward()) {
00416         WERD* werd = werd_it.data();
00417         ++num_words;
00418         num_blobs += werd->cblob_list()->length();
00419       }
00420     }
00421   }
00422   tprintf("Block list stats:\nBlocks = %d\nRows = %d\nWords = %d\nBlobs = %d\n",
00423           num_blocks, num_rows, num_words, num_blobs);
00424 }
00425 
00426 /**********************************************************************
00427  * ExtractBlobsFromSegmentation
00428  *
00429  * Extracts blobs from the given block list and adds them to the output list.
00430  * The block list must have been created by performing a page segmentation.
00431  **********************************************************************/
00432 
00433 void ExtractBlobsFromSegmentation(BLOCK_LIST* blocks,
00434                                   C_BLOB_LIST* output_blob_list) {
00435   C_BLOB_IT return_list_it(output_blob_list);
00436   BLOCK_IT block_it(blocks);
00437   for (block_it.mark_cycle_pt(); !block_it.cycled_list(); block_it.forward()) {
00438     BLOCK* block = block_it.data();
00439     ROW_IT row_it(block->row_list());
00440     for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
00441       ROW* row = row_it.data();
00442       // Iterate over all werds in the row.
00443       WERD_IT werd_it(row->word_list());
00444       for (werd_it.mark_cycle_pt(); !werd_it.cycled_list(); werd_it.forward()) {
00445         WERD* werd = werd_it.data();
00446         return_list_it.move_to_last();
00447         return_list_it.add_list_after(werd->cblob_list());
00448         return_list_it.move_to_last();
00449         return_list_it.add_list_after(werd->rej_cblob_list());
00450       }
00451     }
00452   }
00453 }
00454 
00455 /**********************************************************************
00456  * RefreshWordBlobsFromNewBlobs()
00457  *
00458  * Refreshes the words in the block_list by using blobs in the
00459  * new_blobs list.
00460  * Block list must have word segmentation in it.
00461  * It consumes the blobs provided in the new_blobs list. The blobs leftover in
00462  * the new_blobs list after the call weren't matched to any blobs of the words
00463  * in block list.
00464  * The output not_found_blobs is a list of blobs from the original segmentation
00465  * in the block_list for which no corresponding new blobs were found.
00466  **********************************************************************/
00467 
00468 void RefreshWordBlobsFromNewBlobs(BLOCK_LIST* block_list,
00469                                   C_BLOB_LIST* new_blobs,
00470                                   C_BLOB_LIST* not_found_blobs) {
00471   // Now iterate over all the blobs in the segmentation_block_list_, and just
00472   // replace the corresponding c-blobs inside the werds.
00473   BLOCK_IT block_it(block_list);
00474   for (block_it.mark_cycle_pt(); !block_it.cycled_list(); block_it.forward()) {
00475     BLOCK* block = block_it.data();
00476     // Iterate over all rows in the block.
00477     ROW_IT row_it(block->row_list());
00478     for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
00479       ROW* row = row_it.data();
00480       // Iterate over all werds in the row.
00481       WERD_IT werd_it(row->word_list());
00482       WERD_LIST new_words;
00483       WERD_IT new_words_it(&new_words);
00484       for (werd_it.mark_cycle_pt(); !werd_it.cycled_list(); werd_it.forward()) {
00485         WERD* werd = werd_it.extract();
00486         WERD* new_werd = werd->ConstructWerdWithNewBlobs(new_blobs,
00487                                                          not_found_blobs);
00488         if (new_werd) {
00489           // Insert this new werd into the actual row's werd-list. Remove the
00490           // existing one.
00491           new_words_it.add_after_then_move(new_werd);
00492           delete werd;
00493         } else {
00494           // Reinsert the older word back, for lack of better options.
00495           // This is critical since dropping the words messes up segmentation:
00496           // eg. 1st word in the row might otherwise have W_FUZZY_NON turned on.
00497           new_words_it.add_after_then_move(werd);
00498         }
00499       }
00500       // Get rid of the old word list & replace it with the new one.
00501       row->word_list()->clear();
00502       werd_it.move_to_first();
00503       werd_it.add_list_after(&new_words);
00504     }
00505   }
00506 }