1 /*====================================================================*
2  -  Copyright (C) 2001 Leptonica.  All rights reserved.
3  -
4  -  Redistribution and use in source and binary forms, with or without
5  -  modification, are permitted provided that the following conditions
6  -  are met:
7  -  1. Redistributions of source code must retain the above copyright
8  -     notice, this list of conditions and the following disclaimer.
9  -  2. Redistributions in binary form must reproduce the above
10  -     copyright notice, this list of conditions and the following
11  -     disclaimer in the documentation and/or other materials
12  -     provided with the distribution.
13  -
14  -  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15  -  ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16  -  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17  -  A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL ANY
18  -  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  -  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  -  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  -  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  -  OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23  -  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24  -  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  *====================================================================*/
26 
27 /*!
28  * \file recogbasic.c
29  * <pre>
30  *
31  *      Recog creation, destruction and access
32  *         L_RECOG            *recogCreateFromRecog()
33  *         L_RECOG            *recogCreateFromPixa()
34  *         L_RECOG            *recogCreateFromPixaNoFinish()
35  *         L_RECOG            *recogCreate()
36  *         void                recogDestroy()
37  *         l_int32             recogGetCount()
38  *         l_int32             recogSetParams()
39  *         static l_int32      recogGetCharsetSize()
40  *
41  *      Character/index lookup
42  *         l_int32             recogGetClassIndex()
43  *         l_int32             recogStringToIndex()
44  *         l_int32             recogGetClassString()
45  *         l_int32             l_convertCharstrToInt()
46  *
47  *      Serialization
48  *         L_RECOG            *recogRead()
49  *         L_RECOG            *recogReadStream()
50  *         L_RECOG            *recogReadMem()
51  *         l_int32             recogWrite()
52  *         l_int32             recogWriteStream()
53  *         l_int32             recogWriteMem()
54  *         PIXA               *recogExtractPixa()
55  *         static l_int32      recogAddCharstrLabels()
56  *         static l_int32      recogAddAllSamples()
57  *
58  *  The recognizer functionality is split into four files:
59  *    recogbasic.c: create, destroy, access, serialize
60  *    recogtrain.c: training on labeled and unlabeled data
61  *    recogident.c: running the recognizer(s) on input
62  *    recogdid.c:   running the recognizer(s) on input using a
63  *                  document image decoding (DID) hidden markov model
64  *
65  *  This is a content-adapted (or book-adapted) recognizer (BAR) application.
66  *  The recognizers here are typically assembled from data that has
67  *  been labeled by a generic recognition system, such as Tesseract.
68  *  The general procedure to create a recognizer (recog) from labeled data is
69  *  to add the labeled character bitmaps, either one at a time or
70  *  all together from a pixa with labeled pix.
71  *
72  *  The suggested use for a BAR that consists of labeled templates drawn
73  *  from a single source (e.g., a book) is to identify unlabeled samples
74  *  by using unscaled character templates in the BAR, picking the
75  *  template closest to the unlabeled sample.
76  *
77  *  Outliers can be removed from a pixa of labeled pix.  This is one of
78  *  two methods that use averaged templates (the other is greedy splitting
79  *  of characters).  See recogtrain.c for a discussion and the implementation.
80  *
81  *  A special bootstrap recognizer (BSR) can be used to make a BAR from
82  *  unlabeled book data.  This is done by comparing character images
83  *  from the book with labeled templates in the BSR, where all images
84  *  are scaled to h = 40.  The templates can be either the scanned images
85  *  or images consisting of width-normalized strokes derived from
86  *  the skeleton of the character bitmaps.
87  *
88  *  Two BARs of labeled character data, that have been made by
89  *  different recognizers, can be joined by extracting a pixa of the
90  *  labeled templates from each, joining the two pixa, and then
91  *  and regenerating a BAR from the joined set of templates.
92  *  If all the labeled character data is from a single source (e.g, a book),
93  *  identification can proceed using unscaled templates (either the input
94  *  image or width-normalized lines).  But if the labeled data comes from
95  *  more than one source, (a "hybrid" recognizer), the templates should
96  *  be scaled, and we recommend scaling to a fixed height.
97  *
98  *  Suppose it is not possible to generate a BAR with a sufficient number
99  *  of templates of each class taken from a single source.  In that case,
100  *  templates from the BSR itself can be added.  This is the condition
101  *  described above, where the labeled templates come from multiple
102  *  sources, and it is necessary to do all character matches using
103  *  templates that have been scaled to a fixed height (e.g., 40).
104  *  Likewise, the samples to be identified using this hybrid recognizer
105  *  must be modified in the same way.  See prog/recogtest3.c for an
106  *  example of the steps that can be taken in the construction of a BAR
107  *  using a BSR.
108  *
109  *  For training numeric input, an example set of calls that scales
110  *  each training input to fixed h and will use the line templates of
111  *  width linew for identifying unknown characters is:
112  *         L_Recog  *rec = recogCreate(0, h, linew, 128, 1);
113  *         for (i = 0; i < n; i++) {  // read in n training digits
114  *             Pix *pix = ...
115  *             recogTrainLabeled(rec, pix, NULL, text[i], 0);
116  *         }
117  *         recogTrainingFinished(&rec, 1, -1, -1.0);  // required
118  *
119  *  It is an error if any function that computes averages, removes
120  *  outliers or requests identification of an unlabeled character,
121  *  such as:
122  *     (1) computing the sample averages: recogAverageSamples()
123  *     (2) removing outliers: recogRemoveOutliers1() or recogRemoveOutliers2()
124  *     (3) requesting identification of an unlabeled character:
125  *         recogIdentifyPix()
126  *  is called before an explicit call to finish training.  Note that
127  *  to do further training on a "finished" recognizer, you can set
128  *         recog->train_done = FALSE;
129  *  add the new training samples, and again call
130  *         recogTrainingFinished(&rec, 1, -1, -1.0);  // required
131  *
132  *  If not scaling, using the images directly for identification, and
133  *  removing outliers, do something like this:
134  *      L_Recog  *rec = recogCreate(0, 0, 0, 128, 1);
135  *      for (i = 0; i < n; i++) {  // read in n training characters
136  *          Pix *pix = ...
137  *          recogTrainLabeled(rec, pix, NULL, text[i], 0);
138  *      }
139  *      recogTrainingFinished(&rec, 1, -1, -1.0);
140  *      if (!rec) ... [return]
141  *      // remove outliers
142  *      recogRemoveOutliers1(&rec, 0.7, 2, NULL, NULL);
143  *
144  *  You can generate a recognizer from a pixa where the text field in
145  *  each pix is the character string label for the pix.  For example,
146  *  the following recognizer will store unscaled line images:
147  *      L_Recog  *rec = recogCreateFromPixa(pixa, 0, 0, linew, 128, 1);
148  *  and in use, it is fed unscaled line images to identify.
149  *
150  *  For the following, assume that you have a pixa of labeled templates.
151  *  If it is likely that some of the input templates are mislabeled,
152  *  there are several things that can be done to remove them.
153  *  The first is to put a size and quantity filter on them; e.g.
154  *       Pixa *pixa2 = recogFilterPixaBySize(pixa1, 10, 15, 2.6);
155  *  Then you can remove outliers; e.g.,
156  *       Pixa *pixa3 = pixaRemoveOutliers2(pixa2, -1.0, -1, NULL, NULL);
157  *
158  *  To this point, all templates are from a single source, so you
159  *  can make a recognizer that uses the unscaled templates and optionally
160  *  attempts to split touching characters:
161  *       L_Recog *recog1 = recogCreateFromPixa(pixa3, ...);
162  *  Alternatively, if you need more templates for some of the classes,
163  *  you can pad with templates from a "bootstrap" recognizer (BSR).
164  *  If you pad, it is necessary to scale the templates and input
165  *  samples to a fixed height, and no attempt will be made to split
166  *  the input sample connected components:
167  *       L_Recog *recog1 = recogCreateFromPixa(pixa3, 0, 40, 0, 128, 0);
168  *       recogPadDigitTrainingSet(&recog1, 40, 0);
169  *
170  *  A special case is a pure BSR, that contains images scaled to a fixed
171  *  height (we use 40 in these examples).
172  *  For this,use either the scanned bitmap:
173  *      L_Recog  *recboot = recogCreateFromPixa(pixa, 0, 40, 0, 128, 1);
174  *  or width-normalized lines (use width of 5 here):
175  *      L_Recog  *recboot = recogCreateFromPixa(pixa, 0, 40, 5, 128, 1);
176  *
177  *  This can be used to train a new book adapted recognizer (BAC), on
178  *  unlabeled data from, e.g., a book.  To do this, the following is required:
179  *   (1) the input images from the book must be scaled in the same
180  *       way as those in the BSR, and
181  *   (2) both the BSR and the input images must be set up to be either
182  *       input scanned images or width-normalized lines.
183  *
184  * </pre>
185  */
186 
187 #include <string.h>
188 #include "allheaders.h"
189 
190 static const l_int32  INITIAL_PTR_ARRAYSIZE = 20;  /* n'import quoi */
191 static const l_int32  MAX_EXAMPLES_IN_CLASS = 256;
192 
193     /* Default recog parameters that can be changed */
194 static const l_int32    DEFAULT_CHARSET_TYPE = L_ARABIC_NUMERALS;
195 static const l_int32    DEFAULT_MIN_NOPAD = 1;
196 static const l_float32  DEFAULT_MAX_WH_RATIO = 3.0;  /* max allowed w/h
197                                     ratio for a component to be split  */
198 static const l_float32  DEFAULT_MAX_HT_RATIO = 2.6;  /* max allowed ratio of
199                                max/min unscaled averaged template heights  */
200 static const l_int32    DEFAULT_THRESHOLD = 150;  /* for binarization */
201 static const l_int32    DEFAULT_MAXYSHIFT = 1;  /* for identification */
202 
203     /* Static functions */
204 static l_int32 recogGetCharsetSize(l_int32 type);
205 static l_int32 recogAddCharstrLabels(L_RECOG *recog);
206 static l_int32 recogAddAllSamples(L_RECOG **precog, PIXAA *paa, l_int32 debug);
207 
208 
209 /*------------------------------------------------------------------------*
210  *                Recog: initialization and destruction                   *
211  *------------------------------------------------------------------------*/
212 /*!
213  * \brief   recogCreateFromRecog()
214  *
215  * \param[in]    recs source recog with arbitrary input parameters
216  * \param[in]    scalew  scale all widths to this; use 0 otherwise
217  * \param[in]    scaleh  scale all heights to this; use 0 otherwise
218  * \param[in]    linew   width of normalized strokes; use 0 to skip
219  * \param[in]    threshold for binarization; typically ~128
220  * \param[in]    maxyshift from nominal centroid alignment; default is 1
221  * \return  recd, or NULL on error
222  *
223  * <pre>
224  * Notes:
225  *      (1) This is a convenience function that generates a recog using
226  *          the unscaled training data in an existing recog.
227  *      (2) It is recommended to use %maxyshift = 1 (the default value)
228  *      (3) See recogCreate() for use of %scalew, %scaleh and %linew.
229  * </pre>
230  */
231 L_RECOG *
recogCreateFromRecog(L_RECOG * recs,l_int32 scalew,l_int32 scaleh,l_int32 linew,l_int32 threshold,l_int32 maxyshift)232 recogCreateFromRecog(L_RECOG     *recs,
233                      l_int32      scalew,
234                      l_int32      scaleh,
235                      l_int32      linew,
236                      l_int32      threshold,
237                      l_int32      maxyshift)
238 {
239 L_RECOG  *recd;
240 PIXA     *pixa;
241 
242     PROCNAME("recogCreateFromRecog");
243 
244     if (!recs)
245         return (L_RECOG *)ERROR_PTR("recs not defined", procName, NULL);
246 
247     pixa = recogExtractPixa(recs);
248     recd = recogCreateFromPixa(pixa, scalew, scaleh, linew, threshold,
249                                maxyshift);
250     pixaDestroy(&pixa);
251     return recd;
252 }
253 
254 
255 /*!
256  * \brief   recogCreateFromPixa()
257  *
258  * \param[in]    pixa of labeled, 1 bpp images
259  * \param[in]    scalew  scale all widths to this; use 0 otherwise
260  * \param[in]    scaleh  scale all heights to this; use 0 otherwise
261  * \param[in]    linew   width of normalized strokes; use 0 to skip
262  * \param[in]    threshold for binarization; typically ~150
263  * \param[in]    maxyshift from nominal centroid alignment; default is 1
264  * \return  recog, or NULL on error
265  *
266  * <pre>
267  * Notes:
268  *      (1) This is a convenience function for training from labeled data.
269  *          The pixa can be read from file.
270  *      (2) The pixa should contain the unscaled bitmaps used for training.
271  *      (3) See recogCreate() for use of %scalew, %scaleh and %linew.
272  *      (4) It is recommended to use %maxyshift = 1 (the default value)
273  *      (5) All examples in the same class (i.e., with the same character
274  *          label) should be similar.  They can be made similar by invoking
275  *          recogRemoveOutliers[1,2]() on %pixa before calling this function.
276  * </pre>
277  */
278 L_RECOG *
recogCreateFromPixa(PIXA * pixa,l_int32 scalew,l_int32 scaleh,l_int32 linew,l_int32 threshold,l_int32 maxyshift)279 recogCreateFromPixa(PIXA    *pixa,
280                     l_int32  scalew,
281                     l_int32  scaleh,
282                     l_int32  linew,
283                     l_int32  threshold,
284                     l_int32  maxyshift)
285 {
286 L_RECOG  *recog;
287 
288     PROCNAME("recogCreateFromPixa");
289 
290     if (!pixa)
291         return (L_RECOG *)ERROR_PTR("pixa not defined", procName, NULL);
292 
293     recog = recogCreateFromPixaNoFinish(pixa, scalew, scaleh, linew,
294                                         threshold, maxyshift);
295     if (!recog)
296         return (L_RECOG *)ERROR_PTR("recog not made", procName, NULL);
297 
298     recogTrainingFinished(&recog, 1, -1, -1.0);
299     if (!recog)
300         return (L_RECOG *)ERROR_PTR("bad templates", procName, NULL);
301     return recog;
302 }
303 
304 
305 /*!
306  * \brief   recogCreateFromPixaNoFinish()
307  *
308  * \param[in]    pixa of labeled, 1 bpp images
309  * \param[in]    scalew  scale all widths to this; use 0 otherwise
310  * \param[in]    scaleh  scale all heights to this; use 0 otherwise
311  * \param[in]    linew   width of normalized strokes; use 0 to skip
312  * \param[in]    threshold for binarization; typically ~150
313  * \param[in]    maxyshift from nominal centroid alignment; default is 1
314  * \return  recog, or NULL on error
315  *
316  * <pre>
317  * Notes:
318  *      (1) See recogCreateFromPixa() for details.
319  *      (2) This is also used to generate a pixaa with templates
320  *          in each class within a pixa.  For that, all args except for
321  *          %pixa are ignored.
322  * </pre>
323  */
324 L_RECOG *
recogCreateFromPixaNoFinish(PIXA * pixa,l_int32 scalew,l_int32 scaleh,l_int32 linew,l_int32 threshold,l_int32 maxyshift)325 recogCreateFromPixaNoFinish(PIXA    *pixa,
326                             l_int32  scalew,
327                             l_int32  scaleh,
328                             l_int32  linew,
329                             l_int32  threshold,
330                             l_int32  maxyshift)
331 {
332 char     *text;
333 l_int32   full, n, i, ntext, same, maxd;
334 PIX      *pix;
335 L_RECOG  *recog;
336 
337     PROCNAME("recogCreateFromPixaNoFinish");
338 
339     if (!pixa)
340         return (L_RECOG *)ERROR_PTR("pixa not defined", procName, NULL);
341     pixaVerifyDepth(pixa, &same, &maxd);
342     if (maxd > 1)
343         return (L_RECOG *)ERROR_PTR("not all pix are 1 bpp", procName, NULL);
344 
345     pixaIsFull(pixa, &full, NULL);
346     if (!full)
347         return (L_RECOG *)ERROR_PTR("not all pix are present", procName, NULL);
348 
349     n = pixaGetCount(pixa);
350     pixaCountText(pixa, &ntext);
351     if (ntext == 0)
352         return (L_RECOG *)ERROR_PTR("no pix have text strings", procName, NULL);
353     if (ntext < n)
354         L_ERROR("%d text strings < %d pix\n", procName, ntext, n);
355 
356     recog = recogCreate(scalew, scaleh, linew, threshold, maxyshift);
357     if (!recog)
358         return (L_RECOG *)ERROR_PTR("recog not made", procName, NULL);
359     for (i = 0; i < n; i++) {
360         pix = pixaGetPix(pixa, i, L_CLONE);
361         text = pixGetText(pix);
362         if (!text || strlen(text) == 0) {
363             L_ERROR("pix[%d] has no text\n", procName, i);
364             pixDestroy(&pix);
365             continue;
366         }
367         recogTrainLabeled(recog, pix, NULL, text, 0);
368         pixDestroy(&pix);
369     }
370 
371     return recog;
372 }
373 
374 
375 /*!
376  * \brief   recogCreate()
377  *
378  * \param[in]    scalew  scale all widths to this; use 0 otherwise
379  * \param[in]    scaleh  scale all heights to this; use 0 otherwise
380  * \param[in]    linew   width of normalized strokes; use 0 to skip
381  * \param[in]    threshold for binarization; typically ~128; 0 for default
382  * \param[in]    maxyshift from nominal centroid alignment; default is 1
383  * \return  recog, or NULL on error
384  *
385  * <pre>
386  * Notes:
387  *      (1) If %scalew == 0 and %scaleh == 0, no scaling is done.
388  *          If one of these is 0 and the other is > 0, scaling is isotropic
389  *          to the requested size.  We typically do not set both > 0.
390  *      (2) Use linew > 0 to convert the templates to images with fixed
391  *          width strokes.  linew == 0 skips the conversion.
392  *      (3) The only valid values for %maxyshift are 0, 1 and 2.
393  *          It is recommended to use %maxyshift == 1 (default value).
394  *          Using %maxyshift == 0 is much faster than %maxyshift == 1, but
395  *          it is much less likely to find the template with the best
396  *          correlation.  Use of anything but 1 results in a warning.
397  *      (4) Scaling is used for finding outliers and for training a
398  *          book-adapted recognizer (BAR) from a bootstrap recognizer (BSR).
399  *          Scaling the height to a fixed value and scaling the width
400  *          accordingly (e.g., %scaleh = 40, %scalew = 0) is recommended.
401  *      (5) The storage for most of the arrays is allocated when training
402  *          is finished.
403  * </pre>
404  */
405 L_RECOG *
recogCreate(l_int32 scalew,l_int32 scaleh,l_int32 linew,l_int32 threshold,l_int32 maxyshift)406 recogCreate(l_int32      scalew,
407             l_int32      scaleh,
408             l_int32      linew,
409             l_int32      threshold,
410             l_int32      maxyshift)
411 {
412 L_RECOG  *recog;
413 
414     PROCNAME("recogCreate");
415 
416     if (scalew < 0 || scaleh < 0)
417         return (L_RECOG *)ERROR_PTR("invalid scalew or scaleh", procName, NULL);
418     if (linew > 10)
419         return (L_RECOG *)ERROR_PTR("invalid linew > 10", procName, NULL);
420     if (threshold == 0) threshold = DEFAULT_THRESHOLD;
421     if (threshold < 0 || threshold > 255) {
422         L_WARNING("invalid threshold; using default\n", procName);
423         threshold = DEFAULT_THRESHOLD;
424     }
425     if (maxyshift < 0 || maxyshift > 2) {
426          L_WARNING("invalid maxyshift; using default value\n", procName);
427          maxyshift = DEFAULT_MAXYSHIFT;
428     } else if (maxyshift == 0) {
429          L_WARNING("Using maxyshift = 0; faster, worse correlation results\n",
430                    procName);
431     } else if (maxyshift == 2) {
432          L_WARNING("Using maxyshift = 2; slower\n", procName);
433     }
434 
435     if ((recog = (L_RECOG *)LEPT_CALLOC(1, sizeof(L_RECOG))) == NULL)
436         return (L_RECOG *)ERROR_PTR("rec not made", procName, NULL);
437     recog->templ_use = L_USE_ALL_TEMPLATES;  /* default */
438     recog->threshold = threshold;
439     recog->scalew = scalew;
440     recog->scaleh = scaleh;
441     recog->linew = linew;
442     recog->maxyshift = maxyshift;
443     recogSetParams(recog, 1, -1, -1.0, -1.0);
444     recog->bmf = bmfCreate(NULL, 6);
445     recog->bmf_size = 6;
446     recog->maxarraysize = MAX_EXAMPLES_IN_CLASS;
447 
448         /* Generate the LUTs */
449     recog->centtab = makePixelCentroidTab8();
450     recog->sumtab = makePixelSumTab8();
451     recog->sa_text = sarrayCreate(0);
452     recog->dna_tochar = l_dnaCreate(0);
453 
454         /* Input default values for min component size for splitting.
455          * These are overwritten when pixTrainingFinished() is called. */
456     recog->min_splitw = 6;
457     recog->max_splith = 60;
458 
459         /* Allocate the paa for the unscaled training bitmaps */
460     recog->pixaa_u = pixaaCreate(recog->maxarraysize);
461 
462         /* Generate the storage for debugging */
463     recog->pixadb_boot = pixaCreate(2);
464     recog->pixadb_split = pixaCreate(2);
465     return recog;
466 }
467 
468 
469 /*!
470  * \brief   recogDestroy()
471  *
472  * \param[in,out]   precog will be set to null before returning
473  * \return  void
474  */
475 void
recogDestroy(L_RECOG ** precog)476 recogDestroy(L_RECOG  **precog)
477 {
478 L_RECOG  *recog;
479 
480     PROCNAME("recogDestroy");
481 
482     if (!precog) {
483         L_WARNING("ptr address is null\n", procName);
484         return;
485     }
486 
487     if ((recog = *precog) == NULL) return;
488 
489     LEPT_FREE(recog->centtab);
490     LEPT_FREE(recog->sumtab);
491     sarrayDestroy(&recog->sa_text);
492     l_dnaDestroy(&recog->dna_tochar);
493     pixaaDestroy(&recog->pixaa_u);
494     pixaDestroy(&recog->pixa_u);
495     ptaaDestroy(&recog->ptaa_u);
496     ptaDestroy(&recog->pta_u);
497     numaDestroy(&recog->nasum_u);
498     numaaDestroy(&recog->naasum_u);
499     pixaaDestroy(&recog->pixaa);
500     pixaDestroy(&recog->pixa);
501     ptaaDestroy(&recog->ptaa);
502     ptaDestroy(&recog->pta);
503     numaDestroy(&recog->nasum);
504     numaaDestroy(&recog->naasum);
505     pixaDestroy(&recog->pixa_tr);
506     pixaDestroy(&recog->pixadb_ave);
507     pixaDestroy(&recog->pixa_id);
508     pixDestroy(&recog->pixdb_ave);
509     pixDestroy(&recog->pixdb_range);
510     pixaDestroy(&recog->pixadb_boot);
511     pixaDestroy(&recog->pixadb_split);
512     bmfDestroy(&recog->bmf);
513     rchDestroy(&recog->rch);
514     rchaDestroy(&recog->rcha);
515     recogDestroyDid(recog);
516     LEPT_FREE(recog);
517     *precog = NULL;
518     return;
519 }
520 
521 
522 /*!
523  * \brief   recogGetCount()
524  *
525  * \param[in]    recog
526  * \return  count of classes in recog; 0 if no recog or on error
527  */
528 l_int32
recogGetCount(L_RECOG * recog)529 recogGetCount(L_RECOG  *recog)
530 {
531     PROCNAME("recogGetCount");
532 
533     if (!recog)
534         return ERROR_INT("recog not defined", procName, 0);
535     return recog->setsize;
536 }
537 
538 
539 /*!
540  * \brief   recogSetParams()
541  *
542  * \param[in]    recog         to be padded, if necessary
543  * \param[in]    type          type of char set; -1 for default;
544  *                               see enum in recog.h
545  * \param[in]    min_nopad     min number in a class without padding;
546  *                               use -1 for default
547  * \param[in]    max_wh_ratio  max width/height ratio allowed for splitting;
548  *                               use -1.0 for default
549  * \param[in]    max_ht_ratio  max of max/min averaged template height ratio;
550  *                               use -1.0 for default
551  * \return       0 if OK, 1 on error
552  *
553  * <pre>
554  * Notes:
555  *      (1) This is called when a recog is created.
556  *      (2) Default %min_nopad value allows for some padding.
557  *          To disable padding, set %min_nopad = 0.  To pad only when
558  *          no samples are available for the class, set %min_nopad = 1.
559  *      (3) The %max_wh_ratio limits the width/height ratio for components
560  *          that we attempt to split.  Splitting long components is expensive.
561  *      (4) The %max_ht_ratio is a quality requirement on the training data.
562  *          The recognizer will not run if the averages are computed and
563  *          the templates do not satisfy it.
564  * </pre>
565  */
566 l_int32
recogSetParams(L_RECOG * recog,l_int32 type,l_int32 min_nopad,l_float32 max_wh_ratio,l_float32 max_ht_ratio)567 recogSetParams(L_RECOG   *recog,
568                l_int32    type,
569                l_int32    min_nopad,
570                l_float32  max_wh_ratio,
571                l_float32  max_ht_ratio)
572 {
573 
574     PROCNAME("recogSetParams");
575 
576     if (!recog)
577         return ERROR_INT("recog not defined", procName, 1);
578 
579     recog->charset_type = (type >= 0) ? type : DEFAULT_CHARSET_TYPE;
580     recog->charset_size = recogGetCharsetSize(recog->charset_type);
581     recog->min_nopad = (min_nopad >= 0) ? min_nopad : DEFAULT_MIN_NOPAD;
582     recog->max_wh_ratio = (max_wh_ratio > 0.0) ? max_wh_ratio :
583                           DEFAULT_MAX_WH_RATIO;
584     recog->max_ht_ratio = (max_ht_ratio > 1.0) ? max_ht_ratio :
585                           DEFAULT_MAX_HT_RATIO;
586     return 0;
587 }
588 
589 
590 /*!
591  * \brief   recogGetCharsetSize()
592  *
593  * \param[in]    type of charset
594  * \return  size of charset, or 0 if unknown or on error
595  */
596 static l_int32
recogGetCharsetSize(l_int32 type)597 recogGetCharsetSize(l_int32  type)
598 {
599     PROCNAME("recogGetCharsetSize");
600 
601     switch (type) {
602     case L_UNKNOWN:
603         return 0;
604     case L_ARABIC_NUMERALS:
605         return 10;
606     case L_LC_ROMAN_NUMERALS:
607         return 7;
608     case L_UC_ROMAN_NUMERALS:
609         return 7;
610     case L_LC_ALPHA:
611         return 26;
612     case L_UC_ALPHA:
613         return 26;
614     default:
615         L_ERROR("invalid charset_type %d\n", procName, type);
616         return 0;
617     }
618     return 0;  /* shouldn't happen */
619 }
620 
621 
622 /*------------------------------------------------------------------------*
623  *                         Character/index lookup                         *
624  *------------------------------------------------------------------------*/
625 /*!
626  * \brief   recogGetClassIndex()
627  *
628  * \param[in]    recog with LUT's pre-computed
629  * \param[in]    val integer value; can be up to 3 bytes for UTF-8
630  * \param[in]    text text from which %val was derived; used if not found
631  * \param[out]   pindex index into dna_tochar
632  * \return  0 if found; 1 if not found and added; 2 on error.
633  *
634  * <pre>
635  * Notes:
636  *      (1) This is used during training.  There is one entry in
637  *          recog->dna_tochar (integer value, e.g., ascii) and
638  *          one in recog->sa_text (e.g, ascii letter in a string)
639  *          for each character class.
640  *      (2) This searches the dna character array for %val.  If it is
641  *          not found, the template represents a character class not
642  *          already seen: it increments setsize (the number of character
643  *          classes) by 1, and augments both the index (dna_tochar)
644  *          and text (sa_text) arrays.
645  *      (3) Returns the index in &index, except on error.
646  *      (4) Caller must check the function return value.
647  * </pre>
648  */
649 l_int32
recogGetClassIndex(L_RECOG * recog,l_int32 val,char * text,l_int32 * pindex)650 recogGetClassIndex(L_RECOG  *recog,
651                    l_int32   val,
652                    char     *text,
653                    l_int32  *pindex)
654 {
655 l_int32  i, n, ival;
656 
657     PROCNAME("recogGetClassIndex");
658 
659     if (!pindex)
660         return ERROR_INT("&index not defined", procName, 2);
661     *pindex = -1;
662     if (!recog)
663         return ERROR_INT("recog not defined", procName, 2);
664     if (!text)
665         return ERROR_INT("text not defined", procName, 2);
666 
667         /* Search existing characters */
668     n = l_dnaGetCount(recog->dna_tochar);
669     for (i = 0; i < n; i++) {
670         l_dnaGetIValue(recog->dna_tochar, i, &ival);
671         if (val == ival) {  /* found */
672             *pindex = i;
673             return 0;
674         }
675     }
676 
677        /* If not found... */
678     l_dnaAddNumber(recog->dna_tochar, val);
679     sarrayAddString(recog->sa_text, text, L_COPY);
680     recog->setsize++;
681     *pindex = n;
682     return 1;
683 }
684 
685 
686 /*!
687  * \brief   recogStringToIndex()
688  *
689  * \param[in]    recog
690  * \param[in]    text text string for some class
691  * \param[out]   pindex index for that class; -1 if not found
692  * \return  0 if OK, 1 on error not finding the string is an error
693  */
694 l_int32
recogStringToIndex(L_RECOG * recog,char * text,l_int32 * pindex)695 recogStringToIndex(L_RECOG  *recog,
696                    char     *text,
697                    l_int32  *pindex)
698 {
699 char    *charstr;
700 l_int32  i, n, diff;
701 
702     PROCNAME("recogStringtoIndex");
703 
704     if (!pindex)
705         return ERROR_INT("&index not defined", procName, 1);
706     *pindex = -1;
707     if (!recog)
708         return ERROR_INT("recog not defined", procName, 1);
709     if (!text)
710         return ERROR_INT("text not defined", procName, 1);
711 
712         /* Search existing characters */
713     n = recog->setsize;
714     for (i = 0; i < n; i++) {
715         recogGetClassString(recog, i, &charstr);
716         if (!charstr) {
717             L_ERROR("string not found for index %d\n", procName, i);
718             continue;
719         }
720         diff = strcmp(text, charstr);
721         LEPT_FREE(charstr);
722         if (diff) continue;
723         *pindex = i;
724         return 0;
725     }
726 
727     return 1;  /* not found */
728 }
729 
730 
731 /*!
732  * \brief   recogGetClassString()
733  *
734  * \param[in]    recog
735  * \param[in]    index into array of char types
736  * \param[out]   pcharstr string representation;
737  *                        returns an empty string on error
738  * \return  0 if found, 1 on error
739  *
740  * <pre>
741  * Notes:
742  *      (1) Extracts a copy of the string from sa_text, which
743  *          the caller must free.
744  *      (2) Caller must check the function return value.
745  * </pre>
746  */
747 l_int32
recogGetClassString(L_RECOG * recog,l_int32 index,char ** pcharstr)748 recogGetClassString(L_RECOG  *recog,
749                     l_int32   index,
750                     char    **pcharstr)
751 {
752     PROCNAME("recogGetClassString");
753 
754     if (!pcharstr)
755         return ERROR_INT("&charstr not defined", procName, 1);
756     *pcharstr = stringNew("");
757     if (!recog)
758         return ERROR_INT("recog not defined", procName, 2);
759 
760     if (index < 0 || index >= recog->setsize)
761         return ERROR_INT("invalid index", procName, 1);
762     LEPT_FREE(*pcharstr);
763     *pcharstr = sarrayGetString(recog->sa_text, index, L_COPY);
764     return 0;
765 }
766 
767 
768 /*!
769  * \brief   l_convertCharstrToInt()
770  *
771  * \param[in]    str input string representing one UTF-8 character;
772  *                   not more than 4 bytes
773  * \param[out]   pval integer value for the input.  Think of it
774  *                    as a 1-to-1 hash code.
775  * \return  0 if OK, 1 on error
776  */
777 l_int32
l_convertCharstrToInt(const char * str,l_int32 * pval)778 l_convertCharstrToInt(const char  *str,
779                       l_int32     *pval)
780 {
781 l_int32  size, val;
782 
783     PROCNAME("l_convertCharstrToInt");
784 
785     if (!pval)
786         return ERROR_INT("&val not defined", procName, 1);
787     *pval = 0;
788     if (!str)
789         return ERROR_INT("str not defined", procName, 1);
790     size = strlen(str);
791     if (size == 0)
792         return ERROR_INT("empty string", procName, 1);
793     if (size > 4)
794         return ERROR_INT("invalid string: > 4 bytes", procName, 1);
795 
796     val = (l_int32)str[0];
797     if (size > 1)
798         val = (val << 8) + (l_int32)str[1];
799     if (size > 2)
800         val = (val << 8) + (l_int32)str[2];
801     if (size > 3)
802         val = (val << 8) + (l_int32)str[3];
803     *pval = val;
804     return 0;
805 }
806 
807 
808 /*------------------------------------------------------------------------*
809  *                             Serialization                              *
810  *------------------------------------------------------------------------*/
811 /*!
812  * \brief   recogRead()
813  *
814  * \param[in]    filename
815  * \return  recog, or NULL on error
816  *
817  * <pre>
818  * Notes:
819  *      (1) When a recog is serialized, a pixaa of the templates that are
820  *          actually used for correlation is saved in the pixaa_u array
821  *          of the recog.  These can be different from the templates that
822  *          were used to generate the recog, because those original templates
823  *          can be scaled and turned into normalized lines.  When recog1
824  *          is deserialized to recog2, these templates are put in both the
825  *          unscaled array (pixaa_u) and the modified array (pixaa) in recog2.
826  *          Why not put it in only the unscaled array and let
827  *          recogTrainingFinalized() regenerate the modified templates?
828  *          The reason is that with normalized lines, the operation of
829  *          thinning to a skeleton and dilating back to a fixed width
830  *          is not idempotent.  Thinning to a skeleton saves pixels at
831  *          the end of a line segment, and thickening the skeleton puts
832  *          additional pixels at the end of the lines.  This tends to
833  *          close gaps.
834  * </pre>
835  */
836 L_RECOG *
recogRead(const char * filename)837 recogRead(const char  *filename)
838 {
839 FILE     *fp;
840 L_RECOG  *recog;
841 
842     PROCNAME("recogRead");
843 
844     if (!filename)
845         return (L_RECOG *)ERROR_PTR("filename not defined", procName, NULL);
846     if ((fp = fopenReadStream(filename)) == NULL)
847         return (L_RECOG *)ERROR_PTR("stream not opened", procName, NULL);
848 
849     if ((recog = recogReadStream(fp)) == NULL) {
850         fclose(fp);
851         return (L_RECOG *)ERROR_PTR("recog not read", procName, NULL);
852     }
853 
854     fclose(fp);
855     return recog;
856 }
857 
858 
859 /*!
860  * \brief   recogReadStream()
861  *
862  * \param[in]    fp file stream
863  * \return  recog, or NULL on error
864  */
865 L_RECOG *
recogReadStream(FILE * fp)866 recogReadStream(FILE  *fp)
867 {
868 l_int32   version, setsize, threshold, scalew, scaleh, linew;
869 l_int32   maxyshift, nc;
870 L_DNA    *dna_tochar;
871 PIXAA    *paa;
872 L_RECOG  *recog;
873 SARRAY   *sa_text;
874 
875     PROCNAME("recogReadStream");
876 
877     if (!fp)
878         return (L_RECOG *)ERROR_PTR("stream not defined", procName, NULL);
879 
880     if (fscanf(fp, "\nRecog Version %d\n", &version) != 1)
881         return (L_RECOG *)ERROR_PTR("not a recog file", procName, NULL);
882     if (version != RECOG_VERSION_NUMBER)
883         return (L_RECOG *)ERROR_PTR("invalid recog version", procName, NULL);
884     if (fscanf(fp, "Size of character set = %d\n", &setsize) != 1)
885         return (L_RECOG *)ERROR_PTR("setsize not read", procName, NULL);
886     if (fscanf(fp, "Binarization threshold = %d\n", &threshold) != 1)
887         return (L_RECOG *)ERROR_PTR("binary thresh not read", procName, NULL);
888     if (fscanf(fp, "Maxyshift = %d\n", &maxyshift) != 1)
889         return (L_RECOG *)ERROR_PTR("maxyshift not read", procName, NULL);
890     if (fscanf(fp, "Scale to width = %d\n", &scalew) != 1)
891         return (L_RECOG *)ERROR_PTR("width not read", procName, NULL);
892     if (fscanf(fp, "Scale to height = %d\n", &scaleh) != 1)
893         return (L_RECOG *)ERROR_PTR("height not read", procName, NULL);
894     if (fscanf(fp, "Normalized line width = %d\n", &linew) != 1)
895         return (L_RECOG *)ERROR_PTR("line width not read", procName, NULL);
896     if ((recog = recogCreate(scalew, scaleh, linew, threshold,
897                              maxyshift)) == NULL)
898         return (L_RECOG *)ERROR_PTR("recog not made", procName, NULL);
899 
900     if (fscanf(fp, "\nLabels for character set:\n") != 0) {
901         recogDestroy(&recog);
902         return (L_RECOG *)ERROR_PTR("label intro not read", procName, NULL);
903     }
904     l_dnaDestroy(&recog->dna_tochar);
905     if ((dna_tochar = l_dnaReadStream(fp)) == NULL) {
906         recogDestroy(&recog);
907         return (L_RECOG *)ERROR_PTR("dna_tochar not read", procName, NULL);
908     }
909     recog->dna_tochar = dna_tochar;
910     sarrayDestroy(&recog->sa_text);
911     if ((sa_text = sarrayReadStream(fp)) == NULL) {
912         recogDestroy(&recog);
913         return (L_RECOG *)ERROR_PTR("sa_text not read", procName, NULL);
914     }
915     recog->sa_text = sa_text;
916 
917     if (fscanf(fp, "\nPixaa of all samples in the training set:\n") != 0) {
918         recogDestroy(&recog);
919         return (L_RECOG *)ERROR_PTR("pixaa intro not read", procName, NULL);
920     }
921     if ((paa = pixaaReadStream(fp)) == NULL) {
922         recogDestroy(&recog);
923         return (L_RECOG *)ERROR_PTR("pixaa not read", procName, NULL);
924     }
925     recog->setsize = setsize;
926     nc = pixaaGetCount(paa, NULL);
927     if (nc != setsize) {
928         recogDestroy(&recog);
929         pixaaDestroy(&paa);
930         L_ERROR("(setsize = %d) != (paa count = %d)\n", procName,
931                      setsize, nc);
932         return NULL;
933     }
934 
935     recogAddAllSamples(&recog, paa, 0);  /* this finishes */
936     pixaaDestroy(&paa);
937     if (!recog)
938         return (L_RECOG *)ERROR_PTR("bad templates", procName, NULL);
939     return recog;
940 }
941 
942 
943 /*!
944  * \brief   recogReadMem()
945  *
946  * \param[in]    data  serialization of recog (not ascii)
947  * \param[in]    size  of data in bytes
948  * \return  recog, or NULL on error
949  */
950 L_RECOG *
recogReadMem(const l_uint8 * data,size_t size)951 recogReadMem(const l_uint8  *data,
952              size_t          size)
953 {
954 FILE     *fp;
955 L_RECOG  *recog;
956 
957     PROCNAME("recogReadMem");
958 
959     if (!data)
960         return (L_RECOG *)ERROR_PTR("data not defined", procName, NULL);
961     if ((fp = fopenReadFromMemory(data, size)) == NULL)
962         return (L_RECOG *)ERROR_PTR("stream not opened", procName, NULL);
963 
964     recog = recogReadStream(fp);
965     fclose(fp);
966     if (!recog) L_ERROR("recog not read\n", procName);
967     return recog;
968 }
969 
970 
971 /*!
972  * \brief   recogWrite()
973  *
974  * \param[in]    filename
975  * \param[in]    recog
976  * \return  0 if OK, 1 on error
977  *
978  * <pre>
979  * Notes:
980  *      (1) The pixaa of templates that is written is the modified one
981  *          in the pixaa field. It is the pixaa that is actually used
982  *          for correlation. This is not the unscaled array of labeled
983  *          bitmaps, in pixaa_u, that was used to generate the recog in the
984  *          first place.  See the notes in recogRead() for the rationale.
985  * </pre>
986  */
987 l_int32
recogWrite(const char * filename,L_RECOG * recog)988 recogWrite(const char  *filename,
989            L_RECOG     *recog)
990 {
991 l_int32  ret;
992 FILE    *fp;
993 
994     PROCNAME("recogWrite");
995 
996     if (!filename)
997         return ERROR_INT("filename not defined", procName, 1);
998     if (!recog)
999         return ERROR_INT("recog not defined", procName, 1);
1000 
1001     if ((fp = fopenWriteStream(filename, "wb")) == NULL)
1002         return ERROR_INT("stream not opened", procName, 1);
1003     ret = recogWriteStream(fp, recog);
1004     fclose(fp);
1005     if (ret)
1006         return ERROR_INT("recog not written to stream", procName, 1);
1007     return 0;
1008 }
1009 
1010 
1011 /*!
1012  * \brief   recogWriteStream()
1013  *
1014  * \param[in]    fp file stream opened for "wb"
1015  * \param[in]    recog
1016  * \return  0 if OK, 1 on error
1017  */
1018 l_int32
recogWriteStream(FILE * fp,L_RECOG * recog)1019 recogWriteStream(FILE     *fp,
1020                  L_RECOG  *recog)
1021 {
1022     PROCNAME("recogWriteStream");
1023 
1024     if (!fp)
1025         return ERROR_INT("stream not defined", procName, 1);
1026     if (!recog)
1027         return ERROR_INT("recog not defined", procName, 1);
1028 
1029     fprintf(fp, "\nRecog Version %d\n", RECOG_VERSION_NUMBER);
1030     fprintf(fp, "Size of character set = %d\n", recog->setsize);
1031     fprintf(fp, "Binarization threshold = %d\n", recog->threshold);
1032     fprintf(fp, "Maxyshift = %d\n", recog->maxyshift);
1033     fprintf(fp, "Scale to width = %d\n", recog->scalew);
1034     fprintf(fp, "Scale to height = %d\n", recog->scaleh);
1035     fprintf(fp, "Normalized line width = %d\n", recog->linew);
1036     fprintf(fp, "\nLabels for character set:\n");
1037     l_dnaWriteStream(fp, recog->dna_tochar);
1038     sarrayWriteStream(fp, recog->sa_text);
1039     fprintf(fp, "\nPixaa of all samples in the training set:\n");
1040     pixaaWriteStream(fp, recog->pixaa);
1041 
1042     return 0;
1043 }
1044 
1045 
1046 /*!
1047  * \brief   recogWriteMem()
1048  *
1049  * \param[out]   pdata data of serialized recog (not ascii)
1050  * \param[out]   psize size of returned data
1051  * \param[in]    recog
1052  * \return  0 if OK, 1 on error
1053  *
1054  * <pre>
1055  * Notes:
1056  *      (1) Serializes a recog in memory and puts the result in a buffer.
1057  * </pre>
1058  */
1059 l_int32
recogWriteMem(l_uint8 ** pdata,size_t * psize,L_RECOG * recog)1060 recogWriteMem(l_uint8  **pdata,
1061               size_t    *psize,
1062               L_RECOG   *recog)
1063 {
1064 l_int32  ret;
1065 FILE    *fp;
1066 
1067     PROCNAME("recogWriteMem");
1068 
1069     if (pdata) *pdata = NULL;
1070     if (psize) *psize = 0;
1071     if (!pdata)
1072         return ERROR_INT("&data not defined", procName, 1);
1073     if (!psize)
1074         return ERROR_INT("&size not defined", procName, 1);
1075     if (!recog)
1076         return ERROR_INT("recog not defined", procName, 1);
1077 
1078 #if HAVE_FMEMOPEN
1079     if ((fp = open_memstream((char **)pdata, psize)) == NULL)
1080         return ERROR_INT("stream not opened", procName, 1);
1081     ret = recogWriteStream(fp, recog);
1082 #else
1083     L_INFO("work-around: writing to a temp file\n", procName);
1084   #ifdef _WIN32
1085     if ((fp = fopenWriteWinTempfile()) == NULL)
1086         return ERROR_INT("tmpfile stream not opened", procName, 1);
1087   #else
1088     if ((fp = tmpfile()) == NULL)
1089         return ERROR_INT("tmpfile stream not opened", procName, 1);
1090   #endif  /* _WIN32 */
1091     ret = recogWriteStream(fp, recog);
1092     rewind(fp);
1093     *pdata = l_binaryReadStream(fp, psize);
1094 #endif  /* HAVE_FMEMOPEN */
1095     fclose(fp);
1096     return ret;
1097 }
1098 
1099 
1100 /*!
1101  * \brief   recogExtractPixa()
1102  *
1103  * \param[in]   recog
1104  * \return  pixa if OK, NULL on error
1105  *
1106  * <pre>
1107  * Notes:
1108  *      (1) This generates a pixa of all the unscaled images in the
1109  *          recognizer, where each one has its character class label in
1110  *          the pix text field, by flattening pixaa_u to a pixa.
1111  * </pre>
1112  */
1113 PIXA *
recogExtractPixa(L_RECOG * recog)1114 recogExtractPixa(L_RECOG  *recog)
1115 {
1116     PROCNAME("recogExtractPixa");
1117 
1118     if (!recog)
1119         return (PIXA *)ERROR_PTR("recog not defined", procName, NULL);
1120 
1121     recogAddCharstrLabels(recog);
1122     return pixaaFlattenToPixa(recog->pixaa_u, NULL, L_CLONE);
1123 }
1124 
1125 
1126 /*!
1127  * \brief   recogAddCharstrLabels()
1128  *
1129  * \param[in]    recog
1130  * \return  0 if OK, 1 on error
1131  */
1132 static l_int32
recogAddCharstrLabels(L_RECOG * recog)1133 recogAddCharstrLabels(L_RECOG  *recog)
1134 {
1135 char    *text;
1136 l_int32  i, j, n1, n2;
1137 PIX     *pix;
1138 PIXA    *pixa;
1139 PIXAA   *paa;
1140 
1141     PROCNAME("recogAddCharstrLabels");
1142 
1143     if (!recog)
1144         return ERROR_INT("recog not defined", procName, 1);
1145 
1146         /* Add the labels to each unscaled pix */
1147     paa = recog->pixaa_u;
1148     n1 = pixaaGetCount(paa, NULL);
1149     for (i = 0; i < n1; i++) {
1150         pixa = pixaaGetPixa(paa, i, L_CLONE);
1151         text = sarrayGetString(recog->sa_text, i, L_NOCOPY);
1152         n2 = pixaGetCount(pixa);
1153         for (j = 0; j < n2; j++) {
1154              pix = pixaGetPix(pixa, j, L_CLONE);
1155              pixSetText(pix, text);
1156              pixDestroy(&pix);
1157         }
1158         pixaDestroy(&pixa);
1159     }
1160 
1161     return 0;
1162 }
1163 
1164 
1165 /*!
1166  * \brief   recogAddAllSamples()
1167  *
1168  * \param[in]    precog  addr of recog
1169  * \param[in]    paa     pixaa from previously trained recog
1170  * \param[in]    debug
1171  * \return  0 if OK, 1 on error
1172  *
1173  * <pre>
1174  * Notes:
1175  *      (1) On error, the input recog is destroyed.
1176  *      (2) This is used with the serialization routine recogRead(),
1177  *          where each pixa in the pixaa represents a set of characters
1178  *          in a different class.  Before calling this function, we have
1179  *          verified that the number of character classes, given by the
1180  *          setsize field in %recog, equals the number of pixa in the paa.
1181  *          The character labels for each set are in the sa_text field.
1182  * </pre>
1183  */
1184 static l_int32
recogAddAllSamples(L_RECOG ** precog,PIXAA * paa,l_int32 debug)1185 recogAddAllSamples(L_RECOG  **precog,
1186                    PIXAA     *paa,
1187                    l_int32    debug)
1188 {
1189 char     *text;
1190 l_int32   i, j, nc, ns;
1191 PIX      *pix;
1192 PIXA     *pixa, *pixa1;
1193 L_RECOG  *recog;
1194 
1195     PROCNAME("recogAddAllSamples");
1196 
1197     if (!precog)
1198         return ERROR_INT("&recog not defined", procName, 1);
1199     if ((recog = *precog) == NULL)
1200         return ERROR_INT("recog not defined", procName, 1);
1201     if (!paa) {
1202         recogDestroy(&recog);
1203         return ERROR_INT("paa not defined", procName, 1);
1204     }
1205 
1206     nc = pixaaGetCount(paa, NULL);
1207     for (i = 0; i < nc; i++) {
1208         pixa = pixaaGetPixa(paa, i, L_CLONE);
1209         ns = pixaGetCount(pixa);
1210         text = sarrayGetString(recog->sa_text, i, L_NOCOPY);
1211         pixa1 = pixaCreate(ns);
1212         pixaaAddPixa(recog->pixaa_u, pixa1, L_INSERT);
1213         for (j = 0; j < ns; j++) {
1214             pix = pixaGetPix(pixa, j, L_CLONE);
1215             if (debug) fprintf(stderr, "pix[%d,%d]: text = %s\n", i, j, text);
1216             pixaaAddPix(recog->pixaa_u, i, pix, NULL, L_INSERT);
1217         }
1218         pixaDestroy(&pixa);
1219     }
1220 
1221     recogTrainingFinished(&recog, 0, -1, -1.0);  /* For second parameter,
1222                                              see comment in recogRead() */
1223     if (!recog)
1224         return ERROR_INT("bad templates; recog destroyed", procName, 1);
1225     return 0;
1226 }
1227