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 
FPSEGPT(FPCUTPT * cutpt)37 FPSEGPT::FPSEGPT(  // constructor
38     FPCUTPT *cutpt // create from new form
39 ) {
40   pred = nullptr;
41   mean_sum = cutpt->sum();
42   sq_sum = cutpt->squares();
43   cost = cutpt->cost_function();
44   faked = cutpt->faked;
45   terminal = cutpt->terminal;
46   fake_count = cutpt->fake_count;
47   xpos = cutpt->position();
48   mid_cuts = cutpt->cheap_cuts();
49 }
50 
51 /**********************************************************************
52  * FPSEGPT::FPSEGPT
53  *
54  * Constructor to make a new FPSEGPT.
55  **********************************************************************/
56 
FPSEGPT(int16_t x)57 FPSEGPT::FPSEGPT( // constructor
58     int16_t x     // position
59     )
60     : xpos(x) {
61   pred = nullptr;
62   mean_sum = 0;
63   sq_sum = 0;
64   cost = 0;
65   faked = false;
66   terminal = false;
67   fake_count = 0;
68   mid_cuts = 0;
69 }
70 
71 /**********************************************************************
72  * FPSEGPT::FPSEGPT
73  *
74  * Constructor to make a new FPSEGPT.
75  **********************************************************************/
76 
FPSEGPT(int16_t x,bool faking,int16_t offset,int16_t region_index,int16_t pitch,int16_t pitch_error,FPSEGPT_LIST * prev_list)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     : fake_count(0), xpos(x), mean_sum(0.0), sq_sum(0.0) {
87   int16_t best_fake;              // on previous
88   FPSEGPT *segpt;                 // segment point
89   int32_t dist;                   // from prev segment
90   double sq_dist;                 // squared distance
91   double mean;                    // mean pitch
92   double total;                   // total dists
93   double factor;                  // cost function
94   FPSEGPT_IT pred_it = prev_list; // for previuos segment
95 
96   cost = FLT_MAX;
97   pred = nullptr;
98   faked = faking;
99   terminal = false;
100   best_fake = INT16_MAX;
101   mid_cuts = 0;
102   for (pred_it.mark_cycle_pt(); !pred_it.cycled_list(); pred_it.forward()) {
103     segpt = pred_it.data();
104     if (segpt->fake_count < best_fake) {
105       best_fake = segpt->fake_count;
106     }
107     dist = x - segpt->xpos;
108     if (dist >= pitch - pitch_error && dist <= pitch + pitch_error && !segpt->terminal) {
109       total = segpt->mean_sum + dist;
110       sq_dist = dist * dist + segpt->sq_sum + offset * offset;
111       // sum of squarees
112       mean = total / region_index;
113       factor = mean - pitch;
114       factor *= factor;
115       factor += sq_dist / (region_index)-mean * mean;
116       if (factor < cost) {
117         cost = factor; // find least cost
118         pred = segpt;  // save path
119         mean_sum = total;
120         sq_sum = sq_dist;
121         fake_count = segpt->fake_count + faked;
122       }
123     }
124   }
125   if (fake_count > best_fake + 1) {
126     pred = nullptr; // fail it
127   }
128 }
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 
check_pitch_sync(BLOBNBOX_IT * blob_it,int16_t blob_count,int16_t pitch,int16_t pitch_error,STATS * projection,FPSEGPT_LIST * seg_list)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 ) {
146   int16_t x;          // current coord
147   int16_t min_index;  // blob number
148   int16_t max_index;  // blob number
149   int16_t left_edge;  // of word
150   int16_t right_edge; // of word
151   int16_t right_max;  // max allowed x
152   int16_t min_x;      // in this region
153   int16_t max_x;
154   int16_t region_index;
155   int16_t best_region_index = 0; // for best result
156   int16_t offset;                // dist to legal area
157   int16_t left_best_x;           // edge of good region
158   int16_t right_best_x;          // right edge
159   TBOX min_box;                  // bounding box
160   TBOX max_box;                  // bounding box
161   TBOX next_box;                 // box of next blob
162   FPSEGPT *segpt;                // segment point
163   FPSEGPT_LIST *segpts;          // points in a segment
164   double best_cost;              // best path
165   double mean_sum;               // computes result
166   FPSEGPT *best_end;             // end of best path
167   BLOBNBOX_IT min_it;            // copy iterator
168   BLOBNBOX_IT max_it;            // copy iterator
169   FPSEGPT_IT segpt_it;           // iterator
170                                  // output segments
171   FPSEGPT_IT outseg_it = seg_list;
172   FPSEGPT_LIST_CLIST lattice; // list of lists
173                               // region iterator
174   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   if (pitch < 3) {
181     pitch = 3; // nothing ludicrous
182   }
183   if ((pitch - 3) / 2 < pitch_error) {
184     pitch_error = (pitch - 3) / 2;
185   }
186   min_it = *blob_it;
187   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   left_edge = min_box.left() + pitch_error;
194   for (min_index = 1; min_index < blob_count; min_index++) {
195     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   }
201   right_edge = min_box.right(); // end of word
202   max_x = left_edge;
203   // min permissible
204   min_x = max_x - pitch + pitch_error * 2 + 1;
205   right_max = right_edge + pitch - pitch_error - 1;
206   segpts = new FPSEGPT_LIST; // list of points
207   segpt_it.set_to_list(segpts);
208   for (x = min_x; x <= max_x; x++) {
209     segpt = new FPSEGPT(x); // make a new one
210                             // put in list
211     segpt_it.add_after_then_move(segpt);
212   }
213   // first segment
214   lattice_it.add_before_then_move(segpts);
215   min_index = 0;
216   region_index = 1;
217   best_cost = FLT_MAX;
218   best_end = nullptr;
219   min_it = *blob_it;
220   min_box = box_next(&min_it); // first box
221   do {
222     left_best_x = -1;
223     right_best_x = -1;
224     segpts = new FPSEGPT_LIST; // list of points
225     segpt_it.set_to_list(segpts);
226     min_x += pitch - pitch_error; // next limits
227     max_x += pitch + pitch_error;
228     while (min_box.right() < min_x && min_index < blob_count) {
229       min_index++;
230       min_box = box_next(&min_it);
231     }
232     max_it = min_it;
233     max_index = min_index;
234     max_box = min_box;
235     next_box = box_next(&max_it);
236     for (x = min_x; x <= max_x && x <= right_max; x++) {
237       while (x < right_edge && max_index < blob_count && x > max_box.right()) {
238         max_index++;
239         max_box = next_box;
240         next_box = box_next(&max_it);
241       }
242       if (x <= max_box.left() + pitch_error || x >= max_box.right() - pitch_error ||
243           x >= right_edge || (max_index < blob_count - 1 && x >= next_box.left()) ||
244           (x - max_box.left() > pitch * pitsync_joined_edge &&
245            max_box.right() - x > pitch * pitsync_joined_edge)) {
246         //                      || projection->local_min(x))
247         if (x - max_box.left() > 0 && x - max_box.left() <= pitch_error) {
248           // dist to real break
249           offset = x - max_box.left();
250         } else if (max_box.right() - x > 0 && max_box.right() - x <= pitch_error &&
251                    (max_index >= blob_count - 1 || x < next_box.left())) {
252           offset = max_box.right() - x;
253         } else {
254           offset = 0;
255         }
256         //                              offset=pitsync_offset_freecut_fraction*projection->pile_count(x);
257         segpt = new FPSEGPT(x, false, offset, region_index, pitch, pitch_error, lattice_it.data());
258       } else {
259         offset = projection->pile_count(x);
260         segpt = new FPSEGPT(x, true, offset, region_index, pitch, pitch_error, lattice_it.data());
261       }
262       if (segpt->previous() != nullptr) {
263         segpt_it.add_after_then_move(segpt);
264         if (x >= right_edge - pitch_error) {
265           segpt->terminal = true; // no more wanted
266           if (segpt->cost_function() < best_cost) {
267             best_cost = segpt->cost_function();
268             // find least
269             best_end = segpt;
270             best_region_index = region_index;
271             left_best_x = x;
272             right_best_x = x;
273           } else if (segpt->cost_function() == best_cost && right_best_x == x - 1) {
274             right_best_x = x;
275           }
276         }
277       } else {
278         delete segpt; // no good
279       }
280     }
281     if (segpts->empty()) {
282       if (best_end != nullptr) {
283         break; // already found one
284       }
285       make_illegal_segment(lattice_it.data(), min_box, min_it, region_index, pitch, pitch_error,
286                            segpts);
287     } else {
288       if (right_best_x > left_best_x + 1) {
289         left_best_x = (left_best_x + right_best_x + 1) / 2;
290         for (segpt_it.mark_cycle_pt();
291              !segpt_it.cycled_list() && segpt_it.data()->position() != left_best_x;
292              segpt_it.forward()) {
293           ;
294         }
295         if (segpt_it.data()->position() == left_best_x) {
296           // middle of region
297           best_end = segpt_it.data();
298         }
299       }
300     }
301     // new segment
302     lattice_it.add_before_then_move(segpts);
303     region_index++;
304   } while (min_x < right_edge);
305   ASSERT_HOST(best_end != nullptr); // must always find some
306 
307   for (lattice_it.mark_cycle_pt(); !lattice_it.cycled_list(); lattice_it.forward()) {
308     segpts = lattice_it.data();
309     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     for (segpt_it.mark_cycle_pt(); !segpt_it.cycled_list() && segpt_it.data() != best_end;
324          segpt_it.forward()) {
325       ;
326     }
327     if (segpt_it.data() == best_end) {
328       // save good one
329       segpt = segpt_it.extract();
330       outseg_it.add_before_then_move(segpt);
331       best_end = segpt->previous();
332     }
333   }
334   ASSERT_HOST(best_end == nullptr);
335   ASSERT_HOST(!outseg_it.empty());
336   outseg_it.move_to_last();
337   mean_sum = outseg_it.data()->sum();
338   mean_sum = mean_sum * mean_sum / best_region_index;
339   if (outseg_it.data()->squares() - mean_sum < 0) {
340     tprintf("Impossible sqsum=%g, mean=%g, total=%d\n", outseg_it.data()->squares(),
341             outseg_it.data()->sum(), best_region_index);
342   }
343   lattice.deep_clear(); // shift the lot
344   return outseg_it.data()->squares() - mean_sum;
345 }
346 
347 /**********************************************************************
348  * make_illegal_segment
349  *
350  * Make a fake set of chop points due to having no legal places.
351  **********************************************************************/
352 
make_illegal_segment(FPSEGPT_LIST * prev_list,TBOX blob_box,BLOBNBOX_IT blob_it,int16_t region_index,int16_t pitch,int16_t pitch_error,FPSEGPT_LIST * seg_list)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 ) {
362   int16_t x;         // current coord
363   int16_t min_x = 0; // in this region
364   int16_t max_x = 0;
365   int16_t offset;                 // dist to edge
366   FPSEGPT *segpt;                 // segment point
367   FPSEGPT *prevpt;                // previous point
368   float best_cost;                // best path
369   FPSEGPT_IT segpt_it = seg_list; // iterator
370                                   // previous points
371   FPSEGPT_IT prevpt_it = prev_list;
372 
373   best_cost = FLT_MAX;
374   for (prevpt_it.mark_cycle_pt(); !prevpt_it.cycled_list(); prevpt_it.forward()) {
375     prevpt = prevpt_it.data();
376     if (prevpt->cost_function() < best_cost) {
377       // find least
378       best_cost = prevpt->cost_function();
379       min_x = prevpt->position();
380       max_x = min_x; // limits on coords
381     } else if (prevpt->cost_function() == best_cost) {
382       max_x = prevpt->position();
383     }
384   }
385   min_x += pitch - pitch_error;
386   max_x += pitch + pitch_error;
387   for (x = min_x; x <= max_x; x++) {
388     while (x > blob_box.right()) {
389       blob_box = box_next(&blob_it);
390     }
391     offset = x - blob_box.left();
392     if (blob_box.right() - x < offset) {
393       offset = blob_box.right() - x;
394     }
395     segpt = new FPSEGPT(x, false, offset, region_index, pitch, pitch_error, prev_list);
396     if (segpt->previous() != nullptr) {
397       ASSERT_HOST(offset >= 0);
398       fprintf(stderr, "made fake at %d\n", x);
399       // make one up
400       segpt_it.add_after_then_move(segpt);
401       segpt->faked = true;
402       segpt->fake_count++;
403     } else {
404       delete segpt;
405     }
406   }
407 }
408 
409 } // namespace tesseract
410