tesseract  4.00.00dev
ocrblock.cpp
Go to the documentation of this file.
1 /**********************************************************************
2  * File: ocrblock.cpp (Formerly block.c)
3  * Description: BLOCK member functions and iterator functions.
4  * Author: Ray Smith
5  * Created: Fri Mar 15 09:41:28 GMT 1991
6  *
7  * (C) Copyright 1991, Hewlett-Packard Ltd.
8  ** Licensed under the Apache License, Version 2.0 (the "License");
9  ** you may not use this file except in compliance with the License.
10  ** You may obtain a copy of the License at
11  ** http://www.apache.org/licenses/LICENSE-2.0
12  ** Unless required by applicable law or agreed to in writing, software
13  ** distributed under the License is distributed on an "AS IS" BASIS,
14  ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  ** See the License for the specific language governing permissions and
16  ** limitations under the License.
17  *
18  **********************************************************************/
19 
20 #include <stdlib.h>
21 #include <memory> // std::unique_ptr
22 #include "blckerr.h"
23 #include "ocrblock.h"
24 #include "stepblob.h"
25 #include "tprintf.h"
26 
27 #define BLOCK_LABEL_HEIGHT 150 //char height of block id
28 
35 BLOCK::BLOCK(const char *name, //< filename
36  BOOL8 prop, //< proportional
37  inT16 kern, //< kerning
38  inT16 space, //< spacing
39  inT16 xmin, //< bottom left
40  inT16 ymin, inT16 xmax, //< top right
41  inT16 ymax)
42  : PDBLK (xmin, ymin, xmax, ymax),
43  filename(name),
44  re_rotation_(1.0f, 0.0f),
45  classify_rotation_(1.0f, 0.0f),
46  skew_(1.0f, 0.0f) {
47  ICOORDELT_IT left_it = &leftside;
48  ICOORDELT_IT right_it = &rightside;
49 
50  proportional = prop;
51  right_to_left_ = false;
52  kerning = kern;
53  spacing = space;
54  font_class = -1; //not assigned
55  cell_over_xheight_ = 2.0f;
56  hand_poly = NULL;
57  left_it.set_to_list (&leftside);
58  right_it.set_to_list (&rightside);
59  //make default box
60  left_it.add_to_end (new ICOORDELT (xmin, ymin));
61  left_it.add_to_end (new ICOORDELT (xmin, ymax));
62  right_it.add_to_end (new ICOORDELT (xmax, ymin));
63  right_it.add_to_end (new ICOORDELT (xmax, ymax));
64 }
65 
73  const void *row1,
74  const void *row2) {
75  return (*(ROW **) row2)->bounding_box ().top () -
76  (*(ROW **) row1)->bounding_box ().top ();
77 }
78 
79 
85 void BLOCK::rotate(const FCOORD& rotation) {
86  poly_block()->rotate(rotation);
87  box = *poly_block()->bounding_box();
88 }
89 
90 // Returns the bounding box including the desired combination of upper and
91 // lower noise/diacritic elements.
92 TBOX BLOCK::restricted_bounding_box(bool upper_dots, bool lower_dots) const {
93  TBOX box;
94  // This is a read-only iteration of the rows in the block.
95  ROW_IT it(const_cast<ROW_LIST*>(&rows));
96  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
97  box += it.data()->restricted_bounding_box(upper_dots, lower_dots);
98  }
99  return box;
100 }
101 
110  box = *poly_block()->bounding_box();
111 }
112 
119 void BLOCK::sort_rows() { // order on "top"
120  ROW_IT row_it(&rows);
121 
122  row_it.sort (decreasing_top_order);
123 }
124 
125 
133 void BLOCK::compress() { // squash it up
134  #define ROW_SPACING 5
135 
136  ROW_IT row_it(&rows);
137  ROW *row;
138  ICOORD row_spacing (0, ROW_SPACING);
139 
140  ICOORDELT_IT icoordelt_it;
141 
142  sort_rows();
143 
144  box = TBOX (box.topleft (), box.topleft ());
146  for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
147  row = row_it.data ();
148  row->move (box.botleft () - row_spacing -
149  row->bounding_box ().topleft ());
150  box += row->bounding_box ();
151  }
152 
153  leftside.clear ();
154  icoordelt_it.set_to_list (&leftside);
155  icoordelt_it.add_to_end (new ICOORDELT (box.left (), box.bottom ()));
156  icoordelt_it.add_to_end (new ICOORDELT (box.left (), box.top ()));
157  rightside.clear ();
158  icoordelt_it.set_to_list (&rightside);
159  icoordelt_it.add_to_end (new ICOORDELT (box.right (), box.bottom ()));
160  icoordelt_it.add_to_end (new ICOORDELT (box.right (), box.top ()));
161 }
162 
163 
171 void BLOCK::check_pitch() { // check prop
172  // tprintf("Missing FFT fixed pitch stuff!\n");
173  pitch = -1;
174 }
175 
176 
183 void BLOCK::compress( // squash it up
184  const ICOORD vec // and move
185  ) {
186  box.move (vec);
187  compress();
188 }
189 
190 
197 void BLOCK::print( //print list of sides
198  FILE *, //< file to print on
199  BOOL8 dump //< print full detail
200  ) {
201  ICOORDELT_IT it = &leftside; //iterator
202 
203  box.print ();
204  tprintf ("Proportional= %s\n", proportional ? "TRUE" : "FALSE");
205  tprintf ("Kerning= %d\n", kerning);
206  tprintf ("Spacing= %d\n", spacing);
207  tprintf ("Fixed_pitch=%d\n", pitch);
208  tprintf ("Filename= %s\n", filename.string ());
209 
210  if (dump) {
211  tprintf ("Left side coords are:\n");
212  for (it.mark_cycle_pt (); !it.cycled_list (); it.forward ())
213  tprintf ("(%d,%d) ", it.data ()->x (), it.data ()->y ());
214  tprintf ("\n");
215  tprintf ("Right side coords are:\n");
216  it.set_to_list (&rightside);
217  for (it.mark_cycle_pt (); !it.cycled_list (); it.forward ())
218  tprintf ("(%d,%d) ", it.data ()->x (), it.data ()->y ());
219  tprintf ("\n");
220  }
221 }
222 
229 BLOCK & BLOCK::operator= ( //assignment
230 const BLOCK & source //from this
231 ) {
232  this->ELIST_LINK::operator= (source);
233  this->PDBLK::operator= (source);
234  proportional = source.proportional;
235  kerning = source.kerning;
236  spacing = source.spacing;
237  filename = source.filename; //STRINGs assign ok
238  if (!rows.empty ())
239  rows.clear ();
240  re_rotation_ = source.re_rotation_;
241  classify_rotation_ = source.classify_rotation_;
242  skew_ = source.skew_;
243  return *this;
244 }
245 
246 // This function is for finding the approximate (horizontal) distance from
247 // the x-coordinate of the left edge of a symbol to the left edge of the
248 // text block which contains it. We are passed:
249 // segments - output of PB_LINE_IT::get_line() which contains x-coordinate
250 // intervals for the scan line going through the symbol's y-coordinate.
251 // Each element of segments is of the form (x()=start_x, y()=length).
252 // x - the x coordinate of the symbol we're interested in.
253 // margin - return value, the distance from x,y to the left margin of the
254 // block containing it.
255 // If all segments were to the right of x, we return false and 0.
256 bool LeftMargin(ICOORDELT_LIST *segments, int x, int *margin) {
257  bool found = false;
258  *margin = 0;
259  if (segments->empty())
260  return found;
261  ICOORDELT_IT seg_it(segments);
262  for (seg_it.mark_cycle_pt(); !seg_it.cycled_list(); seg_it.forward()) {
263  int cur_margin = x - seg_it.data()->x();
264  if (cur_margin >= 0) {
265  if (!found) {
266  *margin = cur_margin;
267  } else if (cur_margin < *margin) {
268  *margin = cur_margin;
269  }
270  found = true;
271  }
272  }
273  return found;
274 }
275 
276 // This function is for finding the approximate (horizontal) distance from
277 // the x-coordinate of the right edge of a symbol to the right edge of the
278 // text block which contains it. We are passed:
279 // segments - output of PB_LINE_IT::get_line() which contains x-coordinate
280 // intervals for the scan line going through the symbol's y-coordinate.
281 // Each element of segments is of the form (x()=start_x, y()=length).
282 // x - the x coordinate of the symbol we're interested in.
283 // margin - return value, the distance from x,y to the right margin of the
284 // block containing it.
285 // If all segments were to the left of x, we return false and 0.
286 bool RightMargin(ICOORDELT_LIST *segments, int x, int *margin) {
287  bool found = false;
288  *margin = 0;
289  if (segments->empty())
290  return found;
291  ICOORDELT_IT seg_it(segments);
292  for (seg_it.mark_cycle_pt(); !seg_it.cycled_list(); seg_it.forward()) {
293  int cur_margin = seg_it.data()->x() + seg_it.data()->y() - x;
294  if (cur_margin >= 0) {
295  if (!found) {
296  *margin = cur_margin;
297  } else if (cur_margin < *margin) {
298  *margin = cur_margin;
299  }
300  found = true;
301  }
302  }
303  return found;
304 }
305 
306 // Compute the distance from the left and right ends of each row to the
307 // left and right edges of the block's polyblock. Illustration:
308 // ____________________________ _______________________
309 // | Howdy neighbor! | |rectangular blocks look|
310 // | This text is written to| |more like stacked pizza|
311 // |illustrate how useful poly- |boxes. |
312 // |blobs are in ----------- ------ The polyblob|
313 // |dealing with| _________ |for a BLOCK rec-|
314 // |harder layout| /===========\ |ords the possibly|
315 // |issues. | | _ _ | |skewed pseudo-|
316 // | You see this| | |_| \|_| | |rectangular |
317 // |text is flowed| | } | |boundary that|
318 // |around a mid-| \ ____ | |forms the ideal-|
319 // |cloumn portrait._____ \ / __|ized text margin|
320 // | Polyblobs exist| \ / |from which we should|
321 // |to account for insets| | | |measure paragraph|
322 // |which make otherwise| ----- |indentation. |
323 // ----------------------- ----------------------
324 //
325 // If we identify a drop-cap, we measure the left margin for the lines
326 // below the first line relative to one space past the drop cap. The
327 // first line's margin and those past the drop cap area are measured
328 // relative to the enclosing polyblock.
329 //
330 // TODO(rays): Before this will work well, we'll need to adjust the
331 // polyblob tighter around the text near images, as in:
332 // UNLV_AUTO:mag.3G0 page 2
333 // UNLV_AUTO:mag.3G4 page 16
335  if (row_list()->empty() || row_list()->singleton()) {
336  return;
337  }
338 
339  // If Layout analysis was not called, default to this.
340  POLY_BLOCK rect_block(bounding_box(), PT_FLOWING_TEXT);
341  POLY_BLOCK *pblock = &rect_block;
342  if (poly_block() != NULL) {
343  pblock = poly_block();
344  }
345 
346  // Step One: Determine if there is a drop-cap.
347  // TODO(eger): Fix up drop cap code for RTL languages.
348  ROW_IT r_it(row_list());
349  ROW *first_row = r_it.data();
350  ROW *second_row = r_it.data_relative(1);
351 
352  // initialize the bottom of a fictitious drop cap far above the first line.
353  int drop_cap_bottom = first_row->bounding_box().top() +
354  first_row->bounding_box().height();
355  int drop_cap_right = first_row->bounding_box().left();
356  int mid_second_line = second_row->bounding_box().top() -
357  second_row->bounding_box().height() / 2;
358  WERD_IT werd_it(r_it.data()->word_list()); // words of line one
359  if (!werd_it.empty()) {
360  C_BLOB_IT cblob_it(werd_it.data()->cblob_list());
361  for (cblob_it.mark_cycle_pt(); !cblob_it.cycled_list();
362  cblob_it.forward()) {
363  TBOX bbox = cblob_it.data()->bounding_box();
364  if (bbox.bottom() <= mid_second_line) {
365  // we found a real drop cap
366  first_row->set_has_drop_cap(true);
367  if (drop_cap_bottom > bbox.bottom())
368  drop_cap_bottom = bbox.bottom();
369  if (drop_cap_right < bbox.right())
370  drop_cap_right = bbox.right();
371  }
372  }
373  }
374 
375  // Step Two: Calculate the margin from the text of each row to the block
376  // (or drop-cap) boundaries.
377  PB_LINE_IT lines(pblock);
378  r_it.set_to_list(row_list());
379  for (r_it.mark_cycle_pt(); !r_it.cycled_list(); r_it.forward()) {
380  ROW *row = r_it.data();
381  TBOX row_box = row->bounding_box();
382  int left_y = row->base_line(row_box.left()) + row->x_height();
383  int left_margin;
384  const std::unique_ptr</*non-const*/ ICOORDELT_LIST> segments_left(lines.get_line(left_y));
385  LeftMargin(segments_left.get(), row_box.left(), &left_margin);
386 
387  if (row_box.top() >= drop_cap_bottom) {
388  int drop_cap_distance = row_box.left() - row->space() - drop_cap_right;
389  if (drop_cap_distance < 0)
390  drop_cap_distance = 0;
391  if (drop_cap_distance < left_margin)
392  left_margin = drop_cap_distance;
393  }
394 
395  int right_y = row->base_line(row_box.right()) + row->x_height();
396  int right_margin;
397  const std::unique_ptr</*non-const*/ ICOORDELT_LIST> segments_right(lines.get_line(right_y));
398  RightMargin(segments_right.get(), row_box.right(), &right_margin);
399  row->set_lmargin(left_margin);
400  row->set_rmargin(right_margin);
401  }
402 }
403 
404 /**********************************************************************
405  * PrintSegmentationStats
406  *
407  * Prints segmentation stats for the given block list.
408  **********************************************************************/
409 
410 void PrintSegmentationStats(BLOCK_LIST* block_list) {
411  int num_blocks = 0;
412  int num_rows = 0;
413  int num_words = 0;
414  int num_blobs = 0;
415  BLOCK_IT block_it(block_list);
416  for (block_it.mark_cycle_pt(); !block_it.cycled_list(); block_it.forward()) {
417  BLOCK* block = block_it.data();
418  ++num_blocks;
419  ROW_IT row_it(block->row_list());
420  for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
421  ++num_rows;
422  ROW* row = row_it.data();
423  // Iterate over all werds in the row.
424  WERD_IT werd_it(row->word_list());
425  for (werd_it.mark_cycle_pt(); !werd_it.cycled_list(); werd_it.forward()) {
426  WERD* werd = werd_it.data();
427  ++num_words;
428  num_blobs += werd->cblob_list()->length();
429  }
430  }
431  }
432  tprintf("Block list stats:\nBlocks = %d\nRows = %d\nWords = %d\nBlobs = %d\n",
433  num_blocks, num_rows, num_words, num_blobs);
434 }
435 
436 /**********************************************************************
437  * ExtractBlobsFromSegmentation
438  *
439  * Extracts blobs from the given block list and adds them to the output list.
440  * The block list must have been created by performing a page segmentation.
441  **********************************************************************/
442 
443 void ExtractBlobsFromSegmentation(BLOCK_LIST* blocks,
444  C_BLOB_LIST* output_blob_list) {
445  C_BLOB_IT return_list_it(output_blob_list);
446  BLOCK_IT block_it(blocks);
447  for (block_it.mark_cycle_pt(); !block_it.cycled_list(); block_it.forward()) {
448  BLOCK* block = block_it.data();
449  ROW_IT row_it(block->row_list());
450  for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
451  ROW* row = row_it.data();
452  // Iterate over all werds in the row.
453  WERD_IT werd_it(row->word_list());
454  for (werd_it.mark_cycle_pt(); !werd_it.cycled_list(); werd_it.forward()) {
455  WERD* werd = werd_it.data();
456  return_list_it.move_to_last();
457  return_list_it.add_list_after(werd->cblob_list());
458  return_list_it.move_to_last();
459  return_list_it.add_list_after(werd->rej_cblob_list());
460  }
461  }
462  }
463 }
464 
465 /**********************************************************************
466  * RefreshWordBlobsFromNewBlobs()
467  *
468  * Refreshes the words in the block_list by using blobs in the
469  * new_blobs list.
470  * Block list must have word segmentation in it.
471  * It consumes the blobs provided in the new_blobs list. The blobs leftover in
472  * the new_blobs list after the call weren't matched to any blobs of the words
473  * in block list.
474  * The output not_found_blobs is a list of blobs from the original segmentation
475  * in the block_list for which no corresponding new blobs were found.
476  **********************************************************************/
477 
478 void RefreshWordBlobsFromNewBlobs(BLOCK_LIST* block_list,
479  C_BLOB_LIST* new_blobs,
480  C_BLOB_LIST* not_found_blobs) {
481  // Now iterate over all the blobs in the segmentation_block_list_, and just
482  // replace the corresponding c-blobs inside the werds.
483  BLOCK_IT block_it(block_list);
484  for (block_it.mark_cycle_pt(); !block_it.cycled_list(); block_it.forward()) {
485  BLOCK* block = block_it.data();
486  if (block->poly_block() != NULL && !block->poly_block()->IsText())
487  continue; // Don't touch non-text blocks.
488  // Iterate over all rows in the block.
489  ROW_IT row_it(block->row_list());
490  for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
491  ROW* row = row_it.data();
492  // Iterate over all werds in the row.
493  WERD_IT werd_it(row->word_list());
494  WERD_LIST new_words;
495  WERD_IT new_words_it(&new_words);
496  for (werd_it.mark_cycle_pt(); !werd_it.cycled_list(); werd_it.forward()) {
497  WERD* werd = werd_it.extract();
498  WERD* new_werd = werd->ConstructWerdWithNewBlobs(new_blobs,
499  not_found_blobs);
500  if (new_werd) {
501  // Insert this new werd into the actual row's werd-list. Remove the
502  // existing one.
503  new_words_it.add_after_then_move(new_werd);
504  delete werd;
505  } else {
506  // Reinsert the older word back, for lack of better options.
507  // This is critical since dropping the words messes up segmentation:
508  // eg. 1st word in the row might otherwise have W_FUZZY_NON turned on.
509  new_words_it.add_after_then_move(werd);
510  }
511  }
512  // Get rid of the old word list & replace it with the new one.
513  row->word_list()->clear();
514  werd_it.move_to_first();
515  werd_it.add_list_after(&new_words);
516  }
517  }
518 }
void sort_rows()
decreasing y order
Definition: ocrblock.cpp:119
void rotate(const FCOORD &rotation)
Definition: ocrblock.cpp:85
int decreasing_top_order(const void *row1, const void *row2)
Definition: ocrblock.cpp:72
Definition: points.h:189
PDBLK & operator=(const PDBLK &source)
Definition: pdblock.cpp:238
WERD * ConstructWerdWithNewBlobs(C_BLOB_LIST *all_blobs, C_BLOB_LIST *orphan_blobs)
Definition: werd.cpp:412
void RefreshWordBlobsFromNewBlobs(BLOCK_LIST *block_list, C_BLOB_LIST *new_blobs, C_BLOB_LIST *not_found_blobs)
Definition: ocrblock.cpp:478
void rotate(FCOORD rotation)
Definition: polyblk.cpp:186
void check_pitch()
check proportional
Definition: ocrblock.cpp:171
void move(const ICOORD vec)
Definition: ocrrow.cpp:148
#define tprintf(...)
Definition: tprintf.h:31
void set_has_drop_cap(bool has)
Definition: ocrrow.h:105
WERD_LIST * word_list()
Definition: ocrrow.h:52
float x_height() const
Definition: ocrrow.h:61
int16_t inT16
Definition: host.h:36
inT16 left() const
Definition: rect.h:68
bool IsText() const
Definition: polyblk.h:52
void reflect_in_y_axis()
Definition: polyblk.cpp:210
ICOORDELT_LIST * get_line(inT16 y)
Definition: polyblk.cpp:343
void set_rmargin(inT16 rmargin)
Definition: ocrrow.h:95
unsigned char BOOL8
Definition: host.h:44
bool LeftMargin(ICOORDELT_LIST *segments, int x, int *margin)
Definition: ocrblock.cpp:256
void ExtractBlobsFromSegmentation(BLOCK_LIST *blocks, C_BLOB_LIST *output_blob_list)
Definition: ocrblock.cpp:443
TBOX * bounding_box()
Definition: polyblk.h:38
ICOORD topleft() const
Definition: rect.h:96
TBOX bounding_box() const
Definition: ocrrow.h:85
void print(FILE *fp, BOOL8 dump)
dump whole table
Definition: ocrblock.cpp:197
void PrintSegmentationStats(BLOCK_LIST *block_list)
Definition: ocrblock.cpp:410
inT16 top() const
Definition: rect.h:54
void compress()
shrink white space
Definition: ocrblock.cpp:133
Definition: rect.h:30
POLY_BLOCK * poly_block() const
Definition: pdblock.h:55
void reflect_polygon_in_y_axis()
Definition: ocrblock.cpp:108
C_BLOB_LIST * cblob_list()
Definition: werd.h:100
inT16 height() const
Definition: rect.h:104
const char * filename
Definition: ioapi.h:38
inT16 right() const
Definition: rect.h:75
void print() const
Definition: rect.h:270
C_BLOB_LIST * rej_cblob_list()
Definition: werd.h:95
void move_bottom_edge(const inT16 y)
Definition: rect.h:133
inT16 bottom() const
Definition: rect.h:61
void operator=(const ELIST_LINK &)
Definition: elst.h:101
#define ELISTIZE(CLASSNAME)
Definition: elst.h:961
void set_lmargin(inT16 lmargin)
Definition: ocrrow.h:92
void move(const ICOORD vec)
Definition: rect.h:153
const ICOORD & botleft() const
Definition: rect.h:88
float base_line(float xpos) const
Definition: ocrrow.h:56
ICOORDELT_LIST leftside
Definition: pdblock.h:96
Definition: werd.h:60
#define ROW_SPACING
const TBOX & bounding_box() const
get real box
Definition: pdblock.h:65
Definition: ocrrow.h:32
ROW_LIST * row_list()
get rows
Definition: ocrblock.h:120
void compute_row_margins()
Definition: ocrblock.cpp:334
bool RightMargin(ICOORDELT_LIST *segments, int x, int *margin)
Definition: ocrblock.cpp:286
Definition: ocrblock.h:30
BLOCK & operator=(const BLOCK &source)
Definition: ocrblock.cpp:229
TBOX box
Definition: pdblock.h:98
ICOORDELT_LIST rightside
Definition: pdblock.h:97
integer coordinate
Definition: points.h:30
inT32 space() const
Definition: ocrrow.h:76
page block
Definition: pdblock.h:32
TBOX restricted_bounding_box(bool upper_dots, bool lower_dots) const
Definition: ocrblock.cpp:92