Coverage Report

Created: 2024-02-28 06:46

/src/tesseract/src/textord/pitsync1.cpp
Line
Count
Source (jump to first uncovered line)
1
/**********************************************************************
2
 * File:        pitsync1.cpp  (Formerly pitsync.c)
3
 * Description: Code to find the optimum fixed pitch segmentation of some blobs.
4
 * Author:      Ray Smith
5
 *
6
 * (C) Copyright 1992, 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 "pitsync1.h"
20
21
#include <cfloat> // for FLT_MAX
22
#include <cmath>
23
24
namespace tesseract {
25
26
INT_VAR(pitsync_linear_version, 6, "Use new fast algorithm");
27
double_VAR(pitsync_joined_edge, 0.75, "Dist inside big blob for chopping");
28
double_VAR(pitsync_offset_freecut_fraction, 0.25, "Fraction of cut for free cuts");
29
30
/**********************************************************************
31
 * FPSEGPT::FPSEGPT
32
 *
33
 * Constructor to make a new FPSEGPT.
34
 * The existing FPCUTPT is duplicated.
35
 **********************************************************************/
36
37
FPSEGPT::FPSEGPT(  // constructor
38
    FPCUTPT *cutpt // create from new form
39
819k
) {
40
819k
  pred = nullptr;
41
819k
  mean_sum = cutpt->sum();
42
819k
  sq_sum = cutpt->squares();
43
819k
  cost = cutpt->cost_function();
44
819k
  faked = cutpt->faked;
45
819k
  terminal = cutpt->terminal;
46
819k
  fake_count = cutpt->fake_count;
47
819k
  xpos = cutpt->position();
48
819k
  mid_cuts = cutpt->cheap_cuts();
49
819k
}
50
51
/**********************************************************************
52
 * FPSEGPT::FPSEGPT
53
 *
54
 * Constructor to make a new FPSEGPT.
55
 **********************************************************************/
56
57
FPSEGPT::FPSEGPT( // constructor
58
    int16_t x     // position
59
    )
60
0
    : xpos(x) {
61
0
  pred = nullptr;
62
0
  mean_sum = 0;
63
0
  sq_sum = 0;
64
0
  cost = 0;
65
0
  faked = false;
66
0
  terminal = false;
67
0
  fake_count = 0;
68
0
  mid_cuts = 0;
69
0
}
70
71
/**********************************************************************
72
 * FPSEGPT::FPSEGPT
73
 *
74
 * Constructor to make a new FPSEGPT.
75
 **********************************************************************/
76
77
FPSEGPT::FPSEGPT(           // constructor
78
    int16_t x,              // position
79
    bool faking,            // faking this one
80
    int16_t offset,         // dist to gap
81
    int16_t region_index,   // segment number
82
    int16_t pitch,          // proposed pitch
83
    int16_t pitch_error,    // allowed tolerance
84
    FPSEGPT_LIST *prev_list // previous segment
85
    )
86
0
    : fake_count(0), xpos(x), mean_sum(0.0), sq_sum(0.0) {
87
0
  int16_t best_fake;              // on previous
88
0
  FPSEGPT *segpt;                 // segment point
89
0
  int32_t dist;                   // from prev segment
90
0
  double sq_dist;                 // squared distance
91
0
  double mean;                    // mean pitch
92
0
  double total;                   // total dists
93
0
  double factor;                  // cost function
94
0
  FPSEGPT_IT pred_it = prev_list; // for previuos segment
95
96
0
  cost = FLT_MAX;
97
0
  pred = nullptr;
98
0
  faked = faking;
99
0
  terminal = false;
100
0
  best_fake = INT16_MAX;
101
0
  mid_cuts = 0;
102
0
  for (pred_it.mark_cycle_pt(); !pred_it.cycled_list(); pred_it.forward()) {
103
0
    segpt = pred_it.data();
104
0
    if (segpt->fake_count < best_fake) {
105
0
      best_fake = segpt->fake_count;
106
0
    }
107
0
    dist = x - segpt->xpos;
108
0
    if (dist >= pitch - pitch_error && dist <= pitch + pitch_error && !segpt->terminal) {
109
0
      total = segpt->mean_sum + dist;
110
0
      sq_dist = dist * dist + segpt->sq_sum + offset * offset;
111
      // sum of squarees
112
0
      mean = total / region_index;
113
0
      factor = mean - pitch;
114
0
      factor *= factor;
115
0
      factor += sq_dist / (region_index)-mean * mean;
116
0
      if (factor < cost) {
117
0
        cost = factor; // find least cost
118
0
        pred = segpt;  // save path
119
0
        mean_sum = total;
120
0
        sq_sum = sq_dist;
121
0
        fake_count = segpt->fake_count + faked;
122
0
      }
123
0
    }
124
0
  }
125
0
  if (fake_count > best_fake + 1) {
126
0
    pred = nullptr; // fail it
127
0
  }
128
0
}
129
130
/**********************************************************************
131
 * check_pitch_sync
132
 *
133
 * Construct the lattice of possible segmentation points and choose the
134
 * optimal path. Return the optimal path only.
135
 * The return value is a measure of goodness of the sync.
136
 **********************************************************************/
137
138
double check_pitch_sync(   // find segmentation
139
    BLOBNBOX_IT *blob_it,  // blobs to do
140
    int16_t blob_count,    // no of blobs
141
    int16_t pitch,         // pitch estimate
142
    int16_t pitch_error,   // tolerance
143
    STATS *projection,     // vertical
144
    FPSEGPT_LIST *seg_list // output list
145
0
) {
146
0
  int16_t x;          // current coord
147
0
  int16_t min_index;  // blob number
148
0
  int16_t max_index;  // blob number
149
0
  int16_t left_edge;  // of word
150
0
  int16_t right_edge; // of word
151
0
  int16_t right_max;  // max allowed x
152
0
  int16_t min_x;      // in this region
153
0
  int16_t max_x;
154
0
  int16_t region_index;
155
0
  int16_t best_region_index = 0; // for best result
156
0
  int16_t offset;                // dist to legal area
157
0
  int16_t left_best_x;           // edge of good region
158
0
  int16_t right_best_x;          // right edge
159
0
  TBOX min_box;                  // bounding box
160
0
  TBOX max_box;                  // bounding box
161
0
  TBOX next_box;                 // box of next blob
162
0
  FPSEGPT *segpt;                // segment point
163
0
  FPSEGPT_LIST *segpts;          // points in a segment
164
0
  double best_cost;              // best path
165
0
  double mean_sum;               // computes result
166
0
  FPSEGPT *best_end;             // end of best path
167
0
  BLOBNBOX_IT min_it;            // copy iterator
168
0
  BLOBNBOX_IT max_it;            // copy iterator
169
0
  FPSEGPT_IT segpt_it;           // iterator
170
                                 // output segments
171
0
  FPSEGPT_IT outseg_it = seg_list;
172
0
  FPSEGPT_LIST_CLIST lattice; // list of lists
173
                              // region iterator
174
0
  FPSEGPT_LIST_C_IT lattice_it = &lattice;
175
176
  //      tprintf("Computing sync on word of %d blobs with pitch %d\n",
177
  //              blob_count, pitch);
178
  //      if (blob_count==8 && pitch==27)
179
  //              projection->print(stdout,true);
180
0
  if (pitch < 3) {
181
0
    pitch = 3; // nothing ludicrous
182
0
  }
183
0
  if ((pitch - 3) / 2 < pitch_error) {
184
0
    pitch_error = (pitch - 3) / 2;
185
0
  }
186
0
  min_it = *blob_it;
187
0
  min_box = box_next(&min_it); // get box
188
  //      if (blob_count==8 && pitch==27)
189
  //              tprintf("1st box at (%d,%d)->(%d,%d)\n",
190
  //                      min_box.left(),min_box.bottom(),
191
  //                      min_box.right(),min_box.top());
192
  // left of word
193
0
  left_edge = min_box.left() + pitch_error;
194
0
  for (min_index = 1; min_index < blob_count; min_index++) {
195
0
    min_box = box_next(&min_it);
196
    //              if (blob_count==8 && pitch==27)
197
    //                      tprintf("Box at (%d,%d)->(%d,%d)\n",
198
    //                              min_box.left(),min_box.bottom(),
199
    //                              min_box.right(),min_box.top());
200
0
  }
201
0
  right_edge = min_box.right(); // end of word
202
0
  max_x = left_edge;
203
  // min permissible
204
0
  min_x = max_x - pitch + pitch_error * 2 + 1;
205
0
  right_max = right_edge + pitch - pitch_error - 1;
206
0
  segpts = new FPSEGPT_LIST; // list of points
207
0
  segpt_it.set_to_list(segpts);
208
0
  for (x = min_x; x <= max_x; x++) {
209
0
    segpt = new FPSEGPT(x); // make a new one
210
                            // put in list
211
0
    segpt_it.add_after_then_move(segpt);
212
0
  }
213
  // first segment
214
0
  lattice_it.add_before_then_move(segpts);
215
0
  min_index = 0;
216
0
  region_index = 1;
217
0
  best_cost = FLT_MAX;
218
0
  best_end = nullptr;
219
0
  min_it = *blob_it;
220
0
  min_box = box_next(&min_it); // first box
221
0
  do {
222
0
    left_best_x = -1;
223
0
    right_best_x = -1;
224
0
    segpts = new FPSEGPT_LIST; // list of points
225
0
    segpt_it.set_to_list(segpts);
226
0
    min_x += pitch - pitch_error; // next limits
227
0
    max_x += pitch + pitch_error;
228
0
    while (min_box.right() < min_x && min_index < blob_count) {
229
0
      min_index++;
230
0
      min_box = box_next(&min_it);
231
0
    }
232
0
    max_it = min_it;
233
0
    max_index = min_index;
234
0
    max_box = min_box;
235
0
    next_box = box_next(&max_it);
236
0
    for (x = min_x; x <= max_x && x <= right_max; x++) {
237
0
      while (x < right_edge && max_index < blob_count && x > max_box.right()) {
238
0
        max_index++;
239
0
        max_box = next_box;
240
0
        next_box = box_next(&max_it);
241
0
      }
242
0
      if (x <= max_box.left() + pitch_error || x >= max_box.right() - pitch_error ||
243
0
          x >= right_edge || (max_index < blob_count - 1 && x >= next_box.left()) ||
244
0
          (x - max_box.left() > pitch * pitsync_joined_edge &&
245
0
           max_box.right() - x > pitch * pitsync_joined_edge)) {
246
        //                      || projection->local_min(x))
247
0
        if (x - max_box.left() > 0 && x - max_box.left() <= pitch_error) {
248
          // dist to real break
249
0
          offset = x - max_box.left();
250
0
        } else if (max_box.right() - x > 0 && max_box.right() - x <= pitch_error &&
251
0
                   (max_index >= blob_count - 1 || x < next_box.left())) {
252
0
          offset = max_box.right() - x;
253
0
        } else {
254
0
          offset = 0;
255
0
        }
256
        //                              offset=pitsync_offset_freecut_fraction*projection->pile_count(x);
257
0
        segpt = new FPSEGPT(x, false, offset, region_index, pitch, pitch_error, lattice_it.data());
258
0
      } else {
259
0
        offset = projection->pile_count(x);
260
0
        segpt = new FPSEGPT(x, true, offset, region_index, pitch, pitch_error, lattice_it.data());
261
0
      }
262
0
      if (segpt->previous() != nullptr) {
263
0
        segpt_it.add_after_then_move(segpt);
264
0
        if (x >= right_edge - pitch_error) {
265
0
          segpt->terminal = true; // no more wanted
266
0
          if (segpt->cost_function() < best_cost) {
267
0
            best_cost = segpt->cost_function();
268
            // find least
269
0
            best_end = segpt;
270
0
            best_region_index = region_index;
271
0
            left_best_x = x;
272
0
            right_best_x = x;
273
0
          } else if (segpt->cost_function() == best_cost && right_best_x == x - 1) {
274
0
            right_best_x = x;
275
0
          }
276
0
        }
277
0
      } else {
278
0
        delete segpt; // no good
279
0
      }
280
0
    }
281
0
    if (segpts->empty()) {
282
0
      if (best_end != nullptr) {
283
0
        break; // already found one
284
0
      }
285
0
      make_illegal_segment(lattice_it.data(), min_box, min_it, region_index, pitch, pitch_error,
286
0
                           segpts);
287
0
    } else {
288
0
      if (right_best_x > left_best_x + 1) {
289
0
        left_best_x = (left_best_x + right_best_x + 1) / 2;
290
0
        for (segpt_it.mark_cycle_pt();
291
0
             !segpt_it.cycled_list() && segpt_it.data()->position() != left_best_x;
292
0
             segpt_it.forward()) {
293
0
          ;
294
0
        }
295
0
        if (segpt_it.data()->position() == left_best_x) {
296
          // middle of region
297
0
          best_end = segpt_it.data();
298
0
        }
299
0
      }
300
0
    }
301
    // new segment
302
0
    lattice_it.add_before_then_move(segpts);
303
0
    region_index++;
304
0
  } while (min_x < right_edge);
305
0
  ASSERT_HOST(best_end != nullptr); // must always find some
306
307
0
  for (lattice_it.mark_cycle_pt(); !lattice_it.cycled_list(); lattice_it.forward()) {
308
0
    segpts = lattice_it.data();
309
0
    segpt_it.set_to_list(segpts);
310
    //              if (blob_count==8 && pitch==27)
311
    //              {
312
    //                      for
313
    //                      (segpt_it.mark_cycle_pt();!segpt_it.cycled_list();segpt_it.forward())
314
    //                      {
315
    //                              segpt=segpt_it.data();
316
    //                              tprintf("At %d, (%x) cost=%g, m=%g, sq=%g,
317
    //                              pred=%x\n",
318
    //                                      segpt->position(),segpt,segpt->cost_function(),
319
    //                                      segpt->sum(),segpt->squares(),segpt->previous());
320
    //                      }
321
    //                      tprintf("\n");
322
    //              }
323
0
    for (segpt_it.mark_cycle_pt(); !segpt_it.cycled_list() && segpt_it.data() != best_end;
324
0
         segpt_it.forward()) {
325
0
      ;
326
0
    }
327
0
    if (segpt_it.data() == best_end) {
328
      // save good one
329
0
      segpt = segpt_it.extract();
330
0
      outseg_it.add_before_then_move(segpt);
331
0
      best_end = segpt->previous();
332
0
    }
333
0
  }
334
0
  ASSERT_HOST(best_end == nullptr);
335
0
  ASSERT_HOST(!outseg_it.empty());
336
0
  outseg_it.move_to_last();
337
0
  mean_sum = outseg_it.data()->sum();
338
0
  mean_sum = mean_sum * mean_sum / best_region_index;
339
0
  if (outseg_it.data()->squares() - mean_sum < 0) {
340
0
    tprintf("Impossible sqsum=%g, mean=%g, total=%d\n", outseg_it.data()->squares(),
341
0
            outseg_it.data()->sum(), best_region_index);
342
0
  }
343
0
  lattice.deep_clear(); // shift the lot
344
0
  return outseg_it.data()->squares() - mean_sum;
345
0
}
346
347
/**********************************************************************
348
 * make_illegal_segment
349
 *
350
 * Make a fake set of chop points due to having no legal places.
351
 **********************************************************************/
352
353
void make_illegal_segment(   // find segmentation
354
    FPSEGPT_LIST *prev_list, // previous segments
355
    TBOX blob_box,           // bounding box
356
    BLOBNBOX_IT blob_it,     // iterator
357
    int16_t region_index,    // number of segment
358
    int16_t pitch,           // pitch estimate
359
    int16_t pitch_error,     // tolerance
360
    FPSEGPT_LIST *seg_list   // output list
361
0
) {
362
0
  int16_t x;         // current coord
363
0
  int16_t min_x = 0; // in this region
364
0
  int16_t max_x = 0;
365
0
  int16_t offset;                 // dist to edge
366
0
  FPSEGPT *segpt;                 // segment point
367
0
  FPSEGPT *prevpt;                // previous point
368
0
  float best_cost;                // best path
369
0
  FPSEGPT_IT segpt_it = seg_list; // iterator
370
                                  // previous points
371
0
  FPSEGPT_IT prevpt_it = prev_list;
372
373
0
  best_cost = FLT_MAX;
374
0
  for (prevpt_it.mark_cycle_pt(); !prevpt_it.cycled_list(); prevpt_it.forward()) {
375
0
    prevpt = prevpt_it.data();
376
0
    if (prevpt->cost_function() < best_cost) {
377
      // find least
378
0
      best_cost = prevpt->cost_function();
379
0
      min_x = prevpt->position();
380
0
      max_x = min_x; // limits on coords
381
0
    } else if (prevpt->cost_function() == best_cost) {
382
0
      max_x = prevpt->position();
383
0
    }
384
0
  }
385
0
  min_x += pitch - pitch_error;
386
0
  max_x += pitch + pitch_error;
387
0
  for (x = min_x; x <= max_x; x++) {
388
0
    while (x > blob_box.right()) {
389
0
      blob_box = box_next(&blob_it);
390
0
    }
391
0
    offset = x - blob_box.left();
392
0
    if (blob_box.right() - x < offset) {
393
0
      offset = blob_box.right() - x;
394
0
    }
395
0
    segpt = new FPSEGPT(x, false, offset, region_index, pitch, pitch_error, prev_list);
396
0
    if (segpt->previous() != nullptr) {
397
0
      ASSERT_HOST(offset >= 0);
398
0
      fprintf(stderr, "made fake at %d\n", x);
399
      // make one up
400
0
      segpt_it.add_after_then_move(segpt);
401
0
      segpt->faked = true;
402
0
      segpt->fake_count++;
403
0
    } else {
404
0
      delete segpt;
405
0
    }
406
0
  }
407
0
}
408
409
} // namespace tesseract