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