1 /*====================================================================*
2 - Copyright (C) 2001 Leptonica. All rights reserved.
3 - This software is distributed in the hope that it will be
4 - useful, but with NO WARRANTY OF ANY KIND.
5 - No author or distributor accepts responsibility to anyone for the
6 - consequences of using this software, or for whether it serves any
7 - particular purpose or works at all, unless he or she says so in
8 - writing. Everyone is granted permission to copy, modify and
9 - redistribute this source code, for commercial or non-commercial
10 - purposes, with the following restrictions: (1) the origin of this
11 - source code must not be misrepresented; (2) modified versions must
12 - be plainly marked as such; and (3) this notice may not be removed
13 - or altered from any source or modified source distribution.
14 *====================================================================*/
15
16
17 /*
18 * jbclass.c
19 *
20 * These are functions for unsupervised classification of
21 * collections of connected components -- either characters or
22 * words -- in binary images. They can be used as image
23 * processing steps in jbig2 compression.
24 *
25 * Initialization
26 *
27 * JBCLASSER *jbRankHausInit() [rank hausdorff encoder]
28 * JBCLASSER *jbCorrelationInit() [correlation encoder]
29 * JBCLASSER *jbCorrelationInitWithoutComponents() [ditto]
30 * static JBCLASSER *jbCorrelationInitInternal()
31 *
32 * Classify the pages
33 *
34 * l_int32 jbAddPages()
35 * l_int32 jbAddPage()
36 * l_int32 jbAddPageComponents()
37 *
38 * Rank hausdorff classifier
39 *
40 * l_int32 jbClassifyRankHaus()
41 * l_int32 pixHaustest()
42 * l_int32 pixRankHaustest()
43 *
44 * Binary correlation classifier
45 *
46 * l_int32 jbClassifyCorrelation()
47 *
48 * Determine the image components we start with
49 *
50 * l_int32 jbGetComponents()
51 * PIX *pixWordMaskByDilation()
52 *
53 * Build grayscale composites (templates)
54 *
55 * PIXA *jbAccumulateComposites
56 * PIXA *jbTemplatesFromComposites
57 *
58 * Utility functions for Classer
59 *
60 * JBCLASSER *jbClasserCreate()
61 * void jbClasserDestroy()
62 *
63 * Utility functions for Data
64 *
65 * JBDATA *jbDataSave()
66 * void jbDataDestroy()
67 * l_int32 jbDataWrite()
68 * JBDATA *jbDataRead()
69 * PIXA *jbDataRender()
70 * l_int32 jbGetULCorners()
71 * l_int32 jbGetLLCorners()
72 *
73 * Static helpers
74 *
75 * static JBFINDCTX *findSimilarSizedTemplatesInit()
76 * static l_int32 findSimilarSizedTemplatesNext()
77 * static void findSimilarSizedTemplatesDestroy()
78 * static l_int32 finalPositioningForAlignment()
79 *
80 * Note: this is NOT an implementation of the JPEG jbig2
81 * proposed standard encoder, the specifications for which
82 * can be found at http://www.jpeg.org/jbigpt2.html.
83 * (See below for a full implementation.)
84 * It is an implementation of the lower-level part of an encoder that:
85 *
86 * (1) identifies connected components that are going to be used
87 * (2) puts them in similarity classes (this is an unsupervised
88 * classifier), and
89 * (3) stores the result in a simple file format (2 files,
90 * one for templates and one for page/coordinate/template-index
91 * quartets).
92 *
93 * An actual implementation of the official jbig2 encoder could
94 * start with parts (1) and (2), and would then compress the quartets
95 * according to the standards requirements (e.g., Huffman or
96 * arithmetic coding of coordinate differences and image templates).
97 *
98 * The low-level part of the encoder provided here has the
99 * following useful features:
100 *
101 * - It is accurate in the identification of templates
102 * and classes because it uses a windowed hausdorff
103 * distance metric.
104 * - It is accurate in the placement of the connected
105 * components, doing a two step process of first aligning
106 * the the centroids of the template with those of each instance,
107 * and then making a further correction of up to +- 1 pixel
108 * in each direction to best align the templates.
109 * - It is fast because it uses a morphologically based
110 * matching algorithm to implement the hausdorff criterion,
111 * and it selects the patterns that are possible matches
112 * based on their size.
113 *
114 * We provide two different matching functions, one using Hausdorff
115 * distance and one using a simple image correlation.
116 * The Hausdorff method sometimes produces better results for the
117 * same number of classes, because it gives a relatively small
118 * effective weight to foreground pixels near the boundary,
119 * and a relatively large weight to foreground pixels that are
120 * not near the boundary. By effectively ignoring these boundary
121 * pixels, Hausdorff weighting corresponds better to the expected
122 * probabilities of the pixel values in a scanned image, where the
123 * variations in instances of the same printed character are much
124 * more likely to be in pixels near the boundary. By contrast,
125 * the correlation method gives equal weight to all foreground pixels.
126 *
127 * For best results, use the correlation method. Correlation takes
128 * the number of fg pixels in the AND of instance and template,
129 * divided by the product of the number of fg pixels in instance
130 * and template. It compares this with a threshold that, in
131 * general, depends on the fractional coverage of the template.
132 * For heavy text, the threshold is raised above that for light
133 * text, By using both these parameters (basic threshold and
134 * adjustment factor for text weight), one has more flexibility
135 * and can arrive at the fewest substitution errors, although
136 * this comes at the price of more templates.
137 *
138 * The strict Hausdorff scoring is not a rank weighting, because a
139 * single pixel beyond the given distance will cause a match
140 * failure. A rank Hausdorff is more robust to non-boundary noise,
141 * but it is also more susceptible to confusing components that
142 * should be in different classes. For implementing a jbig2
143 * application for visually lossless binary image compression,
144 * you have two choices:
145 *
146 * (1) use a 3x3 structuring element (size = 3) and a strict
147 * Hausdorff comparison (rank = 1.0 in the rank Hausdorff
148 * function). This will result in a minimal number of classes,
149 * but confusion of small characters, such as italic and
150 * non-italic lower-case 'o', can still occur.
151 * (2) use the correlation method with a threshold of 0.85
152 * and a weighting factor of about 0.7. This will result in
153 * a larger number of classes, but should not be confused
154 * either by similar small characters or by extremely
155 * thick sans serif characters, such as in prog/cootoots.png.
156 *
157 * As mentioned above, if visual substitution errors must be
158 * avoided, you should use the correlation method.
159 *
160 * We provide executables that show how to do the encoding:
161 * prog/jbrankhaus.c
162 * prog/jbcorrelation.c
163 *
164 * The basic flow for correlation classification goes as follows,
165 * where specific choices have been made for parameters (Hausdorff
166 * is the same except for initialization):
167 *
168 * // Initialize and save data in the classer
169 * JBCLASSER *classer =
170 * jbCorrelationInit(JB_CONN_COMPS, 0, 0, 0.8, 0.7);
171 * SARRAY *safiles = getSortedPathnamesInDirectory(directory,
172 * NULL, 0, 0);
173 * jbAddPages(classer, safiles);
174 *
175 * // Save the data in a data structure for serialization,
176 * // and write it into two files.
177 * JBDATA *data = jbDataSave(classer);
178 * jbDataWrite(rootname, data);
179 *
180 * // Reconstruct (render) the pages from the encoded data.
181 * PIXA *pixa = jbDataRender(data, FALSE);
182 *
183 * Adam Langley has recently built a jbig2 standards-compliant
184 * encoder, the first one to appear in open source. You can get
185 * this encoder at:
186 *
187 * http://www.imperialviolet.org/jbig2.html
188 *
189 * It uses arithmetic encoding throughout. It encodes binary images
190 * losslessly with a single arithmetic coding over the full image.
191 * It also does both lossy and lossless encoding from connected
192 * components, using leptonica to generate the templates representing
193 * each cluster.
194 */
195
196 #include <stdio.h>
197 #include <stdlib.h>
198 #include <string.h>
199 #include <math.h>
200 #include "allheaders.h"
201
202 /* MSVC can't handle arrays dimensioned by static const integers */
203 #define L_BUF_SIZE 512
204
205 /* For jbClassifyRankHaus(): size of border added around
206 * pix of each c.c., to allow further processing. This
207 * should be at least the sum of the MAX_DIFF_HEIGHT
208 * (or MAX_DIFF_WIDTH) and one-half the size of the Sel */
209 static const l_int32 JB_ADDED_PIXELS = 6;
210
211 /* For pixHaustest(), pixRankHaustest() and pixCorrelationScore():
212 * choose these to be 2 or greater */
213 static const l_int32 MAX_DIFF_WIDTH = 2; /* use at least 2 */
214 static const l_int32 MAX_DIFF_HEIGHT = 2; /* use at least 2 */
215
216 /* In initialization, you have the option to discard components
217 * (cc, characters or words) that have either width or height larger
218 * than a given size. This is convenient for jbDataSave(), because
219 * the components are placed onto a regular lattice with cell
220 * dimension equal to the maximum component size. The default
221 * values are given here. If you want to save all components,
222 * use a sufficiently large set of dimensions. */
223 static const l_int32 MAX_CONN_COMP_WIDTH = 350; /* default max cc width */
224 static const l_int32 MAX_CHAR_COMP_WIDTH = 350; /* default max char width */
225 static const l_int32 MAX_WORD_COMP_WIDTH = 1000; /* default max word width */
226 static const l_int32 MAX_COMP_HEIGHT = 120; /* default max component height */
227
228 /* Max allowed dilation to merge characters into words */
229 #define MAX_ALLOWED_DILATION 14
230
231 /* This stores the state of a state machine which fetches
232 * similar sized templates */
233 struct JbFindTemplatesState
234 {
235 JBCLASSER *classer; /* classer */
236 l_int32 w; /* desired width */
237 l_int32 h; /* desired height */
238 l_int32 i; /* index into two_by_two step array */
239 NUMA *numa; /* current number array */
240 l_int32 n; /* current element of numa */
241 };
242 typedef struct JbFindTemplatesState JBFINDCTX;
243
244
245 /* Static initialization function */
246 static JBCLASSER * jbCorrelationInitInternal(l_int32 components,
247 l_int32 maxwidth, l_int32 maxheight, l_float32 thresh,
248 l_float32 weightfactor, l_int32 keep_components);
249
250 /* Static helper functions */
251 static JBFINDCTX * findSimilarSizedTemplatesInit(JBCLASSER *classer, PIX *pixs);
252 static l_int32 findSimilarSizedTemplatesNext(JBFINDCTX *context);
253 static void findSimilarSizedTemplatesDestroy(JBFINDCTX **pcontext);
254 static l_int32 finalPositioningForAlignment(PIX *pixs, l_int32 x, l_int32 y,
255 l_int32 idelx, l_int32 idely, PIX *pixt,
256 l_int32 *sumtab, l_int32 *pdx, l_int32 *pdy);
257
258 #ifndef NO_CONSOLE_IO
259 #define DEBUG_PLOT_CC 0
260 #define DEBUG_CORRELATION_SCORE 0
261 #endif /* ~NO_CONSOLE_IO */
262
263
264 /*----------------------------------------------------------------------*
265 * Initialization *
266 *----------------------------------------------------------------------*/
267 /*!
268 * jbRankHausInit()
269 *
270 * Input: components (JB_CONN_COMPS, JB_CHARACTERS, JB_WORDS)
271 * maxwidth (of component; use 0 for default)
272 * maxheight (of component; use 0 for default)
273 * size (of square structuring element; 2, representing
274 * 2x2 sel, is necessary for reasonable accuracy of
275 * small components; combine this with rank ~ 0.97
276 * to avoid undue class expansion)
277 * rank (rank val of match, each way; in [0.5 - 1.0];
278 * when using size = 2, 0.97 is a reasonable value)
279 * Return: jbclasser if OK; NULL on error
280 */
281 JBCLASSER *
jbRankHausInit(l_int32 components,l_int32 maxwidth,l_int32 maxheight,l_int32 size,l_float32 rank)282 jbRankHausInit(l_int32 components,
283 l_int32 maxwidth,
284 l_int32 maxheight,
285 l_int32 size,
286 l_float32 rank)
287 {
288 JBCLASSER *classer;
289
290 PROCNAME("jbRankHausInit");
291
292 if (components != JB_CONN_COMPS && components != JB_CHARACTERS &&
293 components != JB_WORDS)
294 return (JBCLASSER *)ERROR_PTR("invalid components", procName, NULL);
295 if (size < 1 || size > 10)
296 return (JBCLASSER *)ERROR_PTR("size not reasonable", procName, NULL);
297 if (rank < 0.5 || rank > 1.0)
298 return (JBCLASSER *)ERROR_PTR("rank not in [0.5-1.0]", procName, NULL);
299 if (maxwidth == 0) {
300 if (components == JB_CONN_COMPS)
301 maxwidth = MAX_CONN_COMP_WIDTH;
302 else if (components == JB_CHARACTERS)
303 maxwidth = MAX_CHAR_COMP_WIDTH;
304 else /* JB_WORDS */
305 maxwidth = MAX_WORD_COMP_WIDTH;
306 }
307 if (maxheight == 0)
308 maxheight = MAX_COMP_HEIGHT;
309
310 if ((classer = jbClasserCreate(JB_RANKHAUS, components)) == NULL)
311 return (JBCLASSER *)ERROR_PTR("classer not made", procName, NULL);
312 classer->maxwidth = maxwidth;
313 classer->maxheight = maxheight;
314 classer->sizehaus = size;
315 classer->rankhaus = rank;
316 classer->nahash = numaHashCreate(5507, 4); /* 5507 is prime */
317 return classer;
318 }
319
320
321 /*!
322 * jbCorrelationInit()
323 *
324 * Input: components (JB_CONN_COMPS, JB_CHARACTERS, JB_WORDS)
325 * maxwidth (of component; use 0 for default)
326 * maxheight (of component; use 0 for default)
327 * thresh (value for correlation score: in [0.4 - 0.9])
328 * weightfactor (corrects thresh for thick characters [0.0 - 1.0])
329 * Return: jbclasser if OK; NULL on error
330 */
331 JBCLASSER *
jbCorrelationInit(l_int32 components,l_int32 maxwidth,l_int32 maxheight,l_float32 thresh,l_float32 weightfactor)332 jbCorrelationInit(l_int32 components,
333 l_int32 maxwidth,
334 l_int32 maxheight,
335 l_float32 thresh,
336 l_float32 weightfactor)
337 {
338 return jbCorrelationInitInternal(components, maxwidth, maxheight, thresh,
339 weightfactor, 1);
340 }
341
342 /*!
343 * jbCorrelationInitWithoutComponents()
344 *
345 * Input: same as jbCorrelationInit
346 * Output: same as jbCorrelationInit
347 *
348 * Note: acts the same as jbCorrelationInit(), but the resulting
349 * object doesn't keep a list of all the components.
350 */
351 JBCLASSER *
jbCorrelationInitWithoutComponents(l_int32 components,l_int32 maxwidth,l_int32 maxheight,l_float32 thresh,l_float32 weightfactor)352 jbCorrelationInitWithoutComponents(l_int32 components,
353 l_int32 maxwidth,
354 l_int32 maxheight,
355 l_float32 thresh,
356 l_float32 weightfactor)
357 {
358 return jbCorrelationInitInternal(components, maxwidth, maxheight, thresh,
359 weightfactor, 0);
360 }
361
362
363 static JBCLASSER *
jbCorrelationInitInternal(l_int32 components,l_int32 maxwidth,l_int32 maxheight,l_float32 thresh,l_float32 weightfactor,l_int32 keep_components)364 jbCorrelationInitInternal(l_int32 components,
365 l_int32 maxwidth,
366 l_int32 maxheight,
367 l_float32 thresh,
368 l_float32 weightfactor,
369 l_int32 keep_components)
370 {
371 JBCLASSER *classer;
372
373 PROCNAME("jbCorrelationInitInternal");
374
375 if (components != JB_CONN_COMPS && components != JB_CHARACTERS &&
376 components != JB_WORDS)
377 return (JBCLASSER *)ERROR_PTR("invalid components", procName, NULL);
378 if (thresh < 0.4 || thresh > 0.9)
379 return (JBCLASSER *)ERROR_PTR("thresh not in range [0.4 - 0.9]",
380 procName, NULL);
381 if (weightfactor < 0.0 || weightfactor > 1.0)
382 return (JBCLASSER *)ERROR_PTR("weightfactor not in range [0.0 - 1.0]",
383 procName, NULL);
384 if (maxwidth == 0) {
385 if (components == JB_CONN_COMPS)
386 maxwidth = MAX_CONN_COMP_WIDTH;
387 else if (components == JB_CHARACTERS)
388 maxwidth = MAX_CHAR_COMP_WIDTH;
389 else /* JB_WORDS */
390 maxwidth = MAX_WORD_COMP_WIDTH;
391 }
392 if (maxheight == 0)
393 maxheight = MAX_COMP_HEIGHT;
394
395
396 if ((classer = jbClasserCreate(JB_CORRELATION, components)) == NULL)
397 return (JBCLASSER *)ERROR_PTR("classer not made", procName, NULL);
398 classer->maxwidth = maxwidth;
399 classer->maxheight = maxheight;
400 classer->thresh = thresh;
401 classer->weightfactor = weightfactor;
402 classer->nahash = numaHashCreate(5507, 4); /* 5507 is prime */
403 classer->keep_pixaa = keep_components;
404 return classer;
405 }
406
407
408 /*----------------------------------------------------------------------*
409 * Classify the pages *
410 *----------------------------------------------------------------------*/
411 /*!
412 * jbAddPages()
413 *
414 * Input: jbclasser
415 * safiles (of page image file names)
416 * Return: 0 if OK; 1 on error
417 *
418 * Note:
419 * (1) jbclasser makes a copy of the array of file names.
420 * (2) The caller is still responsible for destroying the input array.
421 */
422 l_int32
jbAddPages(JBCLASSER * classer,SARRAY * safiles)423 jbAddPages(JBCLASSER *classer,
424 SARRAY *safiles)
425 {
426 l_int32 i, nfiles;
427 char *fname;
428 PIX *pix;
429
430 PROCNAME("jbAddPages");
431
432 if (!classer)
433 return ERROR_INT("classer not defined", procName, 1);
434 if (!safiles)
435 return ERROR_INT("safiles not defined", procName, 1);
436
437 classer->safiles = sarrayCopy(safiles);
438 nfiles = sarrayGetCount(safiles);
439 for (i = 0; i < nfiles; i++) {
440 fname = sarrayGetString(safiles, i, 0);
441 if ((pix = pixRead(fname)) == NULL) {
442 L_WARNING_INT("image file %d not read", procName, i);
443 continue;
444 }
445 jbAddPage(classer, pix);
446 pixDestroy(&pix);
447 }
448
449 return 0;
450 }
451
452
453 /*!
454 * jbAddPage()
455 *
456 * Input: jbclasser
457 * pixs (of input page)
458 * Return: 0 if OK; 1 on error
459 */
460 l_int32
jbAddPage(JBCLASSER * classer,PIX * pixs)461 jbAddPage(JBCLASSER *classer,
462 PIX *pixs)
463 {
464 BOXA *boxas;
465 PIXA *pixas;
466
467 PROCNAME("jbAddPage");
468
469 if (!classer)
470 return ERROR_INT("classer not defined", procName, 1);
471 if (!pixs)
472 return ERROR_INT("pix not defined", procName, 1);
473
474 classer->w = pixGetWidth(pixs);
475 classer->h = pixGetHeight(pixs);
476
477 /* Get the appropriate components and their bounding boxes */
478 if (jbGetComponents(pixs, classer->components, classer->maxwidth,
479 classer->maxheight, &boxas, &pixas)) {
480 return ERROR_INT("components not made", procName, 1);
481 }
482
483 jbAddPageComponents(classer, pixs, boxas, pixas);
484 boxaDestroy(&boxas);
485 pixaDestroy(&pixas);
486 return 0;
487 }
488
489
490 /*!
491 * jbAddPageComponents()
492 *
493 * Input: jbclasser
494 * pixs (of input page)
495 * boxas (b.b. of components for this page)
496 * pixas (components for this page)
497 * Return: 0 if OK; 1 on error
498 *
499 * Notes:
500 * (1) If there are no components on the page, we don't require input
501 * of empty boxas or pixas, although that's the typical situation.
502 */
503 l_int32
jbAddPageComponents(JBCLASSER * classer,PIX * pixs,BOXA * boxas,PIXA * pixas)504 jbAddPageComponents(JBCLASSER *classer,
505 PIX *pixs,
506 BOXA *boxas,
507 PIXA *pixas)
508 {
509 l_int32 n;
510
511 PROCNAME("jbAddPageComponents");
512
513 if (!classer)
514 return ERROR_INT("classer not defined", procName, 1);
515 if (!pixs)
516 return ERROR_INT("pix not defined", procName, 1);
517
518 /* Test for no components on the current page. Always update the
519 * number of pages processed, even if nothing is on it. */
520 if (!boxas || !pixas || (boxaGetCount(boxas) == 0)) {
521 classer->npages++;
522 return 0;
523 }
524
525 /* Get classes. For hausdorff, it uses a specified size of
526 * structuring element and specified rank. For correlation,
527 * it uses a specified threshold. */
528 if (classer->method == JB_RANKHAUS) {
529 if (jbClassifyRankHaus(classer, boxas, pixas))
530 return ERROR_INT("rankhaus classification failed", procName, 1);
531 }
532 else { /* classer->method == JB_CORRELATION */
533 if (jbClassifyCorrelation(classer, boxas, pixas))
534 return ERROR_INT("correlation classification failed", procName, 1);
535 }
536
537 /* Find the global UL corners, adjusted for each instance so
538 * that the class template and instance will have their
539 * centroids in the same place. Then the template can be
540 * used to replace the instance. */
541 if (jbGetULCorners(classer, pixs, boxas))
542 return ERROR_INT("UL corners not found", procName, 1);
543
544 /* Update total component counts and number of pages processed. */
545 n = boxaGetCount(boxas);
546 classer->baseindex += n;
547 numaAddNumber(classer->nacomps, n);
548 classer->npages++;
549
550 return 0;
551 }
552
553
554 /*----------------------------------------------------------------------*
555 * Classification using windowed rank hausdorff metric *
556 *----------------------------------------------------------------------*/
557 /*!
558 * jbClassifyRankHaus()
559 *
560 * Input: jbclasser
561 * boxa (of new components for classification)
562 * pixas (of new components for classification)
563 * Return: 0 if OK; 1 on error
564 */
565 l_int32
jbClassifyRankHaus(JBCLASSER * classer,BOXA * boxa,PIXA * pixas)566 jbClassifyRankHaus(JBCLASSER *classer,
567 BOXA *boxa,
568 PIXA *pixas)
569 {
570 l_int32 n, nt, i, wt, ht, iclass, size, found, testval;
571 l_int32 *sumtab;
572 l_int32 npages, area1, area3;
573 l_int32 *tab8;
574 l_float32 rank, x1, y1, x2, y2;
575 BOX *box;
576 NUMA *naclass, *napage;
577 NUMA *nafg; /* fg area of all instances */
578 NUMA *nafgt; /* fg area of all templates */
579 JBFINDCTX *findcontext;
580 NUMAHASH *nahash;
581 PIX *pix, *pix1, *pix2, *pix3, *pix4;
582 PIXA *pixa, *pixa1, *pixa2, *pixat, *pixatd;
583 PIXAA *pixaa;
584 PTA *pta, *ptac, *ptact;
585 SEL *sel;
586
587 PROCNAME("jbClassifyRankHaus");
588
589 if (!classer)
590 return ERROR_INT("classer not found", procName, 1);
591 if (!boxa)
592 return ERROR_INT("boxa not found", procName, 1);
593 if (!pixas)
594 return ERROR_INT("pixas not found", procName, 1);
595
596 npages = classer->npages;
597 size = classer->sizehaus;
598 sel = selCreateBrick(size, size, size / 2, size / 2, SEL_HIT);
599
600 /* Generate the bordered pixa, with and without dilation.
601 * pixa1 and pixa2 contain all the input components. */
602 n = pixaGetCount(pixas);
603 pixa1 = pixaCreate(n);
604 pixa2 = pixaCreate(n);
605 for (i = 0; i < n; i++) {
606 pix = pixaGetPix(pixas, i, L_CLONE);
607 pix1 = pixAddBorderGeneral(pix, JB_ADDED_PIXELS, JB_ADDED_PIXELS,
608 JB_ADDED_PIXELS, JB_ADDED_PIXELS, 0);
609 pix2 = pixDilate(NULL, pix1, sel);
610 pixaAddPix(pixa1, pix1, L_INSERT); /* un-dilated */
611 pixaAddPix(pixa2, pix2, L_INSERT); /* dilated */
612 pixDestroy(&pix);
613 }
614
615 /* Get the centroids of all the bordered images.
616 * These are relative to the UL corner of each (bordered) pix. */
617 pta = pixaCentroids(pixa1); /* centroids for this page; use here */
618 ptac = classer->ptac; /* holds centroids of components up to this page */
619 ptaJoin(ptac, pta, 0, 0); /* save centroids of all components */
620 ptact = classer->ptact; /* holds centroids of templates */
621
622 /* Use these to save the class and page of each component. */
623 naclass = classer->naclass;
624 napage = classer->napage;
625 sumtab = makePixelSumTab8();
626
627 /* Store the unbordered pix in a pixaa, in a hierarchical
628 * set of arrays. There is one pixa for each class,
629 * and the pix in each pixa are all the instances found
630 * of that class. This is actually more than one would need
631 * for a jbig2 encoder, but there are two reasons to keep
632 * them around: (1) the set of instances for each class
633 * can be used to make an improved binary (or, better,
634 * a grayscale) template, rather than simply using the first
635 * one in the set; (2) we can investigate the failures
636 * of the classifier. This pixaa grows as we process
637 * successive pages. */
638 pixaa = classer->pixaa;
639
640 /* arrays to store class exemplars (templates) */
641 pixat = classer->pixat; /* un-dilated */
642 pixatd = classer->pixatd; /* dilated */
643
644 /* Fill up the pixaa tree with the template exemplars as
645 * the first pix in each pixa. As we add each pix,
646 * we also add the associated box to the pixa.
647 * We also keep track of the centroid of each pix,
648 * and use the difference between centroids (of the
649 * pix with the exemplar we are checking it with)
650 * to align the two when checking that the Hausdorff
651 * distance does not exceed a threshold.
652 * The threshold is set by the Sel used for dilating.
653 * For example, a 3x3 brick, sel_3, corresponds to a
654 * Hausdorff distance of 1. In general, for an NxN brick,
655 * with N odd, corresponds to a Hausdorff distance of (N - 1)/2.
656 * It turns out that we actually need to use a sel of size 2x2
657 * to avoid small bad components when there is a halftone image
658 * from which components can be chosen.
659 * The larger the Sel you use, the fewer the number of classes,
660 * and the greater the likelihood of putting semantically
661 * different objects in the same class. For simplicity,
662 * we do this separately for the case of rank == 1.0 (exact
663 * match within the Hausdorff distance) and rank < 1.0. */
664 rank = classer->rankhaus;
665 nahash = classer->nahash;
666 if (rank == 1.0) {
667 for (i = 0; i < n; i++) {
668 pix1 = pixaGetPix(pixa1, i, L_CLONE);
669 pix2 = pixaGetPix(pixa2, i, L_CLONE);
670 ptaGetPt(pta, i, &x1, &y1);
671 nt = pixaGetCount(pixat); /* number of templates */
672 found = FALSE;
673 findcontext = findSimilarSizedTemplatesInit(classer, pix1);
674 while ((iclass = findSimilarSizedTemplatesNext(findcontext)) > -1) {
675 /* Find score for this template */
676 pix3 = pixaGetPix(pixat, iclass, L_CLONE);
677 pix4 = pixaGetPix(pixatd, iclass, L_CLONE);
678 ptaGetPt(ptact, iclass, &x2, &y2);
679 testval = pixHaustest(pix1, pix2, pix3, pix4, x1 - x2, y1 - y2,
680 MAX_DIFF_WIDTH, MAX_DIFF_HEIGHT);
681 pixDestroy(&pix3);
682 pixDestroy(&pix4);
683 if (testval == 1) {
684 found = TRUE;
685 numaAddNumber(naclass, iclass);
686 numaAddNumber(napage, npages);
687 if (classer->keep_pixaa) {
688 pixa = pixaaGetPixa(pixaa, iclass, L_CLONE);
689 pix = pixaGetPix(pixas, i, L_CLONE);
690 pixaAddPix(pixa, pix, L_INSERT);
691 box = boxaGetBox(boxa, i, L_CLONE);
692 pixaAddBox(pixa, box, L_INSERT);
693 pixaDestroy(&pixa);
694 }
695 break;
696 }
697 }
698 findSimilarSizedTemplatesDestroy(&findcontext);
699 if (found == FALSE) { /* new class */
700 numaAddNumber(naclass, nt);
701 numaAddNumber(napage, npages);
702 pixa = pixaCreate(0);
703 pix = pixaGetPix(pixas, i, L_CLONE); /* unbordered instance */
704 pixaAddPix(pixa, pix, L_INSERT);
705 wt = pixGetWidth(pix);
706 ht = pixGetHeight(pix);
707 numaHashAdd(nahash, ht * wt, nt);
708 box = boxaGetBox(boxa, i, L_CLONE);
709 pixaAddBox(pixa, box, L_INSERT);
710 pixaaAddPixa(pixaa, pixa, L_INSERT); /* unbordered instance */
711 ptaAddPt(ptact, x1, y1);
712 pixaAddPix(pixat, pix1, L_INSERT); /* bordered template */
713 pixaAddPix(pixatd, pix2, L_INSERT); /* bordered dil template */
714 }
715 else { /* don't save them */
716 pixDestroy(&pix1);
717 pixDestroy(&pix2);
718 }
719 }
720 }
721 else { /* rank < 1.0 */
722 if ((nafg = pixaCountPixels(pixas)) == NULL) /* areas for this page */
723 return ERROR_INT("nafg not made", procName, 1);
724 nafgt = classer->nafgt;
725 tab8 = makePixelSumTab8();
726 for (i = 0; i < n; i++) { /* all instances on this page */
727 pix1 = pixaGetPix(pixa1, i, L_CLONE);
728 numaGetIValue(nafg, i, &area1);
729 pix2 = pixaGetPix(pixa2, i, L_CLONE);
730 ptaGetPt(pta, i, &x1, &y1); /* use pta for this page */
731 nt = pixaGetCount(pixat); /* number of templates */
732 found = FALSE;
733 findcontext = findSimilarSizedTemplatesInit(classer, pix1);
734 while ((iclass = findSimilarSizedTemplatesNext(findcontext)) > -1) {
735 /* Find score for this template */
736 pix3 = pixaGetPix(pixat, iclass, L_CLONE);
737 numaGetIValue(nafgt, iclass, &area3);
738 pix4 = pixaGetPix(pixatd, iclass, L_CLONE);
739 ptaGetPt(ptact, iclass, &x2, &y2);
740 testval = pixRankHaustest(pix1, pix2, pix3, pix4,
741 x1 - x2, y1 - y2,
742 MAX_DIFF_WIDTH, MAX_DIFF_HEIGHT,
743 area1, area3, rank, tab8);
744 pixDestroy(&pix3);
745 pixDestroy(&pix4);
746 if (testval == 1) { /* greedy match; take the first */
747 found = TRUE;
748 numaAddNumber(naclass, iclass);
749 numaAddNumber(napage, npages);
750 if (classer->keep_pixaa) {
751 pixa = pixaaGetPixa(pixaa, iclass, L_CLONE);
752 pix = pixaGetPix(pixas, i, L_CLONE);
753 pixaAddPix(pixa, pix, L_INSERT);
754 box = boxaGetBox(boxa, i, L_CLONE);
755 pixaAddBox(pixa, box, L_INSERT);
756 pixaDestroy(&pixa);
757 }
758 break;
759 }
760 }
761 findSimilarSizedTemplatesDestroy(&findcontext);
762 if (found == FALSE) { /* new class */
763 numaAddNumber(naclass, nt);
764 numaAddNumber(napage, npages);
765 pixa = pixaCreate(0);
766 pix = pixaGetPix(pixas, i, L_CLONE); /* unbordered instance */
767 pixaAddPix(pixa, pix, L_INSERT);
768 wt = pixGetWidth(pix);
769 ht = pixGetHeight(pix);
770 numaHashAdd(nahash, ht * wt, nt);
771 box = boxaGetBox(boxa, i, L_CLONE);
772 pixaAddBox(pixa, box, L_INSERT);
773 pixaaAddPixa(pixaa, pixa, L_INSERT); /* unbordered instance */
774 ptaAddPt(ptact, x1, y1);
775 pixaAddPix(pixat, pix1, L_INSERT); /* bordered template */
776 pixaAddPix(pixatd, pix2, L_INSERT); /* ditto */
777 numaAddNumber(nafgt, area1);
778 }
779 else { /* don't save them */
780 pixDestroy(&pix1);
781 pixDestroy(&pix2);
782 }
783 }
784 FREE(tab8);
785 numaDestroy(&nafg);
786 }
787 classer->nclass = pixaGetCount(pixat);
788
789 FREE(sumtab);
790 ptaDestroy(&pta);
791 pixaDestroy(&pixa1);
792 pixaDestroy(&pixa2);
793 selDestroy(&sel);
794 return 0;
795 }
796
797
798 /*!
799 * pixHaustest()
800 *
801 * Input: pix1 (new pix, not dilated)
802 * pix2 (new pix, dilated)
803 * pix3 (exemplar pix, not dilated)
804 * pix4 (exemplar pix, dilated)
805 * delx (x comp of centroid difference)
806 * dely (y comp of centroid difference)
807 * maxdiffw (max width difference of pix1 and pix2)
808 * maxdiffh (max height difference of pix1 and pix2)
809 * Return: 0 (FALSE) if no match, 1 (TRUE) if the new
810 * pix is in the same class as the exemplar.
811 *
812 * Note: we check first that the two pix are roughly
813 * the same size. Only if they meet that criterion do
814 * we compare the bitmaps. The Hausdorff is a 2-way
815 * check. The centroid difference is used to align the two
816 * images to the nearest integer for each of the checks.
817 * These check that the dilated image of one contains
818 * ALL the pixels of the undilated image of the other.
819 * Checks are done in both direction. A single pixel not
820 * contained in either direction results in failure of the test.
821 */
822 l_int32
pixHaustest(PIX * pix1,PIX * pix2,PIX * pix3,PIX * pix4,l_float32 delx,l_float32 dely,l_int32 maxdiffw,l_int32 maxdiffh)823 pixHaustest(PIX *pix1,
824 PIX *pix2,
825 PIX *pix3,
826 PIX *pix4,
827 l_float32 delx, /* x(1) - x(3) */
828 l_float32 dely, /* y(1) - y(3) */
829 l_int32 maxdiffw,
830 l_int32 maxdiffh)
831 {
832 l_int32 wi, hi, wt, ht, delw, delh, idelx, idely, boolmatch;
833 PIX *pixt;
834
835 /* Eliminate possible matches based on size difference */
836 wi = pixGetWidth(pix1);
837 hi = pixGetHeight(pix1);
838 wt = pixGetWidth(pix3);
839 ht = pixGetHeight(pix3);
840 delw = L_ABS(wi - wt);
841 if (delw > maxdiffw)
842 return FALSE;
843 delh = L_ABS(hi - ht);
844 if (delh > maxdiffh)
845 return FALSE;
846
847 /* Round difference in centroid location to nearest integer;
848 * use this as a shift when doing the matching. */
849 if (delx >= 0)
850 idelx = (l_int32)(delx + 0.5);
851 else
852 idelx = (l_int32)(delx - 0.5);
853 if (dely >= 0)
854 idely = (l_int32)(dely + 0.5);
855 else
856 idely = (l_int32)(dely - 0.5);
857
858 /* Do 1-direction hausdorff, checking that every pixel in pix1
859 * is within a dilation distance of some pixel in pix3. Namely,
860 * that pix4 entirely covers pix1:
861 * pixt = pixSubtract(NULL, pix1, pix4), including shift
862 * where pixt has no ON pixels. */
863 pixt = pixCreateTemplate(pix1);
864 pixRasterop(pixt, 0, 0, wi, hi, PIX_SRC, pix1, 0, 0);
865 pixRasterop(pixt, idelx, idely, wi, hi, PIX_DST & PIX_NOT(PIX_SRC),
866 pix4, 0, 0);
867 pixZero(pixt, &boolmatch);
868 if (boolmatch == 0) {
869 pixDestroy(&pixt);
870 return FALSE;
871 }
872
873 /* Do 1-direction hausdorff, checking that every pixel in pix3
874 * is within a dilation distance of some pixel in pix1. Namely,
875 * that pix2 entirely covers pix3:
876 * pixSubtract(pixt, pix3, pix2), including shift
877 * where pixt has no ON pixels. */
878 pixRasterop(pixt, idelx, idely, wt, ht, PIX_SRC, pix3, 0, 0);
879 pixRasterop(pixt, 0, 0, wt, ht, PIX_DST & PIX_NOT(PIX_SRC), pix2, 0, 0);
880 pixZero(pixt, &boolmatch);
881 pixDestroy(&pixt);
882 return boolmatch;
883 }
884
885
886 /*!
887 * pixRankHaustest()
888 *
889 * Input: pix1 (new pix, not dilated)
890 * pix2 (new pix, dilated)
891 * pix3 (exemplar pix, not dilated)
892 * pix4 (exemplar pix, dilated)
893 * delx (x comp of centroid difference)
894 * dely (y comp of centroid difference)
895 * maxdiffw (max width difference of pix1 and pix2)
896 * maxdiffh (max height difference of pix1 and pix2)
897 * area1 (fg pixels in pix1)
898 * area3 (fg pixels in pix3)
899 * rank (rank value of test, each way)
900 * tab8 (table of pixel sums for byte)
901 * Return: 0 (FALSE) if no match, 1 (TRUE) if the new
902 * pix is in the same class as the exemplar.
903 *
904 * Note: we check first that the two pix are roughly
905 * the same size. Only if they meet that criterion do
906 * we compare the bitmaps. We convert the rank value to
907 * a number of pixels by multiplying the rank fraction by the number
908 * of pixels in the undilated image. The Hausdorff is a 2-way
909 * check. The centroid difference is used to align the two
910 * images to the nearest integer for each of the checks.
911 * The rank hausdorff checks that the dilated image of one
912 * contains the rank fraction of the pixels of the undilated
913 * image of the other. Checks are done in both direction.
914 * Failure of the test in either direction results in failure
915 * of the test.
916 */
917 l_int32
pixRankHaustest(PIX * pix1,PIX * pix2,PIX * pix3,PIX * pix4,l_float32 delx,l_float32 dely,l_int32 maxdiffw,l_int32 maxdiffh,l_int32 area1,l_int32 area3,l_float32 rank,l_int32 * tab8)918 pixRankHaustest(PIX *pix1,
919 PIX *pix2,
920 PIX *pix3,
921 PIX *pix4,
922 l_float32 delx, /* x(1) - x(3) */
923 l_float32 dely, /* y(1) - y(3) */
924 l_int32 maxdiffw,
925 l_int32 maxdiffh,
926 l_int32 area1,
927 l_int32 area3,
928 l_float32 rank,
929 l_int32 *tab8)
930 {
931 l_int32 wi, hi, wt, ht, delw, delh, idelx, idely, boolmatch;
932 l_int32 thresh1, thresh3;
933 PIX *pixt;
934
935 /* Eliminate possible matches based on size difference */
936 wi = pixGetWidth(pix1);
937 hi = pixGetHeight(pix1);
938 wt = pixGetWidth(pix3);
939 ht = pixGetHeight(pix3);
940 delw = L_ABS(wi - wt);
941 if (delw > maxdiffw)
942 return FALSE;
943 delh = L_ABS(hi - ht);
944 if (delh > maxdiffh)
945 return FALSE;
946
947 /* Upper bounds in remaining pixels for allowable match */
948 thresh1 = (l_int32)(area1 * (1. - rank) + 0.5);
949 thresh3 = (l_int32)(area3 * (1. - rank) + 0.5);
950
951 /* Round difference in centroid location to nearest integer;
952 * use this as a shift when doing the matching. */
953 if (delx >= 0)
954 idelx = (l_int32)(delx + 0.5);
955 else
956 idelx = (l_int32)(delx - 0.5);
957 if (dely >= 0)
958 idely = (l_int32)(dely + 0.5);
959 else
960 idely = (l_int32)(dely - 0.5);
961
962 /* Do 1-direction hausdorff, checking that every pixel in pix1
963 * is within a dilation distance of some pixel in pix3. Namely,
964 * that pix4 entirely covers pix1:
965 * pixt = pixSubtract(NULL, pix1, pix4), including shift
966 * where pixt has no ON pixels. */
967 pixt = pixCreateTemplate(pix1);
968 pixRasterop(pixt, 0, 0, wi, hi, PIX_SRC, pix1, 0, 0);
969 pixRasterop(pixt, idelx, idely, wi, hi, PIX_DST & PIX_NOT(PIX_SRC),
970 pix4, 0, 0);
971 pixThresholdPixelSum(pixt, thresh1, &boolmatch, tab8);
972 if (boolmatch == 1) { /* above thresh1 */
973 pixDestroy(&pixt);
974 return FALSE;
975 }
976
977 /* Do 1-direction hausdorff, checking that every pixel in pix3
978 * is within a dilation distance of some pixel in pix1. Namely,
979 * that pix2 entirely covers pix3:
980 * pixSubtract(pixt, pix3, pix2), including shift
981 * where pixt has no ON pixels. */
982 pixRasterop(pixt, idelx, idely, wt, ht, PIX_SRC, pix3, 0, 0);
983 pixRasterop(pixt, 0, 0, wt, ht, PIX_DST & PIX_NOT(PIX_SRC), pix2, 0, 0);
984 pixThresholdPixelSum(pixt, thresh3, &boolmatch, tab8);
985 pixDestroy(&pixt);
986 if (boolmatch == 1) /* above thresh3 */
987 return FALSE;
988 else
989 return TRUE;
990 }
991
992
993 /*----------------------------------------------------------------------*
994 * Classification using windowed correlation score *
995 *----------------------------------------------------------------------*/
996 /*!
997 * jbClassifyCorrelation()
998 *
999 * Input: jbclasser
1000 * boxa (of new components for classification)
1001 * pixas (of new components for classification)
1002 * Return: 0 if OK; 1 on error
1003 */
1004 l_int32
jbClassifyCorrelation(JBCLASSER * classer,BOXA * boxa,PIXA * pixas)1005 jbClassifyCorrelation(JBCLASSER *classer,
1006 BOXA *boxa,
1007 PIXA *pixas)
1008 {
1009 l_int32 n, nt, i, iclass, wt, ht, found, area, area1, area2, npages,
1010 overthreshold;
1011 l_int32 *sumtab, *centtab;
1012 l_uint32 *row, word;
1013 l_float32 x1, y1, x2, y2, xsum, ysum;
1014 l_float32 thresh, weight, threshold;
1015 BOX *box;
1016 NUMA *naclass, *napage;
1017 NUMA *nafgt; /* fg area of all templates */
1018 NUMA *naarea; /* w * h area of all templates */
1019 JBFINDCTX *findcontext;
1020 NUMAHASH *nahash;
1021 PIX *pix, *pix1, *pix2;
1022 PIXA *pixa, *pixa1, *pixat;
1023 PIXAA *pixaa;
1024 PTA *pta, *ptac, *ptact;
1025 l_int32 *pixcts; /* pixel counts of each pixa */
1026 l_int32 **pixrowcts; /* row-by-row pixel counts of each pixa */
1027 l_int32 x, y, rowcount, downcount, wpl;
1028 l_uint8 byte;
1029
1030 PROCNAME("jbClassifyCorrelation");
1031
1032 if (!classer)
1033 return ERROR_INT("classer not found", procName, 1);
1034 if (!boxa)
1035 return ERROR_INT("boxa not found", procName, 1);
1036 if (!pixas)
1037 return ERROR_INT("pixas not found", procName, 1);
1038
1039 npages = classer->npages;
1040
1041 /* Generate the bordered pixa, which contains all the the
1042 * input components. This will not be saved. */
1043 n = pixaGetCount(pixas);
1044 pixa1 = pixaCreate(n);
1045 for (i = 0; i < n; i++) {
1046 pix = pixaGetPix(pixas, i, L_CLONE);
1047 pix1 = pixAddBorderGeneral(pix, JB_ADDED_PIXELS, JB_ADDED_PIXELS,
1048 JB_ADDED_PIXELS, JB_ADDED_PIXELS, 0);
1049 pixaAddPix(pixa1, pix1, L_INSERT);
1050 pixDestroy(&pix);
1051 }
1052
1053 /* Use these to save the class and page of each component. */
1054 naclass = classer->naclass;
1055 napage = classer->napage;
1056
1057 /* Get the number of fg pixels in each component. */
1058 nafgt = classer->nafgt; /* holds fg areas of the templates */
1059 sumtab = makePixelSumTab8();
1060
1061 pixcts = (l_int32 *)CALLOC(n, sizeof(*pixcts));
1062 pixrowcts = (l_int32 **)CALLOC(n, sizeof(*pixrowcts));
1063 centtab = makePixelCentroidTab8();
1064 if (!pixcts || !pixrowcts || !centtab)
1065 return ERROR_INT("calloc fail in pix*cts or centtab", procName, 1);
1066
1067 /* Count the "1" pixels in each row of the pix in pixa1; this
1068 * allows pixCorrelationScoreThresholded to abort early if a match
1069 * is impossible. This loop merges three calculations: the total
1070 * number of "1" pixels, the number of "1" pixels in each row, and
1071 * the centroid. The centroids are relative to the UL corner of
1072 * each (bordered) pix. The pixrowcts[i][y] are the total number
1073 * of fg pixels in pixa[i] below row y. */
1074 pta = ptaCreate(n);
1075 for (i = 0; i < n; i++) {
1076 pix = pixaGetPix(pixa1, i, L_CLONE);
1077 pixrowcts[i] = (l_int32 *)CALLOC(pixGetHeight(pix),
1078 sizeof(**pixrowcts));
1079 xsum = 0;
1080 ysum = 0;
1081 wpl = pixGetWpl(pix);
1082 row = pixGetData(pix) + (pixGetHeight(pix) - 1) * wpl;
1083 downcount = 0;
1084 for (y = pixGetHeight(pix) - 1; y >= 0; y--, row -= wpl) {
1085 pixrowcts[i][y] = downcount;
1086 rowcount = 0;
1087 for (x = 0; x < wpl; x++) {
1088 word = row[x];
1089 byte = word & 0xff;
1090 rowcount += sumtab[byte];
1091 xsum += centtab[byte] + (x * 32 + 24) * sumtab[byte];
1092 byte = (word >> 8) & 0xff;
1093 rowcount += sumtab[byte];
1094 xsum += centtab[byte] + (x * 32 + 16) * sumtab[byte];
1095 byte = (word >> 16) & 0xff;
1096 rowcount += sumtab[byte];
1097 xsum += centtab[byte] + (x * 32 + 8) * sumtab[byte];
1098 byte = (word >> 24) & 0xff;
1099 rowcount += sumtab[byte];
1100 xsum += centtab[byte] + x * 32 * sumtab[byte];
1101 }
1102 downcount += rowcount;
1103 ysum += rowcount * y;
1104 }
1105 pixcts[i] = downcount;
1106 ptaAddPt(pta,
1107 xsum / (l_float32)downcount, ysum / (l_float32)downcount);
1108 pixDestroy(&pix);
1109 }
1110
1111 ptac = classer->ptac; /* holds centroids of components up to this page */
1112 ptaJoin(ptac, pta, 0, 0); /* save centroids of all components */
1113 ptact = classer->ptact; /* holds centroids of templates */
1114
1115 /* Store the unbordered pix in a pixaa, in a hierarchical
1116 * set of arrays. There is one pixa for each class,
1117 * and the pix in each pixa are all the instances found
1118 * of that class. This is actually more than one would need
1119 * for a jbig2 encoder, but there are two reasons to keep
1120 * them around: (1) the set of instances for each class
1121 * can be used to make an improved binary (or, better,
1122 * a grayscale) template, rather than simply using the first
1123 * one in the set; (2) we can investigate the failures
1124 * of the classifier. This pixaa grows as we process
1125 * successive pages. */
1126 pixaa = classer->pixaa;
1127
1128 /* Array to store class exemplars */
1129 pixat = classer->pixat;
1130
1131 /* Fill up the pixaa tree with the template exemplars as
1132 * the first pix in each pixa. As we add each pix,
1133 * we also add the associated box to the pixa.
1134 * We also keep track of the centroid of each pix,
1135 * and use the difference between centroids (of the
1136 * pix with the exemplar we are checking it with)
1137 * to align the two when checking that the correlation
1138 * score exceeds a threshold. The correlation score
1139 * is given by the square of the area of the AND
1140 * between aligned instance and template, divided by
1141 * the product of areas of each image. For identical
1142 * template and instance, the score is 1.0.
1143 * If the threshold is too small, non-equivalent instances
1144 * will be placed in the same class; if too large, there will
1145 * be an unnecessary division of classes representing the
1146 * same character. The weightfactor adds in some of the
1147 * difference (1.0 - thresh), depending on the heaviness
1148 * of the template (measured as the fraction of fg pixels). */
1149 thresh = classer->thresh;
1150 weight = classer->weightfactor;
1151 naarea = classer->naarea;
1152 nahash = classer->nahash;
1153 for (i = 0; i < n; i++) {
1154 pix1 = pixaGetPix(pixa1, i, L_CLONE);
1155 area1 = pixcts[i];
1156 ptaGetPt(pta, i, &x1, &y1); /* centroid for this instance */
1157 nt = pixaGetCount(pixat);
1158 found = FALSE;
1159 findcontext = findSimilarSizedTemplatesInit(classer, pix1);
1160 while ( (iclass = findSimilarSizedTemplatesNext(findcontext)) > -1) {
1161 /* Get the template */
1162 pix2 = pixaGetPix(pixat, iclass, L_CLONE);
1163 numaGetIValue(nafgt, iclass, &area2);
1164 ptaGetPt(ptact, iclass, &x2, &y2); /* template centroid */
1165
1166 /* Find threshold for this template */
1167 if (weight > 0.0) {
1168 numaGetIValue(naarea, iclass, &area);
1169 threshold = thresh + (1. - thresh) * weight * area2 / area;
1170 }
1171 else
1172 threshold = thresh;
1173
1174 /* Find score for this template */
1175 overthreshold = pixCorrelationScoreThresholded(pix1, pix2,
1176 area1, area2,
1177 x1 - x2, y1 - y2,
1178 MAX_DIFF_WIDTH,
1179 MAX_DIFF_HEIGHT,
1180 sumtab,
1181 pixrowcts[i],
1182 threshold);
1183 #if DEBUG_CORRELATION_SCORE
1184 {
1185 l_float32 score, testscore;
1186 l_int32 count, testcount;
1187 score = pixCorrelationScore(pix1, pix2, area1, area2,
1188 x1 - x2, y1 - y2,
1189 MAX_DIFF_WIDTH, MAX_DIFF_HEIGHT,
1190 sumtab);
1191
1192 testscore = pixCorrelationScoreSimple(pix1, pix2, area1, area2,
1193 x1 - x2, y1 - y2, MAX_DIFF_WIDTH,
1194 MAX_DIFF_HEIGHT, sumtab);
1195 count = (l_int32)rint(sqrt(score * area1 * area2));
1196 testcount = (l_int32)rint(sqrt(testscore * area1 * area2));
1197 if ((score >= threshold) != (testscore >= threshold)) {
1198 fprintf(stderr, "Correlation score mismatch: %d(%g,%d) vs %d(%g,%d) (%g)\n",
1199 count, score, score >= threshold,
1200 testcount, testscore, testscore >= threshold,
1201 score - testscore);
1202 }
1203
1204 if ((score >= threshold) != overthreshold) {
1205 fprintf(stderr, "Mismatch between correlation/threshold comparison: %g(%g,%d) >= %g(%g) vs %s\n",
1206 score, score*area1*area2, count, threshold, threshold*area1*area2, (overthreshold ? "true" : "false"));
1207 }
1208 }
1209 #endif /* DEBUG_CORRELATION_SCORE */
1210 pixDestroy(&pix2);
1211
1212 if (overthreshold) { /* greedy match */
1213 found = TRUE;
1214 numaAddNumber(naclass, iclass);
1215 numaAddNumber(napage, npages);
1216 if (classer->keep_pixaa) {
1217 /* We are keeping a record of all components */
1218 pixa = pixaaGetPixa(pixaa, iclass, L_CLONE);
1219 pix = pixaGetPix(pixas, i, L_CLONE);
1220 pixaAddPix(pixa, pix, L_INSERT);
1221 box = boxaGetBox(boxa, i, L_CLONE);
1222 pixaAddBox(pixa, box, L_INSERT);
1223 pixaDestroy(&pixa);
1224 }
1225 break;
1226 }
1227 }
1228 findSimilarSizedTemplatesDestroy(&findcontext);
1229 if (found == FALSE) { /* new class */
1230 numaAddNumber(naclass, nt);
1231 numaAddNumber(napage, npages);
1232 pixa = pixaCreate(0);
1233 pix = pixaGetPix(pixas, i, L_CLONE); /* unbordered instance */
1234 pixaAddPix(pixa, pix, L_INSERT);
1235 wt = pixGetWidth(pix);
1236 ht = pixGetHeight(pix);
1237 numaHashAdd(nahash, ht * wt, nt);
1238 box = boxaGetBox(boxa, i, L_CLONE);
1239 pixaAddBox(pixa, box, L_INSERT);
1240 pixaaAddPixa(pixaa, pixa, L_INSERT); /* unbordered instance */
1241 ptaAddPt(ptact, x1, y1);
1242 numaAddNumber(nafgt, area1);
1243 pixaAddPix(pixat, pix1, L_INSERT); /* bordered template */
1244 area = (pixGetWidth(pix1) - 2 * JB_ADDED_PIXELS) *
1245 (pixGetHeight(pix1) - 2 * JB_ADDED_PIXELS);
1246 numaAddNumber(naarea, area);
1247 }
1248 else { /* don't save it */
1249 pixDestroy(&pix1);
1250 }
1251 }
1252 classer->nclass = pixaGetCount(pixat);
1253
1254 FREE(pixcts);
1255 FREE(centtab);
1256 for (i = 0; i < n; i++) {
1257 FREE(pixrowcts[i]);
1258 }
1259 FREE(pixrowcts);
1260
1261 FREE(sumtab);
1262 ptaDestroy(&pta);
1263 pixaDestroy(&pixa1);
1264 return 0;
1265 }
1266
1267
1268 /*----------------------------------------------------------------------*
1269 * Determine the image components we start with *
1270 *----------------------------------------------------------------------*/
1271 /*!
1272 * jbGetComponents()
1273 *
1274 * Input: pixs (1 bpp)
1275 * components (JB_CONN_COMPS, JB_CHARACTERS, JB_WORDS)
1276 * maxwidth, maxheight (of saved components; larger are discarded)
1277 * &pboxa (<return> b.b. of component items)
1278 * &ppixa (<return> component items)
1279 * Return: 0 if OK, 1 on error
1280 */
1281 l_int32
jbGetComponents(PIX * pixs,l_int32 components,l_int32 maxwidth,l_int32 maxheight,BOXA ** pboxad,PIXA ** ppixad)1282 jbGetComponents(PIX *pixs,
1283 l_int32 components,
1284 l_int32 maxwidth,
1285 l_int32 maxheight,
1286 BOXA **pboxad,
1287 PIXA **ppixad)
1288 {
1289 l_int32 empty, res, redfactor;
1290 BOXA *boxa;
1291 PIX *pixt1, *pixt2, *pixt3;
1292 PIXA *pixa, *pixat;
1293
1294 PROCNAME("jbGetComponents");
1295
1296 if (!pboxad)
1297 return ERROR_INT("&boxad not defined", procName, 1);
1298 *pboxad = NULL;
1299 if (!ppixad)
1300 return ERROR_INT("&pixad not defined", procName, 1);
1301 *ppixad = NULL;
1302 if (!pixs)
1303 return ERROR_INT("pixs not defined", procName, 1);
1304 if (components != JB_CONN_COMPS && components != JB_CHARACTERS &&
1305 components != JB_WORDS)
1306 return ERROR_INT("invalid components", procName, 1);
1307
1308 pixZero(pixs, &empty);
1309 if (empty) {
1310 *pboxad = boxaCreate(0);
1311 *ppixad = pixaCreate(0);
1312 return 0;
1313 }
1314
1315 /* If required, preprocess input pixs. The method for both
1316 * characters and words is to generate a connected component
1317 * mask over the units that we want to aggregrate, which are,
1318 * in general, sets of related connected components in pixs.
1319 * For characters, we want to include the dots with
1320 * 'i', 'j' and '!', so we do a small vertical closing to
1321 * generate the mask. For words, we make a mask over all
1322 * characters in each word. This is a bit more tricky, because
1323 * the spacing between words is difficult to predict a priori,
1324 * and words can be typeset with variable spacing that can
1325 * in some cases be barely larger than the space between
1326 * characters. The first step is to generate the mask and
1327 * identify each of its connected components. */
1328 if (components == JB_CONN_COMPS) { /* no preprocessing */
1329 boxa = pixConnComp(pixs, &pixa, 8);
1330 }
1331 else if (components == JB_CHARACTERS) {
1332 pixt1 = pixMorphSequence(pixs, "c1.6", 0);
1333 boxa = pixConnComp(pixt1, &pixat, 8);
1334 pixa = pixaClipToPix(pixat, pixs);
1335 pixDestroy(&pixt1);
1336 pixaDestroy(&pixat);
1337 }
1338 else { /* components == JB_WORDS */
1339
1340 /* Do the operations at about 150 ppi resolution.
1341 * It is much faster at 75 ppi, but the results are
1342 * more accurate at 150 ppi. This will segment the
1343 * words in body text. It can be expected that relatively
1344 * infrequent words in a larger font will be split. */
1345 res = pixGetXRes(pixs);
1346 if (res <= 200) {
1347 redfactor = 1;
1348 pixt1 = pixClone(pixs);
1349 }
1350 else if (res <= 400) {
1351 redfactor = 2;
1352 pixt1 = pixReduceRankBinaryCascade(pixs, 1, 0, 0, 0);
1353 }
1354 else {
1355 redfactor = 4;
1356 pixt1 = pixReduceRankBinaryCascade(pixs, 1, 1, 0, 0);
1357 }
1358
1359 pixt2 = pixWordMaskByDilation(pixt1, 0, NULL);
1360
1361 /* Expand the optimally dilated word mask to full res. */
1362 pixt3 = pixExpandReplicate(pixt2, redfactor);
1363
1364 /* Pull out the pixels in pixs corresponding to the mask
1365 * components in pixt3. Note that above we used threshold
1366 * levels in the reduction of 1 to insure that the resulting
1367 * mask fully covers the input pixs. The downside of using
1368 * a threshold of 1 is that very close characters from adjacent
1369 * lines can be joined. But with a level of 2 or greater,
1370 * it is necessary to use a seedfill, followed by a pixOr():
1371 * pixt4 = pixSeedfillBinary(NULL, pixt3, pixs, 8);
1372 * pixOr(pixt3, pixt3, pixt4);
1373 * to insure that the mask coverage is complete over pixs. */
1374 boxa = pixConnComp(pixt3, &pixat, 4);
1375 pixa = pixaClipToPix(pixat, pixs);
1376 pixaDestroy(&pixat);
1377 pixDestroy(&pixt1);
1378 pixDestroy(&pixt2);
1379 pixDestroy(&pixt3);
1380 }
1381
1382 /* Remove large components, and save the results. */
1383 *ppixad = pixaSelectBySize(pixa, maxwidth, maxheight, L_SELECT_IF_BOTH,
1384 L_SELECT_IF_LTE, NULL);
1385 *pboxad = boxaSelectBySize(boxa, maxwidth, maxheight, L_SELECT_IF_BOTH,
1386 L_SELECT_IF_LTE, NULL);
1387 pixaDestroy(&pixa);
1388 boxaDestroy(&boxa);
1389
1390 return 0;
1391 }
1392
1393
1394 /*!
1395 * pixWordMaskByDilation()
1396 *
1397 * Input: pixs (1 bpp; typ. at 75 to 150 ppi)
1398 * maxsize (use 0 for default; not to exceed 14)
1399 * &size (<optional return> size of optimal horiz Sel)
1400 * Return: pixd (dilated word mask), or null on error
1401 *
1402 * Notes:
1403 * (1) For 75 to 150 ppi, the optimal dilation should not exceed 7.
1404 * This is the default size chosen if maxsize <= 0.
1405 * (2) To run this on images at resolution between 200 and 300, it
1406 * is advisable to use a larger maxsize, say between 10 and 14.
1407 * (3) The best size for dilating to get word masks is optionally returned.
1408 */
1409 PIX *
pixWordMaskByDilation(PIX * pixs,l_int32 maxsize,l_int32 * psize)1410 pixWordMaskByDilation(PIX *pixs,
1411 l_int32 maxsize,
1412 l_int32 *psize)
1413 {
1414 l_int32 i, diffmin, ndiff, imin;
1415 l_int32 ncc[MAX_ALLOWED_DILATION + 1];
1416 BOXA *boxa;
1417 NUMA *nacc;
1418 PIX *pixt1, *pixt2, *pixt3;
1419 PIXA *pixa;
1420 SEL *sel;
1421
1422 PROCNAME("pixWordMaskbyDilation");
1423
1424 if (!pixs)
1425 return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
1426
1427 /* Find the optimal dilation to create the word mask.
1428 * Look for successively increasing dilations where the
1429 * number of connected components doesn't decrease.
1430 * This is the situation where the components in the
1431 * word mask should properly cover each word. Use of
1432 * 4 cc slightly reduces the likelihood that words from
1433 * different lines are joined. */
1434 diffmin = 1000000;
1435 pixa = pixaCreate(8);
1436 pixt1 = pixCopy(NULL, pixs);
1437 pixaAddPix(pixa, pixt1, L_COPY);
1438
1439 if (maxsize <= 0)
1440 maxsize = 7; /* default */
1441 if (maxsize > MAX_ALLOWED_DILATION)
1442 maxsize = MAX_ALLOWED_DILATION;
1443 nacc = numaCreate(maxsize);
1444 for (i = 0; i <= maxsize; i++) {
1445 if (i == 0) /* first one not dilated */
1446 pixt2 = pixCopy(NULL, pixt1);
1447 else /* successive dilation by sel_2h */
1448 pixt2 = pixMorphSequence(pixt1, "d2.1", 0);
1449 boxa = pixConnCompBB(pixt2, 4);
1450 ncc[i] = boxaGetCount(boxa);
1451 numaAddNumber(nacc, ncc[i]);
1452 if (i > 0) {
1453 ndiff = ncc[i - 1] - ncc[i];
1454 #if DEBUG_PLOT_CC
1455 fprintf(stderr, "ndiff[%d] = %d\n", i - 1, ndiff);
1456 #endif /* DEBUG_PLOT_CC */
1457 if (ndiff < diffmin) {
1458 imin = i;
1459 diffmin = ndiff;
1460 }
1461 }
1462 pixaAddPix(pixa, pixt2, L_COPY);
1463 pixDestroy(&pixt1);
1464 pixt1 = pixt2;
1465 boxaDestroy(&boxa);
1466 }
1467 pixDestroy(&pixt1);
1468
1469 #if DEBUG_PLOT_CC
1470 {GPLOT *gplot;
1471 NUMA *naseq;
1472 naseq = numaMakeSequence(1, 1, numaGetCount(nacc));
1473 gplot = gplotCreate("junk_cc_output", GPLOT_PNG,
1474 "Number of cc vs. horizontal dilation",
1475 "Sel horiz", "Number of cc");
1476 gplotAddPlot(gplot, naseq, nacc, GPLOT_LINES, "");
1477 gplotMakeOutput(gplot);
1478 gplotDestroy(&gplot);
1479 numaDestroy(&naseq);
1480 }
1481 #endif /* DEBUG_PLOT_CC */
1482
1483 /* Save the result of the optimal dilation */
1484 pixt2 = pixaGetPix(pixa, imin, L_CLONE);
1485 sel = selCreateBrick(1, imin, 0, imin - 1, SEL_HIT);
1486 pixt3 = pixErode(NULL, pixt2, sel); /* remove effect of dilation */
1487 selDestroy(&sel);
1488 pixDestroy(&pixt2);
1489 pixaDestroy(&pixa);
1490 if (psize)
1491 *psize = imin + 1;
1492
1493 numaDestroy(&nacc);
1494 return pixt3;
1495 }
1496
1497
1498 /*----------------------------------------------------------------------*
1499 * Build grayscale composites (templates) *
1500 *----------------------------------------------------------------------*/
1501 /*!
1502 * jbAccumulateComposites()
1503 *
1504 * Input: pixaa (one pixa for each class)
1505 * &pna (<return> number of samples used to build each composite)
1506 * &ptat (<return> centroids of bordered composites)
1507 * Return: pixad (accumulated sum of samples in each class),
1508 * or null on error
1509 *
1510 */
1511 PIXA *
jbAccumulateComposites(PIXAA * pixaa,NUMA ** pna,PTA ** pptat)1512 jbAccumulateComposites(PIXAA *pixaa,
1513 NUMA **pna,
1514 PTA **pptat)
1515 {
1516 l_int32 n, nt, i, j, d, minw, maxw, minh, maxh, xdiff, ydiff;
1517 l_float32 x, y, xave, yave;
1518 NUMA *na;
1519 PIX *pix, *pixt1, *pixt2, *pixsum;
1520 PIXA *pixa, *pixad;
1521 PTA *ptat, *pta;
1522
1523 PROCNAME("jbAccumulateComposites");
1524
1525 if (!pptat)
1526 return (PIXA *)ERROR_PTR("&ptat not defined", procName, NULL);
1527 *pptat = NULL;
1528 if (!pna)
1529 return (PIXA *)ERROR_PTR("&na not defined", procName, NULL);
1530 *pna = NULL;
1531 if (!pixaa)
1532 return (PIXA *)ERROR_PTR("pixaa not defined", procName, NULL);
1533
1534 n = pixaaGetCount(pixaa);
1535 if ((ptat = ptaCreate(n)) == NULL)
1536 return (PIXA *)ERROR_PTR("ptat not made", procName, NULL);
1537 *pptat = ptat;
1538 pixad = pixaCreate(n);
1539 na = numaCreate(n);
1540 *pna = na;
1541
1542 for (i = 0; i < n; i++) {
1543 pixa = pixaaGetPixa(pixaa, i, L_CLONE);
1544 nt = pixaGetCount(pixa);
1545 numaAddNumber(na, nt);
1546 if (nt == 0) {
1547 L_WARNING("empty pixa found!", procName);
1548 pixaDestroy(&pixa);
1549 continue;
1550 }
1551 pixaSizeRange(pixa, &minw, &minh, &maxw, &maxh);
1552 pix = pixaGetPix(pixa, 0, L_CLONE);
1553 d = pixGetDepth(pix);
1554 pixDestroy(&pix);
1555 pixt1 = pixCreate(maxw, maxh, d);
1556 pixsum = pixInitAccumulate(maxw, maxh, 0);
1557 pta = pixaCentroids(pixa);
1558
1559 /* Find the average value of the centroids ... */
1560 xave = yave = 0;
1561 for (j = 0; j < nt; j++) {
1562 ptaGetPt(pta, j, &x, &y);
1563 xave += x;
1564 yave += y;
1565 }
1566 xave = xave / (l_float32)nt;
1567 yave = yave / (l_float32)nt;
1568
1569 /* and place all centroids at their average value */
1570 for (j = 0; j < nt; j++) {
1571 pixt2 = pixaGetPix(pixa, j, L_CLONE);
1572 ptaGetPt(pta, j, &x, &y);
1573 xdiff = (l_int32)(x - xave);
1574 ydiff = (l_int32)(y - yave);
1575 pixClearAll(pixt1);
1576 pixRasterop(pixt1, xdiff, ydiff, maxw, maxh, PIX_SRC,
1577 pixt2, 0, 0);
1578 pixAccumulate(pixsum, pixt1, L_ARITH_ADD);
1579 pixDestroy(&pixt2);
1580 }
1581 pixaAddPix(pixad, pixsum, L_INSERT);
1582 ptaAddPt(ptat, xave, yave);
1583
1584 pixaDestroy(&pixa);
1585 pixDestroy(&pixt1);
1586 ptaDestroy(&pta);
1587 }
1588
1589 return pixad;
1590 }
1591
1592
1593 /*!
1594 * jbTemplatesFromComposites()
1595 *
1596 * Input: pixac (one pix of composites for each class)
1597 * na (number of samples used for each class composite)
1598 * Return: pixad (8 bpp templates for each class), or null on error
1599 *
1600 */
1601 PIXA *
jbTemplatesFromComposites(PIXA * pixac,NUMA * na)1602 jbTemplatesFromComposites(PIXA *pixac,
1603 NUMA *na)
1604 {
1605 l_int32 n, i;
1606 l_float32 nt; /* number of samples in the composite; always an integer */
1607 l_float32 factor;
1608 PIX *pixsum; /* accumulated composite */
1609 PIX *pixd;
1610 PIXA *pixad;
1611
1612 PROCNAME("jbTemplatesFromComposites");
1613
1614 if (!pixac)
1615 return (PIXA *)ERROR_PTR("pixac not defined", procName, NULL);
1616 if (!na)
1617 return (PIXA *)ERROR_PTR("na not defined", procName, NULL);
1618
1619 n = pixaGetCount(pixac);
1620 pixad = pixaCreate(n);
1621 for (i = 0; i < n; i++) {
1622 pixsum = pixaGetPix(pixac, i, L_COPY); /* changed internally */
1623 numaGetFValue(na, i, &nt);
1624 factor = 255. / nt;
1625 pixMultConstAccumulate(pixsum, factor, 0); /* changes pixsum */
1626 pixd = pixFinalAccumulate(pixsum, 0, 8);
1627 pixaAddPix(pixad, pixd, L_INSERT);
1628 pixDestroy(&pixsum);
1629 }
1630
1631 return pixad;
1632 }
1633
1634
1635
1636 /*----------------------------------------------------------------------*
1637 * jbig2 utility routines *
1638 *----------------------------------------------------------------------*/
1639 /*!
1640 * jbClasserCreate()
1641 *
1642 * Input: method (JB_RANKHAUS, JB_CORRELATION)
1643 * components (JB_CONN_COMPS, JB_CHARACTERS, JB_WORDS)
1644 * Return: jbclasser, or null on error
1645 */
1646 JBCLASSER *
jbClasserCreate(l_int32 method,l_int32 components)1647 jbClasserCreate(l_int32 method,
1648 l_int32 components)
1649 {
1650 JBCLASSER *classer;
1651
1652 PROCNAME("jbClasserCreate");
1653
1654 if ((classer = (JBCLASSER *)CALLOC(1, sizeof(JBCLASSER))) == NULL)
1655 return (JBCLASSER *)ERROR_PTR("classer not made", procName, NULL);
1656 if (method != JB_RANKHAUS && method != JB_CORRELATION)
1657 return (JBCLASSER *)ERROR_PTR("invalid type", procName, NULL);
1658 if (components != JB_CONN_COMPS && components != JB_CHARACTERS &&
1659 components != JB_WORDS)
1660 return (JBCLASSER *)ERROR_PTR("invalid type", procName, NULL);
1661
1662 classer->method = method;
1663 classer->components = components;
1664 classer->nacomps = numaCreate(0);
1665 classer->pixaa = pixaaCreate(0);
1666 classer->pixat = pixaCreate(0);
1667 classer->pixatd = pixaCreate(0);
1668 classer->nafgt = numaCreate(0);
1669 classer->naarea = numaCreate(0);
1670 classer->ptac = ptaCreate(0);
1671 classer->ptact = ptaCreate(0);
1672 classer->naclass = numaCreate(0);
1673 classer->napage = numaCreate(0);
1674 classer->ptaul = ptaCreate(0);
1675
1676 return classer;
1677 }
1678
1679
1680 /*
1681 * jbClasserDestroy()
1682 *
1683 * Input: &classer (<to be nulled>)
1684 * Return: void
1685 */
1686 void
jbClasserDestroy(JBCLASSER ** pclasser)1687 jbClasserDestroy(JBCLASSER **pclasser)
1688 {
1689 JBCLASSER *classer;
1690
1691 if (!pclasser)
1692 return;
1693 if ((classer = *pclasser) == NULL)
1694 return;
1695
1696 sarrayDestroy(&classer->safiles);
1697 numaDestroy(&classer->nacomps);
1698 pixaaDestroy(&classer->pixaa);
1699 pixaDestroy(&classer->pixat);
1700 pixaDestroy(&classer->pixatd);
1701 numaHashDestroy(&classer->nahash);
1702 numaDestroy(&classer->nafgt);
1703 numaDestroy(&classer->naarea);
1704 ptaDestroy(&classer->ptac);
1705 ptaDestroy(&classer->ptact);
1706 numaDestroy(&classer->naclass);
1707 numaDestroy(&classer->napage);
1708 ptaDestroy(&classer->ptaul);
1709 ptaDestroy(&classer->ptall);
1710 FREE(classer);
1711 *pclasser = NULL;
1712 return;
1713 }
1714
1715
1716 /*!
1717 * jbDataSave()
1718 *
1719 * Input: jbclasser
1720 * latticew, latticeh (cell size used to store each
1721 * connected component in the composite)
1722 * Return: jbdata, or null on error
1723 *
1724 * Notes:
1725 * (1) This routine stores the jbig2-type data required for
1726 * generating a lossy jbig2 version of the image.
1727 * It can be losslessly written to (and read from) two files.
1728 * (2) It generates and stores the mosaic of templates.
1729 * (3) It clones the Numa and Pta arrays, so these must all
1730 * be destroyed by the caller.
1731 * (4) Input 0 to use the default values for latticew and/or latticeh,
1732 */
1733 JBDATA *
jbDataSave(JBCLASSER * classer)1734 jbDataSave(JBCLASSER *classer)
1735 {
1736 l_int32 maxw, maxh;
1737 JBDATA *data;
1738 PIX *pix;
1739
1740 PROCNAME("jbDataSave");
1741
1742 if (!classer)
1743 return (JBDATA *)ERROR_PTR("classer not defined", procName, NULL);
1744
1745 /* Write the templates into an array. */
1746 pixaSizeRange(classer->pixat, NULL, NULL, &maxw, &maxh);
1747 if ((pix = pixaDisplayOnLattice(classer->pixat, maxw + 1, maxh + 1))
1748 == NULL)
1749 return (JBDATA *)ERROR_PTR("data not made", procName, NULL);
1750
1751 if ((data = (JBDATA *)CALLOC(1, sizeof(JBDATA))) == NULL)
1752 return (JBDATA *)ERROR_PTR("data not made", procName, NULL);
1753 data->pix = pix;
1754 data->npages = classer->npages;
1755 data->w = classer->w;
1756 data->h = classer->h;
1757 data->nclass = classer->nclass;
1758 data->latticew = maxw + 1;
1759 data->latticeh = maxh + 1;
1760 data->naclass = numaClone(classer->naclass);
1761 data->napage = numaClone(classer->napage);
1762 data->ptaul = ptaClone(classer->ptaul);
1763
1764 return data;
1765 }
1766
1767
1768 /*
1769 * jbDataDestroy()
1770 *
1771 * Input: &data (<to be nulled>)
1772 * Return: void
1773 */
1774 void
jbDataDestroy(JBDATA ** pdata)1775 jbDataDestroy(JBDATA **pdata)
1776 {
1777 JBDATA *data;
1778
1779 if (!pdata)
1780 return;
1781 if ((data = *pdata) == NULL)
1782 return;
1783
1784 pixDestroy(&data->pix);
1785 numaDestroy(&data->naclass);
1786 numaDestroy(&data->napage);
1787 ptaDestroy(&data->ptaul);
1788 FREE(data);
1789 *pdata = NULL;
1790 return;
1791 }
1792
1793
1794 /*!
1795 * jbDataWrite()
1796 *
1797 * Input: rootname (for output files; everything but the extension)
1798 * jbdata
1799 * Return: 0 if OK, 1 on error
1800 *
1801 * Notes:
1802 * (1) Serialization function that writes data in jbdata to file.
1803 */
1804 l_int32
jbDataWrite(const char * rootout,JBDATA * jbdata)1805 jbDataWrite(const char *rootout,
1806 JBDATA *jbdata)
1807 {
1808 char buf[L_BUF_SIZE];
1809 l_int32 w, h, nclass, npages, cellw, cellh, ncomp, i, x, y, iclass, ipage;
1810 NUMA *naclass, *napage;
1811 PTA *ptaul;
1812 PIX *pixt;
1813 FILE *fp;
1814
1815 PROCNAME("jbDataWrite");
1816
1817 if (!rootout)
1818 return ERROR_INT("no rootout", procName, 1);
1819 if (!jbdata)
1820 return ERROR_INT("no jbdata", procName, 1);
1821
1822 npages = jbdata->npages;
1823 w = jbdata->w;
1824 h = jbdata->h;
1825 pixt = jbdata->pix;
1826 nclass = jbdata->nclass;
1827 cellw = jbdata->latticew;
1828 cellh = jbdata->latticeh;
1829 naclass = jbdata->naclass;
1830 napage = jbdata->napage;
1831 ptaul = jbdata->ptaul;
1832
1833 snprintf(buf, L_BUF_SIZE, "%s%s", rootout, JB_TEMPLATE_EXT);
1834 pixWrite(buf, pixt, IFF_PNG);
1835
1836 snprintf(buf, L_BUF_SIZE, "%s%s", rootout, JB_DATA_EXT);
1837 if ((fp = fopen(buf, "wb")) == NULL)
1838 return ERROR_INT("stream not opened", procName, 1);
1839 ncomp = ptaGetCount(ptaul);
1840 fprintf(fp, "jb data file\n");
1841 fprintf(fp, "num pages = %d\n", npages);
1842 fprintf(fp, "page size: w = %d, h = %d\n", w, h);
1843 fprintf(fp, "num components = %d\n", ncomp);
1844 fprintf(fp, "num classes = %d\n", nclass);
1845 fprintf(fp, "template lattice size: w = %d, h = %d\n", cellw, cellh);
1846 for (i = 0; i < ncomp; i++) {
1847 numaGetIValue(napage, i, &ipage);
1848 numaGetIValue(naclass, i, &iclass);
1849 ptaGetIPt(ptaul, i, &x, &y);
1850 fprintf(fp, "%d %d %d %d\n", ipage, iclass, x, y);
1851 }
1852 fclose(fp);
1853
1854 return 0;
1855 }
1856
1857
1858 /*!
1859 * jbDataRead()
1860 *
1861 * Input: rootname (for template and data files)
1862 * Return: jbdata, or NULL on error
1863 */
1864 JBDATA *
jbDataRead(const char * rootname)1865 jbDataRead(const char *rootname)
1866 {
1867 char fname[L_BUF_SIZE];
1868 char *linestr;
1869 l_uint8 *data;
1870 l_int32 nsa, i, w, h, cellw, cellh, x, y, iclass, ipage;
1871 l_int32 nbytes, npages, nclass, ncomp;
1872 JBDATA *jbdata;
1873 NUMA *naclass, *napage;
1874 PIX *pixs;
1875 PTA *ptaul;
1876 SARRAY *sa;
1877
1878 PROCNAME("jbDataRead");
1879
1880 if (!rootname)
1881 return (JBDATA *)ERROR_PTR("rootname not defined", procName, NULL);
1882
1883 snprintf(fname, L_BUF_SIZE, "%s%s", rootname, JB_TEMPLATE_EXT);
1884 if ((pixs = pixRead(fname)) == NULL)
1885 return (JBDATA *)ERROR_PTR("pix not read", procName, NULL);
1886
1887 snprintf(fname, L_BUF_SIZE, "%s%s", rootname, JB_DATA_EXT);
1888 if ((data = arrayRead(fname, &nbytes)) == NULL)
1889 return (JBDATA *)ERROR_PTR("data not read", procName, NULL);
1890
1891 if ((sa = sarrayCreateLinesFromString((char *)data, 0)) == NULL)
1892 return (JBDATA *)ERROR_PTR("sa not made", procName, NULL);
1893 nsa = sarrayGetCount(sa); /* number of cc + 6 */
1894 linestr = sarrayGetString(sa, 0, 0);
1895 if (strcmp(linestr, "jb data file"))
1896 return (JBDATA *)ERROR_PTR("invalid jb data file", procName, NULL);
1897 linestr = sarrayGetString(sa, 1, 0);
1898 sscanf(linestr, "num pages = %d", &npages);
1899 linestr = sarrayGetString(sa, 2, 0);
1900 sscanf(linestr, "page size: w = %d, h = %d", &w, &h);
1901 linestr = sarrayGetString(sa, 3, 0);
1902 sscanf(linestr, "num components = %d", &ncomp);
1903 linestr = sarrayGetString(sa, 4, 0);
1904 sscanf(linestr, "num classes = %d\n", &nclass);
1905 linestr = sarrayGetString(sa, 5, 0);
1906 sscanf(linestr, "template lattice size: w = %d, h = %d\n", &cellw, &cellh);
1907
1908 #if 1
1909 fprintf(stderr, "num pages = %d\n", npages);
1910 fprintf(stderr, "page size: w = %d, h = %d\n", w, h);
1911 fprintf(stderr, "num components = %d\n", ncomp);
1912 fprintf(stderr, "num classes = %d\n", nclass);
1913 fprintf(stderr, "template lattice size: w = %d, h = %d\n", cellw, cellh);
1914 #endif
1915
1916 if ((naclass = numaCreate(ncomp)) == NULL)
1917 return (JBDATA *)ERROR_PTR("naclass not made", procName, NULL);
1918 if ((napage = numaCreate(ncomp)) == NULL)
1919 return (JBDATA *)ERROR_PTR("napage not made", procName, NULL);
1920 if ((ptaul = ptaCreate(ncomp)) == NULL)
1921 return (JBDATA *)ERROR_PTR("pta not made", procName, NULL);
1922 for (i = 6; i < nsa; i++) {
1923 linestr = sarrayGetString(sa, i, 0);
1924 sscanf(linestr, "%d %d %d %d\n", &ipage, &iclass, &x, &y);
1925 numaAddNumber(napage, ipage);
1926 numaAddNumber(naclass, iclass);
1927 ptaAddPt(ptaul, x, y);
1928 }
1929
1930 if ((jbdata = (JBDATA *)CALLOC(1, sizeof(JBDATA))) == NULL)
1931 return (JBDATA *)ERROR_PTR("data not made", procName, NULL);
1932 jbdata->pix = pixs;
1933 jbdata->npages = npages;
1934 jbdata->w = w;
1935 jbdata->h = h;
1936 jbdata->nclass = nclass;
1937 jbdata->latticew = cellw;
1938 jbdata->latticeh = cellh;
1939 jbdata->naclass = naclass;
1940 jbdata->napage = napage;
1941 jbdata->ptaul = ptaul;
1942
1943 FREE(data);
1944 sarrayDestroy(&sa);
1945 return jbdata;
1946 }
1947
1948
1949 /*!
1950 * jbDataRender()
1951 *
1952 * Input: jbdata
1953 * debugflag (if TRUE, writes into 2 bpp pix and adds
1954 * component outlines in color)
1955 * Return: pixa (reconstruction of original images, using templates) or
1956 * null on error
1957 */
1958 PIXA *
jbDataRender(JBDATA * data,l_int32 debugflag)1959 jbDataRender(JBDATA *data,
1960 l_int32 debugflag)
1961 {
1962 l_int32 i, w, h, cellw, cellh, x, y, iclass, ipage;
1963 l_int32 npages, nclass, ncomp, wp, hp;
1964 BOX *box;
1965 NUMA *naclass, *napage;
1966 PIX *pixt, *pixt2, *pix, *pixd;
1967 PIXA *pixat; /* pixa of templates */
1968 PIXA *pixad; /* pixa of output images */
1969 PIXCMAP *cmap;
1970 PTA *ptaul;
1971
1972 PROCNAME("jbDataRender");
1973
1974 if (!data)
1975 return (PIXA *)ERROR_PTR("data not defined", procName, NULL);
1976
1977 npages = data->npages;
1978 w = data->w;
1979 h = data->h;
1980 pixt = data->pix;
1981 nclass = data->nclass;
1982 cellw = data->latticew;
1983 cellh = data->latticeh;
1984 naclass = data->naclass;
1985 napage = data->napage;
1986 ptaul = data->ptaul;
1987 ncomp = numaGetCount(naclass);
1988
1989 /* Reconstruct the original set of images from the templates
1990 * and the data associated with each component. First,
1991 * generate the output pixa as a set of empty pix. */
1992 if ((pixad = pixaCreate(npages)) == NULL)
1993 return (PIXA *)ERROR_PTR("pixad not made", procName, NULL);
1994 for (i = 0; i < npages; i++) {
1995 if (debugflag == FALSE)
1996 pix = pixCreate(w, h, 1);
1997 else {
1998 pix = pixCreate(w, h, 2);
1999 cmap = pixcmapCreate(2);
2000 pixcmapAddColor(cmap, 255, 255, 255);
2001 pixcmapAddColor(cmap, 0, 0, 0);
2002 pixcmapAddColor(cmap, 255, 0, 0); /* for box outlines */
2003 pixSetColormap(pix, cmap);
2004 }
2005 pixaAddPix(pixad, pix, L_INSERT);
2006 }
2007
2008 /* Put the class templates into a pixa. */
2009 if ((pixat = pixaCreateFromPix(pixt, nclass, cellw, cellh)) == NULL)
2010 return (PIXA *)ERROR_PTR("pixat not made", procName, NULL);
2011
2012 /* Place each component in the right location on its page. */
2013 for (i = 0; i < ncomp; i++) {
2014 numaGetIValue(napage, i, &ipage);
2015 numaGetIValue(naclass, i, &iclass);
2016 pix = pixaGetPix(pixat, iclass, L_CLONE); /* the template */
2017 wp = pixGetWidth(pix);
2018 hp = pixGetHeight(pix);
2019 ptaGetIPt(ptaul, i, &x, &y);
2020 pixd = pixaGetPix(pixad, ipage, L_CLONE); /* the output page */
2021 if (debugflag == FALSE)
2022 pixRasterop(pixd, x, y, wp, hp, PIX_SRC | PIX_DST, pix, 0, 0);
2023 else {
2024 pixt2 = pixConvert1To2Cmap(pix);
2025 pixRasterop(pixd, x, y, wp, hp, PIX_SRC | PIX_DST, pixt2, 0, 0);
2026 box = boxCreate(x, y, wp, hp);
2027 pixRenderBoxArb(pixd, box, 1, 255, 0, 0);
2028 pixDestroy(&pixt2);
2029 boxDestroy(&box);
2030 }
2031 pixDestroy(&pix); /* the clone only */
2032 pixDestroy(&pixd); /* the clone only */
2033 }
2034
2035 pixaDestroy(&pixat);
2036 return pixad;
2037 }
2038
2039
2040 /*!
2041 * jbGetULCorners()
2042 *
2043 * Input: jbclasser
2044 * pixs (full res image)
2045 * boxa (of c.c. bounding rectangles for this page)
2046 * Return: 0 if OK, 1 on error
2047 *
2048 * Notes:
2049 * (1) This computes the ptaul field, which has the global UL corners,
2050 * adjusted for each specific component, so that each component
2051 * can be replaced by the template for its class and have the
2052 * centroid in the template in the same position as the
2053 * centroid of the original connected component. It is important
2054 * that this be done properly to avoid a wavy baseline in the
2055 * result.
2056 * (2) The array fields ptac and ptact give the centroids of
2057 * those components relative to the UL corner of each component.
2058 * Here, we compute the difference in each component, round to
2059 * nearest integer, and correct the box->x and box->y by
2060 * the appropriate integral difference.
2061 * (3) The templates and stored instances are all bordered.
2062 */
2063 l_int32
jbGetULCorners(JBCLASSER * classer,PIX * pixs,BOXA * boxa)2064 jbGetULCorners(JBCLASSER *classer,
2065 PIX *pixs,
2066 BOXA *boxa)
2067 {
2068 l_int32 i, baseindex, index, n, iclass, idelx, idely, x, y, dx, dy;
2069 l_int32 *sumtab;
2070 l_float32 x1, x2, y1, y2, delx, dely;
2071 BOX *box;
2072 NUMA *naclass;
2073 PIX *pixt;
2074 PTA *ptac, *ptact, *ptaul;
2075
2076 PROCNAME("jbGetULCorners");
2077
2078 if (!classer)
2079 return ERROR_INT("classer not defined", procName, 1);
2080 if (!pixs)
2081 return ERROR_INT("pixs not defined", procName, 1);
2082 if (!boxa)
2083 return ERROR_INT("boxa not defined", procName, 1);
2084
2085 n = boxaGetCount(boxa);
2086 ptaul = classer->ptaul;
2087 naclass = classer->naclass;
2088 ptac = classer->ptac;
2089 ptact = classer->ptact;
2090 baseindex = classer->baseindex; /* num components before this page */
2091 sumtab = makePixelSumTab8();
2092 for (i = 0; i < n; i++) {
2093 index = baseindex + i;
2094 ptaGetPt(ptac, index, &x1, &y1);
2095 numaGetIValue(naclass, index, &iclass);
2096 ptaGetPt(ptact, iclass, &x2, &y2);
2097 delx = x2 - x1;
2098 dely = y2 - y1;
2099 if (delx >= 0)
2100 idelx = (l_int32)(delx + 0.5);
2101 else
2102 idelx = (l_int32)(delx - 0.5);
2103 if (dely >= 0)
2104 idely = (l_int32)(dely + 0.5);
2105 else
2106 idely = (l_int32)(dely - 0.5);
2107 if ((box = boxaGetBox(boxa, i, L_CLONE)) == NULL)
2108 return ERROR_INT("box not found", procName, 1);
2109 boxGetGeometry(box, &x, &y, NULL, NULL);
2110
2111 /* Get final increments dx and dy for best alignment */
2112 pixt = pixaGetPix(classer->pixat, iclass, L_CLONE);
2113 finalPositioningForAlignment(pixs, x, y, idelx, idely,
2114 pixt, sumtab, &dx, &dy);
2115 /* if (i % 20 == 0)
2116 fprintf(stderr, "dx = %d, dy = %d\n", dx, dy); */
2117 ptaAddPt(ptaul, x - idelx + dx, y - idely + dy);
2118 boxDestroy(&box);
2119 pixDestroy(&pixt);
2120 }
2121
2122 FREE(sumtab);
2123 return 0;
2124 }
2125
2126
2127 /*!
2128 * jbGetLLCorners()
2129 *
2130 * Input: jbclasser
2131 * Return: 0 if OK, 1 on error
2132 *
2133 * Notes:
2134 * (1) This computes the ptall field, which has the global LL corners,
2135 * adjusted for each specific component, so that each component
2136 * can be replaced by the template for its class and have the
2137 * centroid in the template in the same position as the
2138 * centroid of the original connected component. It is important
2139 * that this be done properly to avoid a wavy baseline in the result.
2140 * (2) It is computed here from the corresponding UL corners, where
2141 * the input templates and stored instances are all bordered.
2142 * This should be done after all pages have been processed.
2143 * (3) For proper substitution, the templates whose LL corners are
2144 * placed in these locations must be UN-bordered.
2145 * This is available for a realistic jbig2 encoder, which would
2146 * (1) encode each template without a border, and (2) encode
2147 * the position using the LL corner (rather than the UL
2148 * corner) because the difference between y-values
2149 * of successive instances is typically close to zero.
2150 */
2151 l_int32
jbGetLLCorners(JBCLASSER * classer)2152 jbGetLLCorners(JBCLASSER *classer)
2153 {
2154 l_int32 i, iclass, n, x1, y1, h;
2155 NUMA *naclass;
2156 PIX *pix;
2157 PIXA *pixat;
2158 PTA *ptaul, *ptall;
2159
2160 PROCNAME("jbGetLLCorners");
2161
2162 if (!classer)
2163 return ERROR_INT("classer not defined", procName, 1);
2164
2165 ptaul = classer->ptaul;
2166 naclass = classer->naclass;
2167 pixat = classer->pixat;
2168
2169 ptaDestroy(&classer->ptall);
2170 n = ptaGetCount(ptaul);
2171 ptall = ptaCreate(n);
2172 classer->ptall = ptall;
2173
2174 /* If the templates were bordered, we would add h - 1 to the UL
2175 * corner y-value. However, because the templates to be used
2176 * here have their borders removed, and the borders are
2177 * JB_ADDED_PIXELS on each side, we add h - 1 - 2 * JB_ADDED_PIXELS
2178 * to the UL corner y-value. */
2179 for (i = 0; i < n; i++) {
2180 ptaGetIPt(ptaul, i, &x1, &y1);
2181 numaGetIValue(naclass, i, &iclass);
2182 pix = pixaGetPix(pixat, iclass, L_CLONE);
2183 h = pixGetHeight(pix);
2184 ptaAddPt(ptall, x1, y1 + h - 1 - 2 * JB_ADDED_PIXELS);
2185 pixDestroy(&pix);
2186 }
2187
2188 return 0;
2189 }
2190
2191
2192 /*----------------------------------------------------------------------*
2193 * Static helpers *
2194 *----------------------------------------------------------------------*/
2195 /* When looking for similar matches we check templates whose size is +/- 2 in
2196 * each direction. This involves 25 possible sizes. This array contains the
2197 * offsets for each of those positions in a spiral pattern. There are 25 pairs
2198 * of numbers in this array: even positions are x values. */
2199 static int two_by_two_walk[50] = {
2200 0, 0,
2201 0, 1,
2202 -1, 0,
2203 0, -1,
2204 1, 0,
2205 -1, 1,
2206 1, 1,
2207 -1, -1,
2208 1, -1,
2209 0, -2,
2210 2, 0,
2211 0, 2,
2212 -2, 0,
2213 -1, -2,
2214 1, -2,
2215 2, -1,
2216 2, 1,
2217 1, 2,
2218 -1, 2,
2219 -2, 1,
2220 -2, -1,
2221 -2, -2,
2222 2, -2,
2223 2, 2,
2224 -2, 2};
2225
2226
2227 /*!
2228 * findSimilarSizedTemplatesInit()
2229 *
2230 * Input: classer
2231 * pixs (instance to be matched)
2232 * Return: Allocated context to be used with findSimilar*
2233 */
2234 static JBFINDCTX *
findSimilarSizedTemplatesInit(JBCLASSER * classer,PIX * pixs)2235 findSimilarSizedTemplatesInit(JBCLASSER *classer,
2236 PIX *pixs)
2237 {
2238 JBFINDCTX *state;
2239
2240 state = (JBFINDCTX *)CALLOC(1, sizeof(JBFINDCTX));
2241 state->w = pixGetWidth(pixs) - 2 * JB_ADDED_PIXELS;
2242 state->h = pixGetHeight(pixs) - 2 * JB_ADDED_PIXELS;
2243 state->classer = classer;
2244
2245 return state;
2246 }
2247
2248
2249 static void
findSimilarSizedTemplatesDestroy(JBFINDCTX ** pstate)2250 findSimilarSizedTemplatesDestroy(JBFINDCTX **pstate)
2251 {
2252 JBFINDCTX *state;
2253
2254 PROCNAME("findSimilarSizedTemplatesDestroy");
2255
2256 if (pstate == NULL) {
2257 L_WARNING("ptr address is null", procName);
2258 return;
2259 }
2260 if ((state = *pstate) == NULL)
2261 return;
2262
2263 numaDestroy(&state->numa);
2264 FREE(state);
2265 *pstate = NULL;
2266 return;
2267 }
2268
2269
2270 /*!
2271 * findSimilarSizedTemplatesNext()
2272 *
2273 * Input: state (from findSimilarSizedTemplatesInit)
2274 * Return: Next template number, or -1 when finished
2275 *
2276 * We have a hash table mapping template area to a list of template
2277 * numbers with that area. We wish to find similar sized templates,
2278 * so we first look for templates with the same width and height, and
2279 * then with width + 1, etc. This walk is guided by the
2280 * two_by_two_walk array, above.
2281 *
2282 * We don't want to have to collect the whole list of templates first because
2283 * (we hope) to find it quickly. So we keep the context for this walk in an
2284 * explictit state structure and this function acts like a generator.
2285 */
2286 static l_int32
findSimilarSizedTemplatesNext(JBFINDCTX * state)2287 findSimilarSizedTemplatesNext(JBFINDCTX *state)
2288 {
2289 l_int32 desiredh, desiredw, size, templ;
2290 PIX *pixt;
2291
2292 while(1) { /* Continue the walk over step 'i' */
2293 if (state->i >= 25) { /* all done */
2294 return -1;
2295 }
2296
2297 desiredw = state->w + two_by_two_walk[2 * state->i];
2298 desiredh = state->h + two_by_two_walk[2 * state->i + 1];
2299 if (desiredh < 1 || desiredw < 1) { /* invalid size */
2300 state->i++;
2301 continue;
2302 }
2303
2304 if (!state->numa) {
2305 /* We have yet to start walking the array for the step 'i' */
2306 state->numa = numaHashGetNuma(state->classer->nahash,
2307 desiredh * desiredw);
2308 if (!state->numa) { /* nothing there */
2309 state->i++;
2310 continue;
2311 }
2312
2313 state->n = 0; /* OK, we got a numa. */
2314 }
2315
2316 /* Continue working on this numa */
2317 size = numaGetCount(state->numa);
2318 for ( ; state->n < size; ) {
2319 templ = (l_int32)(state->numa->array[state->n++] + 0.5);
2320 pixt = pixaGetPix(state->classer->pixat, templ, L_CLONE);
2321 if (pixGetWidth(pixt) - 2 * JB_ADDED_PIXELS == desiredw &&
2322 pixGetHeight(pixt) - 2 * JB_ADDED_PIXELS == desiredh) {
2323 pixDestroy(&pixt);
2324 return templ;
2325 }
2326 pixDestroy(&pixt);
2327 }
2328
2329 /* Exhausted the numa; take another step and try again */
2330 state->i++;
2331 numaDestroy(&state->numa);
2332 continue;
2333 }
2334 }
2335
2336
2337 /*!
2338 * finalPositioningForAlignment()
2339 *
2340 * Input: pixs (input page image)
2341 * x, y (location of UL corner of bb of component in pixs)
2342 * idelx, idely (compensation to match centroids of component
2343 * and template)
2344 * pixt (template, with JB_ADDED_PIXELS of padding on all sides)
2345 * sumtab (for summing fg pixels in an image)
2346 * &dx, &dy (return delta on position for best match; each
2347 * one is in the set {-1, 0, 1})
2348 * Return: 0 if OK, 1 on error
2349 *
2350 */
2351 static l_int32
finalPositioningForAlignment(PIX * pixs,l_int32 x,l_int32 y,l_int32 idelx,l_int32 idely,PIX * pixt,l_int32 * sumtab,l_int32 * pdx,l_int32 * pdy)2352 finalPositioningForAlignment(PIX *pixs,
2353 l_int32 x,
2354 l_int32 y,
2355 l_int32 idelx,
2356 l_int32 idely,
2357 PIX *pixt,
2358 l_int32 *sumtab,
2359 l_int32 *pdx,
2360 l_int32 *pdy)
2361 {
2362 l_int32 w, h, i, j, minx, miny, count, mincount;
2363 PIX *pixi; /* clipped from source pixs */
2364 PIX *pixr; /* temporary storage */
2365 BOX *box;
2366
2367 PROCNAME("finalPositioningForAlignment");
2368
2369 if (!pixs)
2370 return ERROR_INT("pixs not defined", procName, 1);
2371 if (!pixt)
2372 return ERROR_INT("pixt not defined", procName, 1);
2373 if (!pdx || !pdy)
2374 return ERROR_INT("&dx and &dy not both defined", procName, 1);
2375 if (!sumtab)
2376 return ERROR_INT("sumtab not defined", procName, 1);
2377 *pdx = *pdy = 0;
2378
2379 /* Use JB_ADDED_PIXELS pixels padding on each side */
2380 w = pixGetWidth(pixt);
2381 h = pixGetHeight(pixt);
2382 box = boxCreate(x - idelx - JB_ADDED_PIXELS,
2383 y - idely - JB_ADDED_PIXELS, w, h);
2384 pixi = pixClipRectangle(pixs, box, NULL);
2385 boxDestroy(&box);
2386 if (!pixi)
2387 return ERROR_INT("pixi not made", procName, 1);
2388
2389 pixr = pixCreate(pixGetWidth(pixi), pixGetHeight(pixi), 1);
2390 mincount = 0x7fffffff;
2391 for (i = -1; i <= 1; i++) {
2392 for (j = -1; j <= 1; j++) {
2393 pixCopy(pixr, pixi);
2394 pixRasterop(pixr, j, i, w, h, PIX_SRC ^ PIX_DST, pixt, 0, 0);
2395 pixCountPixels(pixr, &count, sumtab);
2396 if (count < mincount) {
2397 minx = j;
2398 miny = i;
2399 mincount = count;
2400 }
2401 }
2402 }
2403 pixDestroy(&pixi);
2404 pixDestroy(&pixr);
2405
2406 *pdx = minx;
2407 *pdy = miny;
2408 return 0;
2409 }
2410