tesseract v5.3.3.20231005
topitch.cpp
Go to the documentation of this file.
1/**********************************************************************
2 * File: topitch.cpp (Formerly to_pitch.c)
3 * Description: Code to determine fixed pitchness and the pitch if fixed.
4 * Author: Ray Smith
5 *
6 * (C) Copyright 1993, Hewlett-Packard Ltd.
7 ** Licensed under the Apache License, Version 2.0 (the "License");
8 ** you may not use this file except in compliance with the License.
9 ** You may obtain a copy of the License at
10 ** http://www.apache.org/licenses/LICENSE-2.0
11 ** Unless required by applicable law or agreed to in writing, software
12 ** distributed under the License is distributed on an "AS IS" BASIS,
13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 ** See the License for the specific language governing permissions and
15 ** limitations under the License.
16 *
17 **********************************************************************/
18
19// Include automatically generated configuration file if running autoconf.
20#ifdef HAVE_CONFIG_H
21# include "config_auto.h"
22#endif
23
24#include "topitch.h"
25
26#include "blobbox.h"
27#include "drawtord.h"
28#include "makerow.h"
29#include "pithsync.h"
30#include "pitsync1.h"
31#include "statistc.h"
32#include "tovars.h"
33#include "wordseg.h"
34
35#include "helpers.h"
36
37#include <memory>
38
39namespace tesseract {
40
41static BOOL_VAR(textord_all_prop, false, "All doc is proportial text");
42BOOL_VAR(textord_debug_pitch_test, false, "Debug on fixed pitch test");
43static BOOL_VAR(textord_disable_pitch_test, false, "Turn off dp fixed pitch algorithm");
44BOOL_VAR(textord_fast_pitch_test, false, "Do even faster pitch algorithm");
45BOOL_VAR(textord_debug_pitch_metric, false, "Write full metric stuff");
46BOOL_VAR(textord_show_row_cuts, false, "Draw row-level cuts");
47BOOL_VAR(textord_show_page_cuts, false, "Draw page-level cuts");
48BOOL_VAR(textord_blockndoc_fixed, false, "Attempt whole doc/block fixed pitch");
49double_VAR(textord_projection_scale, 0.200, "Ding rate for mid-cuts");
50double_VAR(textord_balance_factor, 1.0, "Ding rate for unbalanced char cells");
51
52#define BLOCK_STATS_CLUSTERS 10
53#define MAX_ALLOWED_PITCH 100 // max pixel pitch.
54
55// qsort function to sort 2 floats.
56static int sort_floats(const void *arg1, const void *arg2) {
57 float diff = *reinterpret_cast<const float *>(arg1) - *reinterpret_cast<const float *>(arg2);
58 if (diff > 0) {
59 return 1;
60 } else if (diff < 0) {
61 return -1;
62 } else {
63 return 0;
64 }
65}
66
67/**********************************************************************
68 * compute_fixed_pitch
69 *
70 * Decide whether each row is fixed pitch individually.
71 * Correlate definite and uncertain results to obtain an individual
72 * result for each row in the TO_ROW class.
73 **********************************************************************/
74
75void compute_fixed_pitch(ICOORD page_tr, // top right
76 TO_BLOCK_LIST *port_blocks, // input list
77 float gradient, // page skew
78 FCOORD rotation, // for drawing
79 bool testing_on) { // correct orientation
80 TO_BLOCK_IT block_it; // iterator
81 TO_BLOCK *block; // current block;
82 TO_ROW *row; // current row
83 int block_index; // block number
84 int row_index; // row number
85
86#ifndef GRAPHICS_DISABLED
87 if (textord_show_initial_words && testing_on) {
88 if (to_win == nullptr) {
89 create_to_win(page_tr);
90 }
91 }
92#endif
93
94 block_it.set_to_list(port_blocks);
95 block_index = 1;
96 for (block_it.mark_cycle_pt(); !block_it.cycled_list(); block_it.forward()) {
97 block = block_it.data();
98 compute_block_pitch(block, rotation, block_index, testing_on);
99 block_index++;
100 }
101
102 if (!try_doc_fixed(page_tr, port_blocks, gradient)) {
103 block_index = 1;
104 for (block_it.mark_cycle_pt(); !block_it.cycled_list(); block_it.forward()) {
105 block = block_it.data();
106 if (!try_block_fixed(block, block_index)) {
107 try_rows_fixed(block, block_index, testing_on);
108 }
109 block_index++;
110 }
111 }
112
113 block_index = 1;
114 for (block_it.mark_cycle_pt(); !block_it.cycled_list(); block_it.forward()) {
115 block = block_it.data();
116 POLY_BLOCK *pb = block->block->pdblk.poly_block();
117 if (pb != nullptr && !pb->IsText()) {
118 continue; // Non-text doesn't exist!
119 }
120 // row iterator
121 TO_ROW_IT row_it(block->get_rows());
122 row_index = 1;
123 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
124 row = row_it.data();
125 fix_row_pitch(row, block, port_blocks, row_index, block_index);
126 row_index++;
127 }
128 block_index++;
129 }
130#ifndef GRAPHICS_DISABLED
131 if (textord_show_initial_words && testing_on) {
133 }
134#endif
135}
136
137/**********************************************************************
138 * fix_row_pitch
139 *
140 * Get a pitch_decision for this row by voting among similar rows in the
141 * block, then similar rows over all the page, or any other rows at all.
142 **********************************************************************/
143
144void fix_row_pitch(TO_ROW *bad_row, // row to fix
145 TO_BLOCK *bad_block, // block of bad_row
146 TO_BLOCK_LIST *blocks, // blocks to scan
147 int32_t row_target, // number of row
148 int32_t block_target) { // number of block
149 int16_t mid_cuts;
150 int block_votes; // votes in block
151 int like_votes; // votes over page
152 int other_votes; // votes of unlike blocks
153 int block_index; // number of block
154 int row_index; // number of row
155 int maxwidth; // max pitch
156 TO_BLOCK_IT block_it = blocks; // block iterator
157 TO_BLOCK *block; // current block
158 TO_ROW *row; // current row
159 float sp_sd; // space deviation
160 STATS block_stats; // pitches in block
161 STATS like_stats; // pitches in page
162
163 block_votes = like_votes = other_votes = 0;
164 maxwidth = static_cast<int32_t>(ceil(bad_row->xheight * textord_words_maxspace));
165 if (bad_row->pitch_decision != PITCH_DEF_FIXED && bad_row->pitch_decision != PITCH_DEF_PROP) {
166 block_stats.set_range(0, maxwidth - 1);
167 like_stats.set_range(0, maxwidth - 1);
168 block_index = 1;
169 for (block_it.mark_cycle_pt(); !block_it.cycled_list(); block_it.forward()) {
170 block = block_it.data();
171 POLY_BLOCK *pb = block->block->pdblk.poly_block();
172 if (pb != nullptr && !pb->IsText()) {
173 continue; // Non text doesn't exist!
174 }
175 row_index = 1;
176 TO_ROW_IT row_it(block->get_rows());
177 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
178 row = row_it.data();
179 if ((bad_row->all_caps &&
180 row->xheight + row->ascrise <
181 (bad_row->xheight + bad_row->ascrise) * (1 + textord_pitch_rowsimilarity) &&
182 row->xheight + row->ascrise >
183 (bad_row->xheight + bad_row->ascrise) * (1 - textord_pitch_rowsimilarity)) ||
184 (!bad_row->all_caps &&
185 row->xheight < bad_row->xheight * (1 + textord_pitch_rowsimilarity) &&
186 row->xheight > bad_row->xheight * (1 - textord_pitch_rowsimilarity))) {
187 if (block_index == block_target) {
188 if (row->pitch_decision == PITCH_DEF_FIXED) {
189 block_votes += textord_words_veto_power;
190 block_stats.add(static_cast<int32_t>(row->fixed_pitch), textord_words_veto_power);
191 } else if (row->pitch_decision == PITCH_MAYBE_FIXED ||
193 block_votes++;
194 block_stats.add(static_cast<int32_t>(row->fixed_pitch), 1);
195 } else if (row->pitch_decision == PITCH_DEF_PROP) {
196 block_votes -= textord_words_veto_power;
197 } else if (row->pitch_decision == PITCH_MAYBE_PROP ||
199 block_votes--;
200 }
201 } else {
202 if (row->pitch_decision == PITCH_DEF_FIXED) {
203 like_votes += textord_words_veto_power;
204 like_stats.add(static_cast<int32_t>(row->fixed_pitch), textord_words_veto_power);
205 } else if (row->pitch_decision == PITCH_MAYBE_FIXED ||
207 like_votes++;
208 like_stats.add(static_cast<int32_t>(row->fixed_pitch), 1);
209 } else if (row->pitch_decision == PITCH_DEF_PROP) {
210 like_votes -= textord_words_veto_power;
211 } else if (row->pitch_decision == PITCH_MAYBE_PROP ||
213 like_votes--;
214 }
215 }
216 } else {
217 if (row->pitch_decision == PITCH_DEF_FIXED) {
218 other_votes += textord_words_veto_power;
219 } else if (row->pitch_decision == PITCH_MAYBE_FIXED ||
221 other_votes++;
222 } else if (row->pitch_decision == PITCH_DEF_PROP) {
223 other_votes -= textord_words_veto_power;
224 } else if (row->pitch_decision == PITCH_MAYBE_PROP ||
226 other_votes--;
227 }
228 }
229 row_index++;
230 }
231 block_index++;
232 }
233 if (block_votes > textord_words_veto_power) {
234 bad_row->fixed_pitch = block_stats.ile(0.5);
236 } else if (block_votes <= textord_words_veto_power && like_votes > 0) {
237 bad_row->fixed_pitch = like_stats.ile(0.5);
239 } else {
241 if (block_votes == 0 && like_votes == 0 && other_votes > 0 &&
243 tprintf(
244 "Warning:row %d of block %d set prop with no like rows against "
245 "trend\n",
246 row_target, block_target);
247 }
248 }
249 }
251 tprintf(":b_votes=%d:l_votes=%d:o_votes=%d", block_votes, like_votes, other_votes);
252 tprintf("x=%g:asc=%g\n", bad_row->xheight, bad_row->ascrise);
253 }
254 if (bad_row->pitch_decision == PITCH_CORR_FIXED) {
255 if (bad_row->fixed_pitch < textord_min_xheight) {
256 if (block_votes > 0) {
257 bad_row->fixed_pitch = block_stats.ile(0.5);
258 } else if (block_votes == 0 && like_votes > 0) {
259 bad_row->fixed_pitch = like_stats.ile(0.5);
260 } else {
261 tprintf("Warning:guessing pitch as xheight on row %d, block %d\n", row_target,
262 block_target);
263 bad_row->fixed_pitch = bad_row->xheight;
264 }
265 }
266 if (bad_row->fixed_pitch < textord_min_xheight) {
267 bad_row->fixed_pitch = (float)textord_min_xheight;
268 }
269 bad_row->kern_size = bad_row->fixed_pitch / 4;
270 bad_row->min_space = static_cast<int32_t>(bad_row->fixed_pitch * 0.6);
271 bad_row->max_nonspace = static_cast<int32_t>(bad_row->fixed_pitch * 0.4);
272 bad_row->space_threshold = (bad_row->min_space + bad_row->max_nonspace) / 2;
273 bad_row->space_size = bad_row->fixed_pitch;
274 if (bad_row->char_cells.empty() && !bad_row->blob_list()->empty()) {
275 tune_row_pitch(bad_row, &bad_row->projection, bad_row->projection_left,
276 bad_row->projection_right,
277 (bad_row->fixed_pitch + bad_row->max_nonspace * 3) / 4, bad_row->fixed_pitch,
278 sp_sd, mid_cuts, &bad_row->char_cells, false);
279 }
280 } else if (bad_row->pitch_decision == PITCH_CORR_PROP ||
281 bad_row->pitch_decision == PITCH_DEF_PROP) {
282 bad_row->fixed_pitch = 0.0f;
283 bad_row->char_cells.clear();
284 }
285}
286
287/**********************************************************************
288 * compute_block_pitch
289 *
290 * Decide whether each block is fixed pitch individually.
291 **********************************************************************/
292
293void compute_block_pitch(TO_BLOCK *block, // input list
294 FCOORD rotation, // for drawing
295 int32_t block_index, // block number
296 bool testing_on) { // correct orientation
297 TBOX block_box; // bounding box
298
299 block_box = block->block->pdblk.bounding_box();
300 if (testing_on && textord_debug_pitch_test) {
301 tprintf("Block %d at (%d,%d)->(%d,%d)\n", block_index, block_box.left(), block_box.bottom(),
302 block_box.right(), block_box.top());
303 }
304 block->min_space = static_cast<int32_t>(floor(block->xheight * textord_words_default_minspace));
305 block->max_nonspace = static_cast<int32_t>(ceil(block->xheight * textord_words_default_nonspace));
306 block->fixed_pitch = 0.0f;
307 block->space_size = static_cast<float>(block->min_space);
308 block->kern_size = static_cast<float>(block->max_nonspace);
311 if (!block->get_rows()->empty()) {
312 ASSERT_HOST(block->xheight > 0);
314#ifndef GRAPHICS_DISABLED
315 if (textord_show_initial_words && testing_on) {
316 // overlap_picture_ops(true);
318 }
319#endif
320 compute_rows_pitch(block, block_index, textord_debug_pitch_test && testing_on);
321 }
322}
323
324/**********************************************************************
325 * compute_rows_pitch
326 *
327 * Decide whether each row is fixed pitch individually.
328 **********************************************************************/
329
330bool compute_rows_pitch( // find line stats
331 TO_BLOCK *block, // block to do
332 int32_t block_index, // block number
333 bool testing_on // correct orientation
334) {
335 int32_t maxwidth; // of spaces
336 TO_ROW *row; // current row
337 int32_t row_index; // row number.
338 float lower, upper; // cluster thresholds
339 TO_ROW_IT row_it = block->get_rows();
340
341 row_index = 1;
342 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
343 row = row_it.data();
344 ASSERT_HOST(row->xheight > 0);
346 maxwidth = static_cast<int32_t>(ceil(row->xheight * textord_words_maxspace));
347 if (row_pitch_stats(row, maxwidth, testing_on) &&
348 find_row_pitch(row, maxwidth, textord_dotmatrix_gap + 1, block, block_index, row_index,
349 testing_on)) {
350 if (row->fixed_pitch == 0) {
351 lower = row->pr_nonsp;
352 upper = row->pr_space;
353 row->space_size = upper;
354 row->kern_size = lower;
355 }
356 } else {
357 row->fixed_pitch = 0.0f; // insufficient data
359 }
360 row_index++;
361 }
362 return false;
363}
364
365/**********************************************************************
366 * try_doc_fixed
367 *
368 * Attempt to call the entire document fixed pitch.
369 **********************************************************************/
370
371bool try_doc_fixed( // determine pitch
372 ICOORD page_tr, // top right
373 TO_BLOCK_LIST *port_blocks, // input list
374 float gradient // page skew
375) {
376 int16_t master_x; // uniform shifts
377 int16_t pitch; // median pitch.
378 int x; // profile coord
379 int prop_blocks; // correct counts
380 int fixed_blocks;
381 int total_row_count; // total in page
382 // iterator
383 TO_BLOCK_IT block_it = port_blocks;
384 TO_BLOCK *block; // current block;
385 TO_ROW *row; // current row
386 int16_t projection_left; // edges
387 int16_t projection_right;
388 int16_t row_left; // edges of row
389 int16_t row_right;
390 float master_y; // uniform shifts
391 float shift_factor; // page skew correction
392 float final_pitch; // output pitch
393 float row_y; // baseline
394 STATS projection; // entire page
395 STATS pitches(0, MAX_ALLOWED_PITCH - 1);
396 // for median
397 float sp_sd; // space sd
398 int16_t mid_cuts; // no of cheap cuts
399 float pitch_sd; // sync rating
400
402 block_it.empty() || block_it.data()->get_rows()->empty()) {
403 return false;
404 }
405 shift_factor = gradient / (gradient * gradient + 1);
406 // row iterator
407 TO_ROW_IT row_it(block_it.data()->get_rows());
408 master_x = row_it.data()->projection_left;
409 master_y = row_it.data()->baseline.y(master_x);
410 projection_left = INT16_MAX;
411 projection_right = -INT16_MAX;
412 prop_blocks = 0;
413 fixed_blocks = 0;
414 total_row_count = 0;
415
416 for (block_it.mark_cycle_pt(); !block_it.cycled_list(); block_it.forward()) {
417 block = block_it.data();
418 row_it.set_to_list(block->get_rows());
419 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
420 row = row_it.data();
421 total_row_count++;
422 if (row->fixed_pitch > 0) {
423 pitches.add(static_cast<int32_t>(row->fixed_pitch), 1);
424 }
425 // find median
426 row_y = row->baseline.y(master_x);
427 row_left = static_cast<int16_t>(row->projection_left - shift_factor * (master_y - row_y));
428 row_right = static_cast<int16_t>(row->projection_right - shift_factor * (master_y - row_y));
429 if (row_left < projection_left) {
430 projection_left = row_left;
431 }
432 if (row_right > projection_right) {
433 projection_right = row_right;
434 }
435 }
436 }
437 if (pitches.get_total() == 0) {
438 return false;
439 }
440 projection.set_range(projection_left, projection_right - 1);
441
442 for (block_it.mark_cycle_pt(); !block_it.cycled_list(); block_it.forward()) {
443 block = block_it.data();
444 row_it.set_to_list(block->get_rows());
445 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
446 row = row_it.data();
447 row_y = row->baseline.y(master_x);
448 row_left = static_cast<int16_t>(row->projection_left - shift_factor * (master_y - row_y));
449 for (x = row->projection_left; x < row->projection_right; x++, row_left++) {
450 projection.add(row_left, row->projection.pile_count(x));
451 }
452 }
453 }
454
455 row_it.set_to_list(block_it.data()->get_rows());
456 row = row_it.data();
457#ifndef GRAPHICS_DISABLED
458 if (textord_show_page_cuts && to_win != nullptr) {
459 projection.plot(to_win, projection_left, row->intercept(), 1.0f, -1.0f, ScrollView::CORAL);
460 }
461#endif
462 final_pitch = pitches.ile(0.5);
463 pitch = static_cast<int16_t>(final_pitch);
464 pitch_sd = tune_row_pitch(row, &projection, projection_left, projection_right, pitch * 0.75,
465 final_pitch, sp_sd, mid_cuts, &row->char_cells, false);
466
468 tprintf(
469 "try_doc:props=%d:fixed=%d:pitch=%d:final_pitch=%g:pitch_sd=%g:sp_sd=%"
470 "g:sd/trc=%g:sd/p=%g:sd/trc/p=%g\n",
471 prop_blocks, fixed_blocks, pitch, final_pitch, pitch_sd, sp_sd, pitch_sd / total_row_count,
472 pitch_sd / pitch, pitch_sd / total_row_count / pitch);
473 }
474
475#ifndef GRAPHICS_DISABLED
476 if (textord_show_page_cuts && to_win != nullptr) {
477 float row_shift; // shift for row
478 ICOORDELT_LIST *master_cells; // cells for page
479 master_cells = &row->char_cells;
480 for (block_it.mark_cycle_pt(); !block_it.cycled_list(); block_it.forward()) {
481 block = block_it.data();
482 row_it.set_to_list(block->get_rows());
483 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
484 row = row_it.data();
485 row_y = row->baseline.y(master_x);
486 row_shift = shift_factor * (master_y - row_y);
487 plot_row_cells(to_win, ScrollView::GOLDENROD, row, row_shift, master_cells);
488 }
489 }
490 }
491#endif
492 row->char_cells.clear();
493 return false;
494}
495
496/**********************************************************************
497 * try_block_fixed
498 *
499 * Try to call the entire block fixed.
500 **********************************************************************/
501
502bool try_block_fixed( // find line stats
503 TO_BLOCK *block, // block to do
504 int32_t block_index // block number
505) {
506 return false;
507}
508
509/**********************************************************************
510 * try_rows_fixed
511 *
512 * Decide whether each row is fixed pitch individually.
513 **********************************************************************/
514
515bool try_rows_fixed( // find line stats
516 TO_BLOCK *block, // block to do
517 int32_t block_index, // block number
518 bool testing_on // correct orientation
519) {
520 TO_ROW *row; // current row
521 int32_t row_index; // row number.
522 int32_t def_fixed = 0; // counters
523 int32_t def_prop = 0;
524 int32_t maybe_fixed = 0;
525 int32_t maybe_prop = 0;
526 int32_t dunno = 0;
527 int32_t corr_fixed = 0;
528 int32_t corr_prop = 0;
529 float lower, upper; // cluster thresholds
530 TO_ROW_IT row_it = block->get_rows();
531
532 row_index = 1;
533 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
534 row = row_it.data();
535 ASSERT_HOST(row->xheight > 0);
536 if (row->fixed_pitch > 0 && fixed_pitch_row(row, block->block, block_index)) {
537 if (row->fixed_pitch == 0) {
538 lower = row->pr_nonsp;
539 upper = row->pr_space;
540 row->space_size = upper;
541 row->kern_size = lower;
542 }
543 }
544 row_index++;
545 }
546 count_block_votes(block, def_fixed, def_prop, maybe_fixed, maybe_prop, corr_fixed, corr_prop,
547 dunno);
548 if (testing_on &&
550 tprintf("Initially:");
551 print_block_counts(block, block_index);
552 }
553 if (def_fixed > def_prop * textord_words_veto_power) {
555 } else if (def_prop > def_fixed * textord_words_veto_power) {
557 } else if (def_fixed > 0 || def_prop > 0) {
559 } else if (maybe_fixed > maybe_prop * textord_words_veto_power) {
561 } else if (maybe_prop > maybe_fixed * textord_words_veto_power) {
563 } else {
565 }
566 return false;
567}
568
569/**********************************************************************
570 * print_block_counts
571 *
572 * Count up how many rows have what decision and print the results.
573 **********************************************************************/
574
575void print_block_counts( // find line stats
576 TO_BLOCK *block, // block to do
577 int32_t block_index // block number
578) {
579 int32_t def_fixed = 0; // counters
580 int32_t def_prop = 0;
581 int32_t maybe_fixed = 0;
582 int32_t maybe_prop = 0;
583 int32_t dunno = 0;
584 int32_t corr_fixed = 0;
585 int32_t corr_prop = 0;
586
587 count_block_votes(block, def_fixed, def_prop, maybe_fixed, maybe_prop, corr_fixed, corr_prop,
588 dunno);
589 tprintf("Block %d has (%d,%d,%d)", block_index, def_fixed, maybe_fixed, corr_fixed);
590 if (textord_blocksall_prop && (def_fixed || maybe_fixed || corr_fixed)) {
591 tprintf(" (Wrongly)");
592 }
593 tprintf(" fixed, (%d,%d,%d)", def_prop, maybe_prop, corr_prop);
594 if (textord_blocksall_fixed && (def_prop || maybe_prop || corr_prop)) {
595 tprintf(" (Wrongly)");
596 }
597 tprintf(" prop, %d dunno\n", dunno);
598}
599
600/**********************************************************************
601 * count_block_votes
602 *
603 * Count the number of rows in the block with each kind of pitch_decision.
604 **********************************************************************/
605
606void count_block_votes( // find line stats
607 TO_BLOCK *block, // block to do
608 int32_t &def_fixed, // add to counts
609 int32_t &def_prop, int32_t &maybe_fixed, int32_t &maybe_prop, int32_t &corr_fixed,
610 int32_t &corr_prop, int32_t &dunno) {
611 TO_ROW *row; // current row
612 TO_ROW_IT row_it = block->get_rows();
613
614 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
615 row = row_it.data();
616 switch (row->pitch_decision) {
617 case PITCH_DUNNO:
618 dunno++;
619 break;
620 case PITCH_DEF_PROP:
621 def_prop++;
622 break;
623 case PITCH_MAYBE_PROP:
624 maybe_prop++;
625 break;
626 case PITCH_DEF_FIXED:
627 def_fixed++;
628 break;
630 maybe_fixed++;
631 break;
632 case PITCH_CORR_PROP:
633 corr_prop++;
634 break;
635 case PITCH_CORR_FIXED:
636 corr_fixed++;
637 break;
638 }
639 }
640}
641
642/**********************************************************************
643 * row_pitch_stats
644 *
645 * Decide whether each row is fixed pitch individually.
646 **********************************************************************/
647
648bool row_pitch_stats( // find line stats
649 TO_ROW *row, // current row
650 int32_t maxwidth, // of spaces
651 bool testing_on // correct orientation
652) {
653 BLOBNBOX *blob; // current blob
654 int gap_index; // current gap
655 int32_t prev_x; // end of prev blob
656 int32_t cluster_count; // no of clusters
657 int32_t prev_count; // of clusters
658 int32_t smooth_factor; // for smoothing stats
659 TBOX blob_box; // bounding box
660 float lower, upper; // cluster thresholds
661 // gap sizes
662 float gaps[BLOCK_STATS_CLUSTERS];
663 // blobs
664 BLOBNBOX_IT blob_it = row->blob_list();
665 STATS gap_stats(0, maxwidth - 1);
666 STATS cluster_stats[BLOCK_STATS_CLUSTERS + 1];
667 // clusters
668
669 smooth_factor = static_cast<int32_t>(row->xheight * textord_wordstats_smooth_factor + 1.5);
670 if (!blob_it.empty()) {
671 prev_x = blob_it.data()->bounding_box().right();
672 blob_it.forward();
673 while (!blob_it.at_first()) {
674 blob = blob_it.data();
675 if (!blob->joined_to_prev()) {
676 blob_box = blob->bounding_box();
677 if (blob_box.left() - prev_x < maxwidth) {
678 gap_stats.add(blob_box.left() - prev_x, 1);
679 }
680 prev_x = blob_box.right();
681 }
682 blob_it.forward();
683 }
684 }
685 if (gap_stats.get_total() == 0) {
686 return false;
687 }
688 cluster_count = 0;
689 lower = row->xheight * words_initial_lower;
690 upper = row->xheight * words_initial_upper;
691 gap_stats.smooth(smooth_factor);
692 do {
693 prev_count = cluster_count;
694 cluster_count = gap_stats.cluster(lower, upper, textord_spacesize_ratioprop,
695 BLOCK_STATS_CLUSTERS, cluster_stats);
696 } while (cluster_count > prev_count && cluster_count < BLOCK_STATS_CLUSTERS);
697 if (cluster_count < 1) {
698 return false;
699 }
700 for (gap_index = 0; gap_index < cluster_count; gap_index++) {
701 gaps[gap_index] = cluster_stats[gap_index + 1].ile(0.5);
702 }
703 // get medians
704 if (testing_on) {
705 tprintf("cluster_count=%d:", cluster_count);
706 for (gap_index = 0; gap_index < cluster_count; gap_index++) {
707 tprintf(" %g(%d)", gaps[gap_index], cluster_stats[gap_index + 1].get_total());
708 }
709 tprintf("\n");
710 }
711 qsort(gaps, cluster_count, sizeof(float), sort_floats);
712
713 // Try to find proportional non-space and space for row.
715 upper = row->xheight * textord_words_min_minspace;
716 for (gap_index = 0; gap_index < cluster_count && gaps[gap_index] < lower; gap_index++) {
717 ;
718 }
719 if (gap_index == 0) {
720 if (testing_on) {
721 tprintf("No clusters below nonspace threshold!!\n");
722 }
723 if (cluster_count > 1) {
724 row->pr_nonsp = gaps[0];
725 row->pr_space = gaps[1];
726 } else {
727 row->pr_nonsp = lower;
728 row->pr_space = gaps[0];
729 }
730 } else {
731 row->pr_nonsp = gaps[gap_index - 1];
732 while (gap_index < cluster_count && gaps[gap_index] < upper) {
733 gap_index++;
734 }
735 if (gap_index == cluster_count) {
736 if (testing_on) {
737 tprintf("No clusters above nonspace threshold!!\n");
738 }
740 } else {
741 row->pr_space = gaps[gap_index];
742 }
743 }
744
745 // Now try to find the fixed pitch space and non-space.
746 upper = row->xheight * words_default_fixed_space;
747 for (gap_index = 0; gap_index < cluster_count && gaps[gap_index] < upper; gap_index++) {
748 ;
749 }
750 if (gap_index == 0) {
751 if (testing_on) {
752 tprintf("No clusters below space threshold!!\n");
753 }
754 row->fp_nonsp = upper;
755 row->fp_space = gaps[0];
756 } else {
757 row->fp_nonsp = gaps[gap_index - 1];
758 if (gap_index == cluster_count) {
759 if (testing_on) {
760 tprintf("No clusters above space threshold!!\n");
761 }
762 row->fp_space = row->xheight;
763 } else {
764 row->fp_space = gaps[gap_index];
765 }
766 }
767 if (testing_on) {
768 tprintf(
769 "Initial estimates:pr_nonsp=%g, pr_space=%g, fp_nonsp=%g, "
770 "fp_space=%g\n",
771 row->pr_nonsp, row->pr_space, row->fp_nonsp, row->fp_space);
772 }
773 return true; // computed some stats
774}
775
776/**********************************************************************
777 * find_row_pitch
778 *
779 * Check to see if this row could be fixed pitch using the given spacings.
780 * Blobs with gaps smaller than the lower threshold are assumed to be one.
781 * The larger threshold is the word gap threshold.
782 **********************************************************************/
783
784bool find_row_pitch( // find lines
785 TO_ROW *row, // row to do
786 int32_t maxwidth, // max permitted space
787 int32_t dm_gap, // ignorable gaps
788 TO_BLOCK *block, // block of row
789 int32_t block_index, // block_number
790 int32_t row_index, // number of row
791 bool testing_on // correct orientation
792) {
793 bool used_dm_model; // looks like dot matrix
794 float min_space; // estimate threshold
795 float non_space; // gap size
796 float gap_iqr; // interquartile range
797 float pitch_iqr;
798 float dm_gap_iqr; // interquartile range
799 float dm_pitch_iqr;
800 float dm_pitch; // pitch with dm on
801 float pitch; // revised estimate
802 float initial_pitch; // guess at pitch
803 STATS gap_stats(0, maxwidth - 1);
804 // centre-centre
805 STATS pitch_stats(0, maxwidth - 1);
806
807 row->fixed_pitch = 0.0f;
808 initial_pitch = row->fp_space;
809 if (initial_pitch > row->xheight * (1 + words_default_fixed_limit)) {
810 initial_pitch = row->xheight; // keep pitch decent
811 }
812 non_space = row->fp_nonsp;
813 if (non_space > initial_pitch) {
814 non_space = initial_pitch;
815 }
816 min_space = (initial_pitch + non_space) / 2;
817
818 if (!count_pitch_stats(row, &gap_stats, &pitch_stats, initial_pitch, min_space, true, false,
819 dm_gap)) {
820 dm_gap_iqr = 0.0001f;
821 dm_pitch_iqr = maxwidth * 2.0f;
822 dm_pitch = initial_pitch;
823 } else {
824 dm_gap_iqr = gap_stats.ile(0.75) - gap_stats.ile(0.25);
825 dm_pitch_iqr = pitch_stats.ile(0.75) - pitch_stats.ile(0.25);
826 dm_pitch = pitch_stats.ile(0.5);
827 }
828 gap_stats.clear();
829 pitch_stats.clear();
830 if (!count_pitch_stats(row, &gap_stats, &pitch_stats, initial_pitch, min_space, true, false, 0)) {
831 gap_iqr = 0.0001f;
832 pitch_iqr = maxwidth * 3.0f;
833 } else {
834 gap_iqr = gap_stats.ile(0.75) - gap_stats.ile(0.25);
835 pitch_iqr = pitch_stats.ile(0.75) - pitch_stats.ile(0.25);
836 if (testing_on) {
837 tprintf(
838 "First fp iteration:initial_pitch=%g, gap_iqr=%g, pitch_iqr=%g, "
839 "pitch=%g\n",
840 initial_pitch, gap_iqr, pitch_iqr, pitch_stats.ile(0.5));
841 }
842 initial_pitch = pitch_stats.ile(0.5);
843 if (min_space > initial_pitch && count_pitch_stats(row, &gap_stats, &pitch_stats, initial_pitch,
844 initial_pitch, true, false, 0)) {
845 min_space = initial_pitch;
846 gap_iqr = gap_stats.ile(0.75) - gap_stats.ile(0.25);
847 pitch_iqr = pitch_stats.ile(0.75) - pitch_stats.ile(0.25);
848 if (testing_on) {
849 tprintf(
850 "Revised fp iteration:initial_pitch=%g, gap_iqr=%g, pitch_iqr=%g, "
851 "pitch=%g\n",
852 initial_pitch, gap_iqr, pitch_iqr, pitch_stats.ile(0.5));
853 }
854 initial_pitch = pitch_stats.ile(0.5);
855 }
856 }
858 tprintf("Blk=%d:Row=%d:%c:p_iqr=%g:g_iqr=%g:dm_p_iqr=%g:dm_g_iqr=%g:%c:", block_index,
859 row_index, 'X', pitch_iqr, gap_iqr, dm_pitch_iqr, dm_gap_iqr,
860 pitch_iqr > maxwidth && dm_pitch_iqr > maxwidth
861 ? 'D'
862 : (pitch_iqr * dm_gap_iqr <= dm_pitch_iqr * gap_iqr ? 'S' : 'M'));
863 }
864 if (pitch_iqr > maxwidth && dm_pitch_iqr > maxwidth) {
867 tprintf("\n");
868 }
869 return false; // insufficient data
870 }
871 if (pitch_iqr * dm_gap_iqr <= dm_pitch_iqr * gap_iqr) {
872 if (testing_on) {
873 tprintf(
874 "Choosing non dm version:pitch_iqr=%g, gap_iqr=%g, dm_pitch_iqr=%g, "
875 "dm_gap_iqr=%g\n",
876 pitch_iqr, gap_iqr, dm_pitch_iqr, dm_gap_iqr);
877 }
878 gap_iqr = gap_stats.ile(0.75) - gap_stats.ile(0.25);
879 pitch_iqr = pitch_stats.ile(0.75) - pitch_stats.ile(0.25);
880 pitch = pitch_stats.ile(0.5);
881 used_dm_model = false;
882 } else {
883 if (testing_on) {
884 tprintf(
885 "Choosing dm version:pitch_iqr=%g, gap_iqr=%g, dm_pitch_iqr=%g, "
886 "dm_gap_iqr=%g\n",
887 pitch_iqr, gap_iqr, dm_pitch_iqr, dm_gap_iqr);
888 }
889 gap_iqr = dm_gap_iqr;
890 pitch_iqr = dm_pitch_iqr;
891 pitch = dm_pitch;
892 used_dm_model = true;
893 }
895 tprintf("rev_p_iqr=%g:rev_g_iqr=%g:pitch=%g:", pitch_iqr, gap_iqr, pitch);
896 tprintf("p_iqr/g=%g:p_iqr/x=%g:iqr_res=%c:", pitch_iqr / gap_iqr, pitch_iqr / block->xheight,
897 pitch_iqr < gap_iqr * textord_fpiqr_ratio &&
898 pitch_iqr < block->xheight * textord_max_pitch_iqr &&
899 pitch < block->xheight * textord_words_default_maxspace
900 ? 'F'
901 : 'P');
902 }
903 if (pitch_iqr < gap_iqr * textord_fpiqr_ratio &&
904 pitch_iqr < block->xheight * textord_max_pitch_iqr &&
905 pitch < block->xheight * textord_words_default_maxspace) {
907 } else {
909 }
910 row->fixed_pitch = pitch;
911 row->kern_size = gap_stats.ile(0.5);
912 row->min_space = static_cast<int32_t>(row->fixed_pitch + non_space) / 2;
913 if (row->min_space > row->fixed_pitch) {
914 row->min_space = static_cast<int32_t>(row->fixed_pitch);
915 }
916 row->max_nonspace = row->min_space;
917 row->space_size = row->fixed_pitch;
918 row->space_threshold = (row->max_nonspace + row->min_space) / 2;
919 row->used_dm_model = used_dm_model;
920 return true;
921}
922
923/**********************************************************************
924 * fixed_pitch_row
925 *
926 * Check to see if this row could be fixed pitch using the given spacings.
927 * Blobs with gaps smaller than the lower threshold are assumed to be one.
928 * The larger threshold is the word gap threshold.
929 **********************************************************************/
930
931bool fixed_pitch_row(TO_ROW *row, // row to do
932 BLOCK *block,
933 int32_t block_index // block_number
934) {
935 const char *res_string; // pitch result
936 int16_t mid_cuts; // no of cheap cuts
937 float non_space; // gap size
938 float pitch_sd; // error on pitch
939 float sp_sd = 0.0f; // space sd
940
941 non_space = row->fp_nonsp;
942 if (non_space > row->fixed_pitch) {
943 non_space = row->fixed_pitch;
944 }
945 POLY_BLOCK *pb = block != nullptr ? block->pdblk.poly_block() : nullptr;
946 if (textord_all_prop || (pb != nullptr && !pb->IsText())) {
947 // Set the decision to definitely proportional.
948 pitch_sd = textord_words_def_prop * row->fixed_pitch;
950 } else {
951 pitch_sd = tune_row_pitch(row, &row->projection, row->projection_left, row->projection_right,
952 (row->fixed_pitch + non_space * 3) / 4, row->fixed_pitch, sp_sd,
953 mid_cuts, &row->char_cells, block_index == textord_debug_block);
954 if (pitch_sd < textord_words_pitchsd_threshold * row->fixed_pitch &&
955 ((pitsync_linear_version & 3) < 3 ||
956 ((pitsync_linear_version & 3) >= 3 &&
957 (row->used_dm_model || sp_sd > 20 || (pitch_sd == 0 && sp_sd > 10))))) {
958 if (pitch_sd < textord_words_def_fixed * row->fixed_pitch && !row->all_caps &&
959 ((pitsync_linear_version & 3) < 3 || sp_sd > 20)) {
961 } else {
963 }
964 } else if ((pitsync_linear_version & 3) < 3 || sp_sd > 20 || mid_cuts > 0 ||
965 pitch_sd >= textord_words_pitchsd_threshold * row->fixed_pitch) {
966 if (pitch_sd < textord_words_def_prop * row->fixed_pitch) {
968 } else {
970 }
971 } else {
973 }
974 }
975
977 res_string = "??";
978 switch (row->pitch_decision) {
979 case PITCH_DEF_PROP:
980 res_string = "DP";
981 break;
982 case PITCH_MAYBE_PROP:
983 res_string = "MP";
984 break;
985 case PITCH_DEF_FIXED:
986 res_string = "DF";
987 break;
989 res_string = "MF";
990 break;
991 default:
992 res_string = "??";
993 }
994 tprintf(":sd/p=%g:occ=%g:init_res=%s\n", pitch_sd / row->fixed_pitch, sp_sd, res_string);
995 }
996 return true;
997}
998
999/**********************************************************************
1000 * count_pitch_stats
1001 *
1002 * Count up the gap and pitch stats on the block to see if it is fixed pitch.
1003 * Blobs with gaps smaller than the lower threshold are assumed to be one.
1004 * The larger threshold is the word gap threshold.
1005 * The return value indicates whether there were any decent values to use.
1006 **********************************************************************/
1007
1008bool count_pitch_stats( // find lines
1009 TO_ROW *row, // row to do
1010 STATS *gap_stats, // blob gaps
1011 STATS *pitch_stats, // centre-centre stats
1012 float initial_pitch, // guess at pitch
1013 float min_space, // estimate space size
1014 bool ignore_outsize, // discard big objects
1015 bool split_outsize, // split big objects
1016 int32_t dm_gap // ignorable gaps
1017) {
1018 bool prev_valid; // not word broken
1019 BLOBNBOX *blob; // current blob
1020 // blobs
1021 BLOBNBOX_IT blob_it = row->blob_list();
1022 int32_t prev_right; // end of prev blob
1023 int32_t prev_centre; // centre of previous blob
1024 int32_t x_centre; // centre of this blob
1025 int32_t blob_width; // width of blob
1026 int32_t width_units; // no of widths in blob
1027 float width; // blob width
1028 TBOX blob_box; // bounding box
1029 TBOX joined_box; // of super blob
1030
1031 gap_stats->clear();
1032 pitch_stats->clear();
1033 if (blob_it.empty()) {
1034 return false;
1035 }
1036 prev_valid = false;
1037 prev_centre = 0;
1038 prev_right = 0; // stop compiler warning
1039 joined_box = blob_it.data()->bounding_box();
1040 do {
1041 blob_it.forward();
1042 blob = blob_it.data();
1043 if (!blob->joined_to_prev()) {
1044 blob_box = blob->bounding_box();
1045 if ((blob_box.left() - joined_box.right() < dm_gap && !blob_it.at_first()) ||
1046 blob->cblob() == nullptr) {
1047 joined_box += blob_box; // merge blobs
1048 } else {
1049 blob_width = joined_box.width();
1050 if (split_outsize) {
1051 width_units =
1052 static_cast<int32_t>(floor(static_cast<float>(blob_width) / initial_pitch + 0.5));
1053 if (width_units < 1) {
1054 width_units = 1;
1055 }
1056 width_units--;
1057 } else if (ignore_outsize) {
1058 width = static_cast<float>(blob_width) / initial_pitch;
1059 width_units =
1060 width < 1 + words_default_fixed_limit && width > 1 - words_default_fixed_limit ? 0
1061 : -1;
1062 } else {
1063 width_units = 0; // everything in
1064 }
1065 x_centre = static_cast<int32_t>(joined_box.left() +
1066 (blob_width - width_units * initial_pitch) / 2);
1067 if (prev_valid && width_units >= 0) {
1068 // if (width_units>0)
1069 // {
1070 // tprintf("wu=%d,
1071 // width=%d,
1072 // xc=%d, adding
1073 // %d\n",
1074 // width_units,blob_width,x_centre,x_centre-prev_centre);
1075 // }
1076 gap_stats->add(joined_box.left() - prev_right, 1);
1077 pitch_stats->add(x_centre - prev_centre, 1);
1078 }
1079 prev_centre = static_cast<int32_t>(x_centre + width_units * initial_pitch);
1080 prev_right = joined_box.right();
1081 prev_valid = blob_box.left() - joined_box.right() < min_space;
1082 prev_valid = prev_valid && width_units >= 0;
1083 joined_box = blob_box;
1084 }
1085 }
1086 } while (!blob_it.at_first());
1087 return gap_stats->get_total() >= 3;
1088}
1089
1090/**********************************************************************
1091 * tune_row_pitch
1092 *
1093 * Use a dp algorithm to fit the character cells and return the sd of
1094 * the cell size over the row.
1095 **********************************************************************/
1096
1097float tune_row_pitch( // find fp cells
1098 TO_ROW *row, // row to do
1099 STATS *projection, // vertical projection
1100 int16_t projection_left, // edge of projection
1101 int16_t projection_right, // edge of projection
1102 float space_size, // size of blank
1103 float &initial_pitch, // guess at pitch
1104 float &best_sp_sd, // space sd
1105 int16_t &best_mid_cuts, // no of cheap cuts
1106 ICOORDELT_LIST *best_cells, // row cells
1107 bool testing_on // individual words
1108) {
1109 int pitch_delta; // offset pitch
1110 int16_t mid_cuts; // cheap cuts
1111 float pitch_sd; // current sd
1112 float best_sd; // best result
1113 float best_pitch; // pitch for best result
1114 float initial_sd; // starting error
1115 float sp_sd; // space sd
1116 ICOORDELT_LIST test_cells; // row cells
1117 ICOORDELT_IT best_it; // start of best list
1118
1120 return tune_row_pitch2(row, projection, projection_left, projection_right, space_size,
1121 initial_pitch, best_sp_sd,
1122 // space sd
1123 best_mid_cuts, best_cells, testing_on);
1124 }
1125 if (textord_disable_pitch_test) {
1126 best_sp_sd = initial_pitch;
1127 return initial_pitch;
1128 }
1129 initial_sd = compute_pitch_sd(row, projection, projection_left, projection_right, space_size,
1130 initial_pitch, best_sp_sd, best_mid_cuts, best_cells, testing_on);
1131 best_sd = initial_sd;
1132 best_pitch = initial_pitch;
1133 if (testing_on) {
1134 tprintf("tune_row_pitch:start pitch=%g, sd=%g\n", best_pitch, best_sd);
1135 }
1136 for (pitch_delta = 1; pitch_delta <= textord_pitch_range; pitch_delta++) {
1137 pitch_sd =
1138 compute_pitch_sd(row, projection, projection_left, projection_right, space_size,
1139 initial_pitch + pitch_delta, sp_sd, mid_cuts, &test_cells, testing_on);
1140 if (testing_on) {
1141 tprintf("testing pitch at %g, sd=%g\n", initial_pitch + pitch_delta, pitch_sd);
1142 }
1143 if (pitch_sd < best_sd) {
1144 best_sd = pitch_sd;
1145 best_mid_cuts = mid_cuts;
1146 best_sp_sd = sp_sd;
1147 best_pitch = initial_pitch + pitch_delta;
1148 best_cells->clear();
1149 best_it.set_to_list(best_cells);
1150 best_it.add_list_after(&test_cells);
1151 } else {
1152 test_cells.clear();
1153 }
1154 if (pitch_sd > initial_sd) {
1155 break; // getting worse
1156 }
1157 }
1158 for (pitch_delta = 1; pitch_delta <= textord_pitch_range; pitch_delta++) {
1159 pitch_sd =
1160 compute_pitch_sd(row, projection, projection_left, projection_right, space_size,
1161 initial_pitch - pitch_delta, sp_sd, mid_cuts, &test_cells, testing_on);
1162 if (testing_on) {
1163 tprintf("testing pitch at %g, sd=%g\n", initial_pitch - pitch_delta, pitch_sd);
1164 }
1165 if (pitch_sd < best_sd) {
1166 best_sd = pitch_sd;
1167 best_mid_cuts = mid_cuts;
1168 best_sp_sd = sp_sd;
1169 best_pitch = initial_pitch - pitch_delta;
1170 best_cells->clear();
1171 best_it.set_to_list(best_cells);
1172 best_it.add_list_after(&test_cells);
1173 } else {
1174 test_cells.clear();
1175 }
1176 if (pitch_sd > initial_sd) {
1177 break;
1178 }
1179 }
1180 initial_pitch = best_pitch;
1181
1183 print_pitch_sd(row, projection, projection_left, projection_right, space_size, best_pitch);
1184 }
1185
1186 return best_sd;
1187}
1188
1189/**********************************************************************
1190 * tune_row_pitch
1191 *
1192 * Use a dp algorithm to fit the character cells and return the sd of
1193 * the cell size over the row.
1194 **********************************************************************/
1195
1196float tune_row_pitch2( // find fp cells
1197 TO_ROW *row, // row to do
1198 STATS *projection, // vertical projection
1199 int16_t projection_left, // edge of projection
1200 int16_t projection_right, // edge of projection
1201 float space_size, // size of blank
1202 float &initial_pitch, // guess at pitch
1203 float &best_sp_sd, // space sd
1204 int16_t &best_mid_cuts, // no of cheap cuts
1205 ICOORDELT_LIST *best_cells, // row cells
1206 bool testing_on // individual words
1207) {
1208 int pitch_delta; // offset pitch
1209 int16_t pixel; // pixel coord
1210 int16_t best_pixel; // pixel coord
1211 int16_t best_delta; // best pitch
1212 int16_t best_pitch; // best pitch
1213 int16_t start; // of good range
1214 int16_t end; // of good range
1215 int32_t best_count; // lowest sum
1216 float best_sd; // best result
1217
1218 best_sp_sd = initial_pitch;
1219
1220 best_pitch = static_cast<int>(initial_pitch);
1221 if (textord_disable_pitch_test || best_pitch <= textord_pitch_range) {
1222 return initial_pitch;
1223 }
1224 std::unique_ptr<STATS[]> sum_proj(new STATS[textord_pitch_range * 2 + 1]); // summed projection
1225
1226 for (pitch_delta = -textord_pitch_range; pitch_delta <= textord_pitch_range; pitch_delta++) {
1227 sum_proj[textord_pitch_range + pitch_delta].set_range(0, best_pitch + pitch_delta);
1228 }
1229 for (pixel = projection_left; pixel <= projection_right; pixel++) {
1230 for (pitch_delta = -textord_pitch_range; pitch_delta <= textord_pitch_range; pitch_delta++) {
1231 sum_proj[textord_pitch_range + pitch_delta].add(
1232 (pixel - projection_left) % (best_pitch + pitch_delta), projection->pile_count(pixel));
1233 }
1234 }
1235 best_count = sum_proj[textord_pitch_range].pile_count(0);
1236 best_delta = 0;
1237 best_pixel = 0;
1238 for (pitch_delta = -textord_pitch_range; pitch_delta <= textord_pitch_range; pitch_delta++) {
1239 for (pixel = 0; pixel < best_pitch + pitch_delta; pixel++) {
1240 if (sum_proj[textord_pitch_range + pitch_delta].pile_count(pixel) < best_count) {
1241 best_count = sum_proj[textord_pitch_range + pitch_delta].pile_count(pixel);
1242 best_delta = pitch_delta;
1243 best_pixel = pixel;
1244 }
1245 }
1246 }
1247 if (testing_on) {
1248 tprintf("tune_row_pitch:start pitch=%g, best_delta=%d, count=%d\n", initial_pitch, best_delta,
1249 best_count);
1250 }
1251 best_pitch += best_delta;
1252 initial_pitch = best_pitch;
1253 best_count++;
1254 best_count += best_count;
1255 for (start = best_pixel - 2;
1256 start > best_pixel - best_pitch &&
1257 sum_proj[textord_pitch_range + best_delta].pile_count(start % best_pitch) <= best_count;
1258 start--) {
1259 ;
1260 }
1261 for (end = best_pixel + 2;
1262 end < best_pixel + best_pitch &&
1263 sum_proj[textord_pitch_range + best_delta].pile_count(end % best_pitch) <= best_count;
1264 end++) {
1265 ;
1266 }
1267
1268 best_sd = compute_pitch_sd(row, projection, projection_left, projection_right, space_size,
1269 initial_pitch, best_sp_sd, best_mid_cuts, best_cells, testing_on,
1270 start, end);
1271 if (testing_on) {
1272 tprintf("tune_row_pitch:output pitch=%g, sd=%g\n", initial_pitch, best_sd);
1273 }
1274
1276 print_pitch_sd(row, projection, projection_left, projection_right, space_size, initial_pitch);
1277 }
1278
1279 return best_sd;
1280}
1281
1282/**********************************************************************
1283 * compute_pitch_sd
1284 *
1285 * Use a dp algorithm to fit the character cells and return the sd of
1286 * the cell size over the row.
1287 **********************************************************************/
1288
1289float compute_pitch_sd( // find fp cells
1290 TO_ROW *row, // row to do
1291 STATS *projection, // vertical projection
1292 int16_t projection_left, // edge
1293 int16_t projection_right, // edge
1294 float space_size, // size of blank
1295 float initial_pitch, // guess at pitch
1296 float &sp_sd, // space sd
1297 int16_t &mid_cuts, // no of free cuts
1298 ICOORDELT_LIST *row_cells, // list of chop pts
1299 bool testing_on, // individual words
1300 int16_t start, // start of good range
1301 int16_t end // end of good range
1302) {
1303 int16_t occupation; // no of cells in word.
1304 // blobs
1305 BLOBNBOX_IT blob_it = row->blob_list();
1306 BLOBNBOX_IT start_it; // start of word
1307 BLOBNBOX_IT plot_it; // for plotting
1308 int16_t blob_count; // no of blobs
1309 TBOX blob_box; // bounding box
1310 TBOX prev_box; // of super blob
1311 int32_t prev_right; // of word sync
1312 int scale_factor; // on scores for big words
1313 int32_t sp_count; // spaces
1314 FPSEGPT_LIST seg_list; // char cells
1315 FPSEGPT_IT seg_it; // iterator
1316 int16_t segpos; // position of segment
1317 int16_t cellpos; // previous cell boundary
1318 // iterator
1319 ICOORDELT_IT cell_it = row_cells;
1320 ICOORDELT *cell; // new cell
1321 double sqsum; // sum of squares
1322 double spsum; // of spaces
1323 double sp_var; // space error
1324 double word_sync; // result for word
1325 int32_t total_count; // total blobs
1326
1327 if ((pitsync_linear_version & 3) > 1) {
1328 word_sync = compute_pitch_sd2(row, projection, projection_left, projection_right, initial_pitch,
1329 occupation, mid_cuts, row_cells, testing_on, start, end);
1330 sp_sd = occupation;
1331 return word_sync;
1332 }
1333 mid_cuts = 0;
1334 cellpos = 0;
1335 total_count = 0;
1336 sqsum = 0;
1337 sp_count = 0;
1338 spsum = 0;
1339 prev_right = -1;
1340 if (blob_it.empty()) {
1341 return space_size * 10;
1342 }
1343#ifndef GRAPHICS_DISABLED
1344 if (testing_on && to_win != nullptr) {
1345 blob_box = blob_it.data()->bounding_box();
1346 projection->plot(to_win, projection_left, row->intercept(), 1.0f, -1.0f, ScrollView::CORAL);
1347 }
1348#endif
1349 start_it = blob_it;
1350 blob_count = 0;
1351 blob_box = box_next(&blob_it); // first blob
1352 blob_it.mark_cycle_pt();
1353 do {
1354 for (; blob_count > 0; blob_count--) {
1355 box_next(&start_it);
1356 }
1357 do {
1358 prev_box = blob_box;
1359 blob_count++;
1360 blob_box = box_next(&blob_it);
1361 } while (!blob_it.cycled_list() && blob_box.left() - prev_box.right() < space_size);
1362 plot_it = start_it;
1363 if (pitsync_linear_version & 3) {
1364 word_sync = check_pitch_sync2(&start_it, blob_count, static_cast<int16_t>(initial_pitch), 2,
1365 projection, projection_left, projection_right,
1366 row->xheight * textord_projection_scale, occupation, &seg_list,
1367 start, end);
1368 } else {
1369 word_sync = check_pitch_sync(&start_it, blob_count, static_cast<int16_t>(initial_pitch), 2,
1370 projection, &seg_list);
1371 }
1372 if (testing_on) {
1373 tprintf("Word ending at (%d,%d), len=%d, sync rating=%g, ", prev_box.right(), prev_box.top(),
1374 seg_list.length() - 1, word_sync);
1375 seg_it.set_to_list(&seg_list);
1376 for (seg_it.mark_cycle_pt(); !seg_it.cycled_list(); seg_it.forward()) {
1377 if (seg_it.data()->faked) {
1378 tprintf("(F)");
1379 }
1380 tprintf("%d, ", seg_it.data()->position());
1381 // tprintf("C=%g, s=%g, sq=%g\n",
1382 // seg_it.data()->cost_function(),
1383 // seg_it.data()->sum(),
1384 // seg_it.data()->squares());
1385 }
1386 tprintf("\n");
1387 }
1388#ifndef GRAPHICS_DISABLED
1389 if (textord_show_fixed_cuts && blob_count > 0 && to_win != nullptr) {
1391 }
1392#endif
1393 seg_it.set_to_list(&seg_list);
1394 if (prev_right >= 0) {
1395 sp_var = seg_it.data()->position() - prev_right;
1396 sp_var -= floor(sp_var / initial_pitch + 0.5) * initial_pitch;
1397 sp_var *= sp_var;
1398 spsum += sp_var;
1399 sp_count++;
1400 }
1401 for (seg_it.mark_cycle_pt(); !seg_it.cycled_list(); seg_it.forward()) {
1402 segpos = seg_it.data()->position();
1403 if (cell_it.empty() || segpos > cellpos + initial_pitch / 2) {
1404 // big gap
1405 while (!cell_it.empty() && segpos > cellpos + initial_pitch * 3 / 2) {
1406 cell = new ICOORDELT(cellpos + static_cast<int16_t>(initial_pitch), 0);
1407 cell_it.add_after_then_move(cell);
1408 cellpos += static_cast<int16_t>(initial_pitch);
1409 }
1410 // make new one
1411 cell = new ICOORDELT(segpos, 0);
1412 cell_it.add_after_then_move(cell);
1413 cellpos = segpos;
1414 } else if (segpos > cellpos - initial_pitch / 2) {
1415 cell = cell_it.data();
1416 // average positions
1417 cell->set_x((cellpos + segpos) / 2);
1418 cellpos = cell->x();
1419 }
1420 }
1421 seg_it.move_to_last();
1422 prev_right = seg_it.data()->position();
1424 scale_factor = (seg_list.length() - 2) / 2;
1425 if (scale_factor < 1) {
1426 scale_factor = 1;
1427 }
1428 } else {
1429 scale_factor = 1;
1430 }
1431 sqsum += word_sync * scale_factor;
1432 total_count += (seg_list.length() - 1) * scale_factor;
1433 seg_list.clear();
1434 } while (!blob_it.cycled_list());
1435 sp_sd = sp_count > 0 ? sqrt(spsum / sp_count) : 0;
1436 return total_count > 0 ? sqrt(sqsum / total_count) : space_size * 10;
1437}
1438
1439/**********************************************************************
1440 * compute_pitch_sd2
1441 *
1442 * Use a dp algorithm to fit the character cells and return the sd of
1443 * the cell size over the row.
1444 **********************************************************************/
1445
1446float compute_pitch_sd2( // find fp cells
1447 TO_ROW *row, // row to do
1448 STATS *projection, // vertical projection
1449 int16_t projection_left, // edge
1450 int16_t projection_right, // edge
1451 float initial_pitch, // guess at pitch
1452 int16_t &occupation, // no of occupied cells
1453 int16_t &mid_cuts, // no of free cuts
1454 ICOORDELT_LIST *row_cells, // list of chop pts
1455 bool testing_on, // individual words
1456 int16_t start, // start of good range
1457 int16_t end // end of good range
1458) {
1459 // blobs
1460 BLOBNBOX_IT blob_it = row->blob_list();
1461 BLOBNBOX_IT plot_it;
1462 int16_t blob_count; // no of blobs
1463 TBOX blob_box; // bounding box
1464 FPSEGPT_LIST seg_list; // char cells
1465 FPSEGPT_IT seg_it; // iterator
1466 int16_t segpos; // position of segment
1467 // iterator
1468 ICOORDELT_IT cell_it = row_cells;
1469 ICOORDELT *cell; // new cell
1470 double word_sync; // result for word
1471
1472 mid_cuts = 0;
1473 if (blob_it.empty()) {
1474 occupation = 0;
1475 return initial_pitch * 10;
1476 }
1477#ifndef GRAPHICS_DISABLED
1478 if (testing_on && to_win != nullptr) {
1479 projection->plot(to_win, projection_left, row->intercept(), 1.0f, -1.0f, ScrollView::CORAL);
1480 }
1481#endif
1482 blob_count = 0;
1483 blob_it.mark_cycle_pt();
1484 do {
1485 // first blob
1486 blob_box = box_next(&blob_it);
1487 blob_count++;
1488 } while (!blob_it.cycled_list());
1489 plot_it = blob_it;
1490 word_sync = check_pitch_sync2(
1491 &blob_it, blob_count, static_cast<int16_t>(initial_pitch), 2, projection, projection_left,
1492 projection_right, row->xheight * textord_projection_scale, occupation, &seg_list, start, end);
1493 if (testing_on) {
1494 tprintf("Row ending at (%d,%d), len=%d, sync rating=%g, ", blob_box.right(), blob_box.top(),
1495 seg_list.length() - 1, word_sync);
1496 seg_it.set_to_list(&seg_list);
1497 for (seg_it.mark_cycle_pt(); !seg_it.cycled_list(); seg_it.forward()) {
1498 if (seg_it.data()->faked) {
1499 tprintf("(F)");
1500 }
1501 tprintf("%d, ", seg_it.data()->position());
1502 // tprintf("C=%g, s=%g, sq=%g\n",
1503 // seg_it.data()->cost_function(),
1504 // seg_it.data()->sum(),
1505 // seg_it.data()->squares());
1506 }
1507 tprintf("\n");
1508 }
1509#ifndef GRAPHICS_DISABLED
1510 if (textord_show_fixed_cuts && blob_count > 0 && to_win != nullptr) {
1512 }
1513#endif
1514 seg_it.set_to_list(&seg_list);
1515 for (seg_it.mark_cycle_pt(); !seg_it.cycled_list(); seg_it.forward()) {
1516 segpos = seg_it.data()->position();
1517 // make new one
1518 cell = new ICOORDELT(segpos, 0);
1519 cell_it.add_after_then_move(cell);
1520 if (seg_it.at_last()) {
1521 mid_cuts = seg_it.data()->cheap_cuts();
1522 }
1523 }
1524 seg_list.clear();
1525 return occupation > 0 ? sqrt(word_sync / occupation) : initial_pitch * 10;
1526}
1527
1528/**********************************************************************
1529 * print_pitch_sd
1530 *
1531 * Use a dp algorithm to fit the character cells and return the sd of
1532 * the cell size over the row.
1533 **********************************************************************/
1534
1535void print_pitch_sd( // find fp cells
1536 TO_ROW *row, // row to do
1537 STATS *projection, // vertical projection
1538 int16_t projection_left, // edges //size of blank
1539 int16_t projection_right, float space_size,
1540 float initial_pitch // guess at pitch
1541) {
1542 const char *res2; // pitch result
1543 int16_t occupation; // used cells
1544 float sp_sd; // space sd
1545 // blobs
1546 BLOBNBOX_IT blob_it = row->blob_list();
1547 BLOBNBOX_IT start_it; // start of word
1548 BLOBNBOX_IT row_start; // start of row
1549 int16_t blob_count; // no of blobs
1550 int16_t total_blob_count; // total blobs in line
1551 TBOX blob_box; // bounding box
1552 TBOX prev_box; // of super blob
1553 int32_t prev_right; // of word sync
1554 int scale_factor; // on scores for big words
1555 int32_t sp_count; // spaces
1556 FPSEGPT_LIST seg_list; // char cells
1557 FPSEGPT_IT seg_it; // iterator
1558 double sqsum; // sum of squares
1559 double spsum; // of spaces
1560 double sp_var; // space error
1561 double word_sync; // result for word
1562 double total_count; // total cuts
1563
1564 if (blob_it.empty()) {
1565 return;
1566 }
1567 row_start = blob_it;
1568 total_blob_count = 0;
1569
1570 total_count = 0;
1571 sqsum = 0;
1572 sp_count = 0;
1573 spsum = 0;
1574 prev_right = -1;
1575 blob_it = row_start;
1576 start_it = blob_it;
1577 blob_count = 0;
1578 blob_box = box_next(&blob_it); // first blob
1579 blob_it.mark_cycle_pt();
1580 do {
1581 for (; blob_count > 0; blob_count--) {
1582 box_next(&start_it);
1583 }
1584 do {
1585 prev_box = blob_box;
1586 blob_count++;
1587 blob_box = box_next(&blob_it);
1588 } while (!blob_it.cycled_list() && blob_box.left() - prev_box.right() < space_size);
1589 word_sync = check_pitch_sync2(
1590 &start_it, blob_count, static_cast<int16_t>(initial_pitch), 2, projection, projection_left,
1591 projection_right, row->xheight * textord_projection_scale, occupation, &seg_list, 0, 0);
1592 total_blob_count += blob_count;
1593 seg_it.set_to_list(&seg_list);
1594 if (prev_right >= 0) {
1595 sp_var = seg_it.data()->position() - prev_right;
1596 sp_var -= floor(sp_var / initial_pitch + 0.5) * initial_pitch;
1597 sp_var *= sp_var;
1598 spsum += sp_var;
1599 sp_count++;
1600 }
1601 seg_it.move_to_last();
1602 prev_right = seg_it.data()->position();
1604 scale_factor = (seg_list.length() - 2) / 2;
1605 if (scale_factor < 1) {
1606 scale_factor = 1;
1607 }
1608 } else {
1609 scale_factor = 1;
1610 }
1611 sqsum += word_sync * scale_factor;
1612 total_count += (seg_list.length() - 1) * scale_factor;
1613 seg_list.clear();
1614 } while (!blob_it.cycled_list());
1615 sp_sd = sp_count > 0 ? sqrt(spsum / sp_count) : 0;
1616 word_sync = total_count > 0 ? sqrt(sqsum / total_count) : space_size * 10;
1617 tprintf("new_sd=%g:sd/p=%g:new_sp_sd=%g:res=%c:", word_sync, word_sync / initial_pitch, sp_sd,
1618 word_sync < textord_words_pitchsd_threshold * initial_pitch ? 'F' : 'P');
1619
1620 start_it = row_start;
1621 blob_it = row_start;
1622 word_sync =
1623 check_pitch_sync2(&blob_it, total_blob_count, static_cast<int16_t>(initial_pitch), 2,
1624 projection, projection_left, projection_right,
1625 row->xheight * textord_projection_scale, occupation, &seg_list, 0, 0);
1626 if (occupation > 1) {
1627 word_sync /= occupation;
1628 }
1629 word_sync = sqrt(word_sync);
1630
1631#ifndef GRAPHICS_DISABLED
1632 if (textord_show_row_cuts && to_win != nullptr) {
1633 plot_fp_cells2(to_win, ScrollView::CORAL, row, &seg_list);
1634 }
1635#endif
1636 seg_list.clear();
1637 if (word_sync < textord_words_pitchsd_threshold * initial_pitch) {
1638 if (word_sync < textord_words_def_fixed * initial_pitch && !row->all_caps) {
1639 res2 = "DF";
1640 } else {
1641 res2 = "MF";
1642 }
1643 } else {
1644 res2 = word_sync < textord_words_def_prop * initial_pitch ? "MP" : "DP";
1645 }
1646 tprintf(
1647 "row_sd=%g:sd/p=%g:res=%c:N=%d:res2=%s,init pitch=%g, row_pitch=%g, "
1648 "all_caps=%d\n",
1649 word_sync, word_sync / initial_pitch,
1650 word_sync < textord_words_pitchsd_threshold * initial_pitch ? 'F' : 'P', occupation, res2,
1651 initial_pitch, row->fixed_pitch, row->all_caps);
1652}
1653
1654/**********************************************************************
1655 * find_repeated_chars
1656 *
1657 * Extract marked leader blobs and put them
1658 * into words in advance of fixed pitch checking and word generation.
1659 **********************************************************************/
1660void find_repeated_chars(TO_BLOCK *block, // Block to search.
1661 bool testing_on) { // Debug mode.
1662 POLY_BLOCK *pb = block->block->pdblk.poly_block();
1663 if (pb != nullptr && !pb->IsText()) {
1664 return; // Don't find repeated chars in non-text blocks.
1665 }
1666
1667 TO_ROW *row;
1668 BLOBNBOX_IT box_it;
1669 BLOBNBOX_IT search_it; // forward search
1670 WERD *word; // new word
1671 TBOX word_box; // for plotting
1672 int blobcount, repeated_set;
1673
1674 TO_ROW_IT row_it = block->get_rows();
1675 if (row_it.empty()) {
1676 return; // empty block
1677 }
1678 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
1679 row = row_it.data();
1680 box_it.set_to_list(row->blob_list());
1681 if (box_it.empty()) {
1682 continue; // no blobs in this row
1683 }
1684 if (!row->rep_chars_marked()) {
1686 }
1687 if (row->num_repeated_sets() == 0) {
1688 continue; // nothing to do for this row
1689 }
1690 // new words
1691 WERD_IT word_it(&row->rep_words);
1692 do {
1693 if (box_it.data()->repeated_set() != 0 && !box_it.data()->joined_to_prev()) {
1694 blobcount = 1;
1695 repeated_set = box_it.data()->repeated_set();
1696 search_it = box_it;
1697 search_it.forward();
1698 while (!search_it.at_first() && search_it.data()->repeated_set() == repeated_set) {
1699 blobcount++;
1700 search_it.forward();
1701 }
1702 // After the call to make_real_word() all the blobs from this
1703 // repeated set will be removed from the blob list. box_it will be
1704 // set to point to the blob after the end of the extracted sequence.
1705 word = make_real_word(&box_it, blobcount, box_it.at_first(), 1);
1706 if (!box_it.empty() && box_it.data()->joined_to_prev()) {
1707 tprintf("Bad box joined to prev at");
1708 box_it.data()->bounding_box().print();
1709 tprintf("After repeated word:");
1710 word->bounding_box().print();
1711 }
1712 ASSERT_HOST(box_it.empty() || !box_it.data()->joined_to_prev());
1713 word->set_flag(W_REP_CHAR, true);
1714 word->set_flag(W_DONT_CHOP, true);
1715 word_it.add_after_then_move(word);
1716 } else {
1717 box_it.forward();
1718 }
1719 } while (!box_it.at_first());
1720 }
1721}
1722
1723/**********************************************************************
1724 * plot_fp_word
1725 *
1726 * Plot a block of words as if fixed pitch.
1727 **********************************************************************/
1728
1729#ifndef GRAPHICS_DISABLED
1730void plot_fp_word( // draw block of words
1731 TO_BLOCK *block, // block to draw
1732 float pitch, // pitch to draw with
1733 float nonspace // for space threshold
1734) {
1735 TO_ROW *row; // current row
1736 TO_ROW_IT row_it = block->get_rows();
1737
1738 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
1739 row = row_it.data();
1740 row->min_space = static_cast<int32_t>((pitch + nonspace) / 2);
1741 row->max_nonspace = row->min_space;
1742 row->space_threshold = row->min_space;
1743 plot_word_decisions(to_win, static_cast<int16_t>(pitch), row);
1744 }
1745}
1746#endif
1747
1748} // namespace tesseract
#define BOOL_VAR(name, val, comment)
Definition: params.h:360
#define double_VAR(name, val, comment)
Definition: params.h:366
#define ASSERT_HOST(x)
Definition: errcode.h:54
#define MAX_ALLOWED_PITCH
Definition: topitch.cpp:53
#define BLOCK_STATS_CLUSTERS
Definition: topitch.cpp:52
@ W_DONT_CHOP
fixed pitch chopped
Definition: werd.h:39
@ W_REP_CHAR
repeated character
Definition: werd.h:40
int textord_dotmatrix_gap
Definition: tovars.cpp:28
void compute_fixed_pitch(ICOORD page_tr, TO_BLOCK_LIST *port_blocks, float gradient, FCOORD rotation, bool testing_on)
Definition: topitch.cpp:75
bool try_block_fixed(TO_BLOCK *block, int32_t block_index)
Definition: topitch.cpp:502
double words_initial_upper
Definition: tovars.cpp:47
void compute_block_pitch(TO_BLOCK *block, FCOORD rotation, int32_t block_index, bool testing_on)
Definition: topitch.cpp:293
bool textord_blocksall_prop
Definition: tovars.cpp:27
void plot_fp_cells2(ScrollView *win, ScrollView::Color colour, TO_ROW *row, FPSEGPT_LIST *seg_list)
Definition: drawtord.cpp:353
double textord_wordstats_smooth_factor
Definition: tovars.cpp:31
double words_initial_lower
Definition: tovars.cpp:46
int textord_words_veto_power
Definition: tovars.cpp:43
bool fixed_pitch_row(TO_ROW *row, BLOCK *block, int32_t block_index)
Definition: topitch.cpp:931
void plot_fp_word(TO_BLOCK *block, float pitch, float nonspace)
Definition: topitch.cpp:1730
int textord_min_xheight
Definition: makerow.cpp:70
double textord_words_default_nonspace
Definition: tovars.cpp:36
void tprintf(const char *format,...)
Definition: tprintf.cpp:41
bool textord_show_fixed_cuts
Definition: drawtord.cpp:35
float compute_pitch_sd2(TO_ROW *row, STATS *projection, int16_t projection_left, int16_t projection_right, float initial_pitch, int16_t &occupation, int16_t &mid_cuts, ICOORDELT_LIST *row_cells, bool testing_on, int16_t start, int16_t end)
Definition: topitch.cpp:1446
double words_default_fixed_space
Definition: tovars.cpp:49
int pitsync_linear_version
Definition: pitsync1.cpp:26
void mark_repeated_chars(TO_ROW *row)
Definition: makerow.cpp:2565
WERD * make_real_word(BLOBNBOX_IT *box_it, int32_t blobcount, bool bol, uint8_t blanks)
Definition: wordseg.cpp:559
float compute_pitch_sd(TO_ROW *row, STATS *projection, int16_t projection_left, int16_t projection_right, float space_size, float initial_pitch, float &sp_sd, int16_t &mid_cuts, ICOORDELT_LIST *row_cells, bool testing_on, int16_t start, int16_t end)
Definition: topitch.cpp:1289
ScrollView * to_win
Definition: drawtord.cpp:37
int textord_pitch_range
Definition: tovars.cpp:30
int textord_debug_block
Definition: tovars.cpp:29
bool try_rows_fixed(TO_BLOCK *block, int32_t block_index, bool testing_on)
Definition: topitch.cpp:515
double textord_words_default_maxspace
Definition: tovars.cpp:33
void find_repeated_chars(TO_BLOCK *block, bool testing_on)
Definition: topitch.cpp:1660
double textord_projection_scale
Definition: topitch.cpp:49
@ PITCH_DUNNO
Definition: blobbox.h:48
@ PITCH_MAYBE_FIXED
Definition: blobbox.h:50
@ PITCH_DEF_FIXED
Definition: blobbox.h:49
@ PITCH_MAYBE_PROP
Definition: blobbox.h:52
@ PITCH_DEF_PROP
Definition: blobbox.h:51
@ PITCH_CORR_FIXED
Definition: blobbox.h:53
@ PITCH_CORR_PROP
Definition: blobbox.h:54
void print_pitch_sd(TO_ROW *row, STATS *projection, int16_t projection_left, int16_t projection_right, float space_size, float initial_pitch)
Definition: topitch.cpp:1535
bool textord_blockndoc_fixed
Definition: topitch.cpp:48
double check_pitch_sync2(BLOBNBOX_IT *blob_it, int16_t blob_count, int16_t pitch, int16_t pitch_error, STATS *projection, int16_t projection_left, int16_t projection_right, float projection_scale, int16_t &occupation_count, FPSEGPT_LIST *seg_list, int16_t start, int16_t end)
Definition: pithsync.cpp:292
bool count_pitch_stats(TO_ROW *row, STATS *gap_stats, STATS *pitch_stats, float initial_pitch, float min_space, bool ignore_outsize, bool split_outsize, int32_t dm_gap)
Definition: topitch.cpp:1008
bool textord_pitch_scalebigwords
Definition: tovars.cpp:45
double textord_words_min_minspace
Definition: tovars.cpp:35
bool find_row_pitch(TO_ROW *row, int32_t maxwidth, int32_t dm_gap, TO_BLOCK *block, int32_t block_index, int32_t row_index, bool testing_on)
Definition: topitch.cpp:784
void fix_row_pitch(TO_ROW *bad_row, TO_BLOCK *bad_block, TO_BLOCK_LIST *blocks, int32_t row_target, int32_t block_target)
Definition: topitch.cpp:144
bool textord_blocksall_fixed
Definition: tovars.cpp:26
bool textord_debug_pitch_metric
Definition: topitch.cpp:45
double textord_words_maxspace
Definition: tovars.cpp:32
float tune_row_pitch2(TO_ROW *row, STATS *projection, int16_t projection_left, int16_t projection_right, float space_size, float &initial_pitch, float &best_sp_sd, int16_t &best_mid_cuts, ICOORDELT_LIST *best_cells, bool testing_on)
Definition: topitch.cpp:1196
void print_block_counts(TO_BLOCK *block, int32_t block_index)
Definition: topitch.cpp:575
bool textord_debug_pitch_test
Definition: topitch.cpp:42
bool row_pitch_stats(TO_ROW *row, int32_t maxwidth, bool testing_on)
Definition: topitch.cpp:648
double textord_balance_factor
Definition: topitch.cpp:50
bool textord_show_row_cuts
Definition: topitch.cpp:46
bool try_doc_fixed(ICOORD page_tr, TO_BLOCK_LIST *port_blocks, float gradient)
Definition: topitch.cpp:371
void plot_word_decisions(ScrollView *win, int16_t pitch, TO_ROW *row)
Definition: drawtord.cpp:238
double words_default_prop_nonspace
Definition: tovars.cpp:48
bool textord_fast_pitch_test
Definition: topitch.cpp:44
double textord_fpiqr_ratio
Definition: tovars.cpp:53
ScrollView * create_to_win(ICOORD page_tr)
Definition: drawtord.cpp:47
double textord_words_pitchsd_threshold
Definition: tovars.cpp:40
float tune_row_pitch(TO_ROW *row, STATS *projection, int16_t projection_left, int16_t projection_right, float space_size, float &initial_pitch, float &best_sp_sd, int16_t &best_mid_cuts, ICOORDELT_LIST *best_cells, bool testing_on)
Definition: topitch.cpp:1097
void plot_row_cells(ScrollView *win, ScrollView::Color colour, TO_ROW *row, float xshift, ICOORDELT_LIST *cells)
Definition: drawtord.cpp:387
bool textord_show_initial_words
Definition: tovars.cpp:25
void count_block_votes(TO_BLOCK *block, int32_t &def_fixed, int32_t &def_prop, int32_t &maybe_fixed, int32_t &maybe_prop, int32_t &corr_fixed, int32_t &corr_prop, int32_t &dunno)
Definition: topitch.cpp:606
double words_default_fixed_limit
Definition: tovars.cpp:50
double check_pitch_sync(BLOBNBOX_IT *blob_it, int16_t blob_count, int16_t pitch, int16_t pitch_error, STATS *projection, FPSEGPT_LIST *seg_list)
Definition: pitsync1.cpp:138
double textord_max_pitch_iqr
Definition: tovars.cpp:54
double textord_words_def_prop
Definition: tovars.cpp:42
double textord_words_default_minspace
Definition: tovars.cpp:34
bool textord_show_page_cuts
Definition: topitch.cpp:47
double textord_spacesize_ratioprop
Definition: tovars.cpp:52
bool compute_rows_pitch(TO_BLOCK *block, int32_t block_index, bool testing_on)
Definition: topitch.cpp:330
TBOX box_next(BLOBNBOX_IT *it)
Definition: blobbox.cpp:638
double textord_pitch_rowsimilarity
Definition: tovars.cpp:44
const TBOX & bounding_box() const
Definition: blobbox.h:239
C_BLOB * cblob() const
Definition: blobbox.h:277
bool joined_to_prev() const
Definition: blobbox.h:265
bool rep_chars_marked() const
Definition: blobbox.h:637
QSPLINE baseline
Definition: blobbox.h:676
int32_t min_space
Definition: blobbox.h:669
ICOORDELT_LIST char_cells
Definition: blobbox.h:675
BLOBNBOX_LIST * blob_list()
Definition: blobbox.h:608
WERD_LIST rep_words
Definition: blobbox.h:674
int num_repeated_sets() const
Definition: blobbox.h:643
int32_t max_nonspace
Definition: blobbox.h:670
bool used_dm_model
Definition: blobbox.h:653
STATS projection
Definition: blobbox.h:677
float space_size
Definition: blobbox.h:673
float fixed_pitch
Definition: blobbox.h:657
int32_t space_threshold
Definition: blobbox.h:671
float intercept() const
Definition: blobbox.h:598
void compute_vertical_projection()
Definition: blobbox.cpp:799
PITCH_TYPE pitch_decision
Definition: blobbox.h:656
int16_t projection_left
Definition: blobbox.h:654
int16_t projection_right
Definition: blobbox.h:655
TO_ROW_LIST * get_rows()
Definition: blobbox.h:709
int32_t min_space
Definition: blobbox.h:796
int32_t max_nonspace
Definition: blobbox.h:797
PITCH_TYPE pitch_decision
Definition: blobbox.h:782
PDBLK pdblk
Page Description Block.
Definition: ocrblock.h:185
POLY_BLOCK * poly_block() const
Definition: pdblock.h:59
void bounding_box(ICOORD &bottom_left, ICOORD &top_right) const
get box
Definition: pdblock.h:67
integer coordinate
Definition: points.h:36
void set_x(TDimension xin)
rewrite function
Definition: points.h:67
TDimension x() const
access function
Definition: points.h:58
bool IsText() const
Definition: polyblk.h:52
double y(double x) const
Definition: quspline.cpp:203
TDimension left() const
Definition: rect.h:82
TDimension width() const
Definition: rect.h:126
TDimension top() const
Definition: rect.h:68
void print() const
Definition: rect.h:289
TDimension right() const
Definition: rect.h:89
TDimension bottom() const
Definition: rect.h:75
void add(int32_t value, int32_t count)
Definition: statistc.cpp:99
void plot(ScrollView *window, float xorigin, float yorigin, float xscale, float yscale, ScrollView::Color colour) const
Definition: statistc.cpp:596
int32_t pile_count(int32_t value) const
Definition: statistc.h:72
int32_t get_total() const
Definition: statistc.h:85
int32_t cluster(float lower, float upper, float multiple, int32_t max_clusters, STATS *clusters)
Definition: statistc.cpp:334
void smooth(int32_t factor)
Definition: statistc.cpp:301
double ile(double frac) const
Definition: statistc.cpp:172
bool set_range(int32_t min_bucket_value, int32_t max_bucket_value)
Definition: statistc.cpp:59
void set_flag(WERD_FLAGS mask, bool value)
Definition: werd.h:131
TBOX bounding_box() const
Definition: werd.cpp:155
static void Update()
Definition: scrollview.cpp:700