/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 |