comparison mupdf-source/thirdparty/leptonica/src/morphapp.c @ 2:b50eed0cc0ef upstream

ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4. The directory name has changed: no version number in the expanded directory now.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:43:07 +0200
parents
children
comparison
equal deleted inserted replaced
1:1d09e1dec1d9 2:b50eed0cc0ef
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 /*!
29 * \file morphapp.c
30 * <pre>
31 *
32 * These are some useful and/or interesting composite
33 * image processing operations, of the type that are often
34 * useful in applications. Most are morphological in
35 * nature.
36 *
37 * Extraction of boundary pixels
38 * PIX *pixExtractBoundary()
39 *
40 * Selective morph sequence operation under mask
41 * PIX *pixMorphSequenceMasked()
42 *
43 * Selective morph sequence operation on each component
44 * PIX *pixMorphSequenceByComponent()
45 * PIXA *pixaMorphSequenceByComponent()
46 *
47 * Selective morph sequence operation on each region
48 * PIX *pixMorphSequenceByRegion()
49 * PIXA *pixaMorphSequenceByRegion()
50 *
51 * Union and intersection of parallel composite operations
52 * PIX *pixUnionOfMorphOps()
53 * PIX *pixIntersectionOfMorphOps()
54 *
55 * Selective connected component filling
56 * PIX *pixSelectiveConnCompFill()
57 *
58 * Removal of matched patterns
59 * PIX *pixRemoveMatchedPattern()
60 *
61 * Display of matched patterns
62 * PIX *pixDisplayMatchedPattern()
63 *
64 * Extension of pixa by iterative erosion or dilation (and by scaling)
65 * PIXA *pixaExtendByMorph()
66 * PIXA *pixaExtendByScaling()
67 *
68 * Iterative morphological seed filling (don't use for real work)
69 * PIX *pixSeedfillMorph()
70 *
71 * Granulometry on binary images
72 * NUMA *pixRunHistogramMorph()
73 *
74 * Composite operations on grayscale images
75 * PIX *pixTophat()
76 * PIX *pixHDome()
77 * PIX *pixFastTophat()
78 * PIX *pixMorphGradient()
79 *
80 * Centroid of component
81 * PTA *pixaCentroids()
82 * l_int32 pixCentroid()
83 * </pre>
84 */
85
86 #ifdef HAVE_CONFIG_H
87 #include <config_auto.h>
88 #endif /* HAVE_CONFIG_H */
89
90 #include "allheaders.h"
91 #include "array_internal.h"
92
93 #define SWAP(x, y) {temp = (x); (x) = (y); (y) = temp;}
94
95 /*-----------------------------------------------------------------*
96 * Extraction of boundary pixels *
97 *-----------------------------------------------------------------*/
98 /*!
99 * \brief pixExtractBoundary()
100 *
101 * \param[in] pixs 1 bpp
102 * \param[in] type 0 for background pixels; 1 for foreground pixels
103 * \return pixd, or NULL on error
104 *
105 * <pre>
106 * Notes:
107 * (1) Extracts the fg or bg boundary pixels for each component.
108 * Components are assumed to end at the boundary of pixs.
109 * </pre>
110 */
111 PIX *
112 pixExtractBoundary(PIX *pixs,
113 l_int32 type)
114 {
115 PIX *pixd;
116
117 if (!pixs)
118 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
119
120 if (type == 0)
121 pixd = pixDilateBrick(NULL, pixs, 3, 3);
122 else
123 pixd = pixErodeBrick(NULL, pixs, 3, 3);
124 pixXor(pixd, pixd, pixs);
125 return pixd;
126 }
127
128
129 /*-----------------------------------------------------------------*
130 * Selective morph sequence operation under mask *
131 *-----------------------------------------------------------------*/
132 /*!
133 * \brief pixMorphSequenceMasked()
134 *
135 * \param[in] pixs 1 bpp
136 * \param[in] pixm [optional] 1 bpp mask
137 * \param[in] sequence string specifying sequence of operations
138 * \param[in] dispsep horizontal separation in pixels between
139 * successive displays; use zero to suppress display
140 * \return pixd, or NULL on error
141 *
142 * <pre>
143 * Notes:
144 * (1) This applies the morph sequence to the image, but only allows
145 * changes in pixs for pixels under the background of pixm.
146 * (5) If pixm is NULL, this is just pixMorphSequence().
147 * </pre>
148 */
149 PIX *
150 pixMorphSequenceMasked(PIX *pixs,
151 PIX *pixm,
152 const char *sequence,
153 l_int32 dispsep)
154 {
155 PIX *pixd;
156
157 if (!pixs)
158 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
159 if (!sequence)
160 return (PIX *)ERROR_PTR("sequence not defined", __func__, NULL);
161
162 pixd = pixMorphSequence(pixs, sequence, dispsep);
163 pixCombineMasked(pixd, pixs, pixm); /* restore src pixels under mask fg */
164 return pixd;
165 }
166
167
168 /*-----------------------------------------------------------------*
169 * Morph sequence operation on each component *
170 *-----------------------------------------------------------------*/
171 /*!
172 * \brief pixMorphSequenceByComponent()
173 *
174 * \param[in] pixs 1 bpp
175 * \param[in] sequence string specifying sequence
176 * \param[in] connectivity 4 or 8
177 * \param[in] minw min width to consider; use 0 or 1 for any width
178 * \param[in] minh min height to consider; use 0 or 1 for any height
179 * \param[out] pboxa [optional] return boxa of c.c. in pixs
180 * \return pixd, or NULL on error
181 *
182 * <pre>
183 * Notes:
184 * (1) See pixMorphSequence() for composing operation sequences.
185 * (2) This operates separately on each c.c. in the input pix.
186 * (3) The dilation does NOT increase the c.c. size; it is clipped
187 * to the size of the original c.c. This is necessary to
188 * keep the c.c. independent after the operation.
189 * (4) You can specify that the width and/or height must equal
190 * or exceed a minimum size for the operation to take place.
191 * (5) Use NULL for boxa to avoid returning the boxa.
192 * </pre>
193 */
194 PIX *
195 pixMorphSequenceByComponent(PIX *pixs,
196 const char *sequence,
197 l_int32 connectivity,
198 l_int32 minw,
199 l_int32 minh,
200 BOXA **pboxa)
201 {
202 l_int32 n, i, x, y, w, h;
203 BOXA *boxa;
204 PIX *pix, *pixd;
205 PIXA *pixas, *pixad;
206
207 if (!pixs)
208 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
209 if (!sequence)
210 return (PIX *)ERROR_PTR("sequence not defined", __func__, NULL);
211
212 if (minw <= 0) minw = 1;
213 if (minh <= 0) minh = 1;
214
215 /* Get the c.c. */
216 if ((boxa = pixConnComp(pixs, &pixas, connectivity)) == NULL)
217 return (PIX *)ERROR_PTR("boxa not made", __func__, NULL);
218
219 /* Operate on each c.c. independently */
220 pixad = pixaMorphSequenceByComponent(pixas, sequence, minw, minh);
221 pixaDestroy(&pixas);
222 boxaDestroy(&boxa);
223 if (!pixad)
224 return (PIX *)ERROR_PTR("pixad not made", __func__, NULL);
225
226 /* Display the result out into pixd */
227 pixd = pixCreateTemplate(pixs);
228 n = pixaGetCount(pixad);
229 for (i = 0; i < n; i++) {
230 pixaGetBoxGeometry(pixad, i, &x, &y, &w, &h);
231 pix = pixaGetPix(pixad, i, L_CLONE);
232 pixRasterop(pixd, x, y, w, h, PIX_PAINT, pix, 0, 0);
233 pixDestroy(&pix);
234 }
235
236 if (pboxa)
237 *pboxa = pixaGetBoxa(pixad, L_CLONE);
238 pixaDestroy(&pixad);
239 return pixd;
240 }
241
242
243 /*!
244 * \brief pixaMorphSequenceByComponent()
245 *
246 * \param[in] pixas of 1 bpp pix
247 * \param[in] sequence string specifying sequence
248 * \param[in] minw min width to consider; use 0 or 1 for any width
249 * \param[in] minh min height to consider; use 0 or 1 for any height
250 * \return pixad, or NULL on error
251 *
252 * <pre>
253 * Notes:
254 * (1) See pixMorphSequence() for composing operation sequences.
255 * (2) This operates separately on each c.c. in the input pixa.
256 * (3) You can specify that the width and/or height must equal
257 * or exceed a minimum size for the operation to take place.
258 * (4) The input pixa should have a boxa giving the locations
259 * of the pix components.
260 * </pre>
261 */
262 PIXA *
263 pixaMorphSequenceByComponent(PIXA *pixas,
264 const char *sequence,
265 l_int32 minw,
266 l_int32 minh)
267 {
268 l_int32 n, i, w, h, d;
269 BOX *box;
270 PIX *pix1, *pix2;
271 PIXA *pixad;
272
273 if (!pixas)
274 return (PIXA *)ERROR_PTR("pixas not defined", __func__, NULL);
275 if ((n = pixaGetCount(pixas)) == 0)
276 return (PIXA *)ERROR_PTR("no pix in pixas", __func__, NULL);
277 if (n != pixaGetBoxaCount(pixas))
278 L_WARNING("boxa size != n\n", __func__);
279 pixaGetPixDimensions(pixas, 0, NULL, NULL, &d);
280 if (d != 1)
281 return (PIXA *)ERROR_PTR("depth not 1 bpp", __func__, NULL);
282
283 if (!sequence)
284 return (PIXA *)ERROR_PTR("sequence not defined", __func__, NULL);
285 if (minw <= 0) minw = 1;
286 if (minh <= 0) minh = 1;
287
288 if ((pixad = pixaCreate(n)) == NULL)
289 return (PIXA *)ERROR_PTR("pixad not made", __func__, NULL);
290 for (i = 0; i < n; i++) {
291 pixaGetPixDimensions(pixas, i, &w, &h, NULL);
292 if (w >= minw && h >= minh) {
293 if ((pix1 = pixaGetPix(pixas, i, L_CLONE)) == NULL) {
294 pixaDestroy(&pixad);
295 return (PIXA *)ERROR_PTR("pix1 not found", __func__, NULL);
296 }
297 if ((pix2 = pixMorphCompSequence(pix1, sequence, 0)) == NULL) {
298 pixaDestroy(&pixad);
299 return (PIXA *)ERROR_PTR("pix2 not made", __func__, NULL);
300 }
301 pixaAddPix(pixad, pix2, L_INSERT);
302 box = pixaGetBox(pixas, i, L_COPY);
303 pixaAddBox(pixad, box, L_INSERT);
304 pixDestroy(&pix1);
305 }
306 }
307
308 return pixad;
309 }
310
311
312 /*-----------------------------------------------------------------*
313 * Morph sequence operation on each region *
314 *-----------------------------------------------------------------*/
315 /*!
316 * \brief pixMorphSequenceByRegion()
317 *
318 * \param[in] pixs 1 bpp
319 * \param[in] pixm mask specifying regions
320 * \param[in] sequence string specifying sequence
321 * \param[in] connectivity 4 or 8, used on mask
322 * \param[in] minw min width to consider; use 0 or 1 for any width
323 * \param[in] minh min height to consider; use 0 or 1 for any height
324 * \param[out] pboxa [optional] return boxa of c.c. in pixm
325 * \return pixd, or NULL on error
326 *
327 * <pre>
328 * Notes:
329 * (1) See pixMorphCompSequence() for composing operation sequences.
330 * (2) This operates separately on the region in pixs corresponding
331 * to each c.c. in the mask pixm. It differs from
332 * pixMorphSequenceByComponent() in that the latter does not have
333 * a pixm (mask), but instead operates independently on each
334 * component in pixs.
335 * (3) Dilation will NOT increase the region size; the result
336 * is clipped to the size of the mask region. This is necessary
337 * to make regions independent after the operation.
338 * (4) You can specify that the width and/or height of a region must
339 * equal or exceed a minimum size for the operation to take place.
340 * (5) Use NULL for %pboxa to avoid returning the boxa.
341 * </pre>
342 */
343 PIX *
344 pixMorphSequenceByRegion(PIX *pixs,
345 PIX *pixm,
346 const char *sequence,
347 l_int32 connectivity,
348 l_int32 minw,
349 l_int32 minh,
350 BOXA **pboxa)
351 {
352 l_int32 n, i, x, y, w, h;
353 BOXA *boxa;
354 PIX *pix, *pixd;
355 PIXA *pixam, *pixad;
356
357 if (pboxa) *pboxa = NULL;
358 if (!pixs)
359 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
360 if (!pixm)
361 return (PIX *)ERROR_PTR("pixm not defined", __func__, NULL);
362 if (pixGetDepth(pixs) != 1 || pixGetDepth(pixm) != 1)
363 return (PIX *)ERROR_PTR("pixs and pixm not both 1 bpp", __func__, NULL);
364 if (!sequence)
365 return (PIX *)ERROR_PTR("sequence not defined", __func__, NULL);
366
367 if (minw <= 0) minw = 1;
368 if (minh <= 0) minh = 1;
369
370 /* Get the c.c. of the mask */
371 if ((boxa = pixConnComp(pixm, &pixam, connectivity)) == NULL)
372 return (PIX *)ERROR_PTR("boxa not made", __func__, NULL);
373
374 /* Operate on each region in pixs independently */
375 pixad = pixaMorphSequenceByRegion(pixs, pixam, sequence, minw, minh);
376 pixaDestroy(&pixam);
377 boxaDestroy(&boxa);
378 if (!pixad)
379 return (PIX *)ERROR_PTR("pixad not made", __func__, NULL);
380
381 /* Display the result out into pixd */
382 pixd = pixCreateTemplate(pixs);
383 n = pixaGetCount(pixad);
384 for (i = 0; i < n; i++) {
385 pixaGetBoxGeometry(pixad, i, &x, &y, &w, &h);
386 pix = pixaGetPix(pixad, i, L_CLONE);
387 pixRasterop(pixd, x, y, w, h, PIX_PAINT, pix, 0, 0);
388 pixDestroy(&pix);
389 }
390
391 if (pboxa)
392 *pboxa = pixaGetBoxa(pixad, L_CLONE);
393 pixaDestroy(&pixad);
394 return pixd;
395 }
396
397
398 /*!
399 * \brief pixaMorphSequenceByRegion()
400 *
401 * \param[in] pixs 1 bpp
402 * \param[in] pixam of 1 bpp mask elements
403 * \param[in] sequence string specifying sequence
404 * \param[in] minw min width to consider; use 0 or 1 for any width
405 * \param[in] minh min height to consider; use 0 or 1 for any height
406 * \return pixad, or NULL on error
407 *
408 * <pre>
409 * Notes:
410 * (1) See pixMorphSequence() for composing operation sequences.
411 * (2) This operates separately on each region in the input pixs
412 * defined by the components in pixam.
413 * (3) You can specify that the width and/or height of a mask
414 * component must equal or exceed a minimum size for the
415 * operation to take place.
416 * (4) The input pixam should have a boxa giving the locations
417 * of the regions in pixs.
418 * </pre>
419 */
420 PIXA *
421 pixaMorphSequenceByRegion(PIX *pixs,
422 PIXA *pixam,
423 const char *sequence,
424 l_int32 minw,
425 l_int32 minh)
426 {
427 l_int32 n, i, w, h, same, maxd, fullpa, fullba;
428 BOX *box;
429 PIX *pix1, *pix2, *pix3;
430 PIXA *pixad;
431
432 if (!pixs)
433 return (PIXA *)ERROR_PTR("pixs not defined", __func__, NULL);
434 if (pixGetDepth(pixs) != 1)
435 return (PIXA *)ERROR_PTR("pixs not 1 bpp", __func__, NULL);
436 if (!sequence)
437 return (PIXA *)ERROR_PTR("sequence not defined", __func__, NULL);
438 if (!pixam)
439 return (PIXA *)ERROR_PTR("pixam not defined", __func__, NULL);
440 pixaVerifyDepth(pixam, &same, &maxd);
441 if (maxd != 1)
442 return (PIXA *)ERROR_PTR("mask depth not 1 bpp", __func__, NULL);
443 pixaIsFull(pixam, &fullpa, &fullba);
444 if (!fullpa || !fullba)
445 return (PIXA *)ERROR_PTR("missing comps in pixam", __func__, NULL);
446 n = pixaGetCount(pixam);
447 if (minw <= 0) minw = 1;
448 if (minh <= 0) minh = 1;
449
450 if ((pixad = pixaCreate(n)) == NULL)
451 return (PIXA *)ERROR_PTR("pixad not made", __func__, NULL);
452
453 /* Use the rectangle to remove the appropriate part of pixs;
454 * then AND with the mask component to get the actual fg
455 * of pixs that is under the mask component. */
456 for (i = 0; i < n; i++) {
457 pixaGetPixDimensions(pixam, i, &w, &h, NULL);
458 if (w >= minw && h >= minh) {
459 pix1 = pixaGetPix(pixam, i, L_CLONE);
460 box = pixaGetBox(pixam, i, L_COPY);
461 pix2 = pixClipRectangle(pixs, box, NULL);
462 pixAnd(pix2, pix2, pix1);
463 pix3 = pixMorphCompSequence(pix2, sequence, 0);
464 pixDestroy(&pix1);
465 pixDestroy(&pix2);
466 if (!pix3) {
467 boxDestroy(&box);
468 pixaDestroy(&pixad);
469 L_ERROR("pix3 not made in iter %d; aborting\n", __func__, i);
470 break;
471 }
472 pixaAddPix(pixad, pix3, L_INSERT);
473 pixaAddBox(pixad, box, L_INSERT);
474 }
475 }
476
477 return pixad;
478 }
479
480
481 /*-----------------------------------------------------------------*
482 * Union and intersection of parallel composite operations *
483 *-----------------------------------------------------------------*/
484 /*!
485 * \brief pixUnionOfMorphOps()
486 *
487 * \param[in] pixs 1 bpp
488 * \param[in] sela
489 * \param[in] type L_MORPH_DILATE, etc.
490 * \return pixd union of the specified morphological operation
491 * on pixs for each Sel in the Sela, or NULL on error
492 */
493 PIX *
494 pixUnionOfMorphOps(PIX *pixs,
495 SELA *sela,
496 l_int32 type)
497 {
498 l_int32 n, i;
499 PIX *pixt, *pixd;
500 SEL *sel;
501
502 if (!pixs || pixGetDepth(pixs) != 1)
503 return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL);
504 if (!sela)
505 return (PIX *)ERROR_PTR("sela not defined", __func__, NULL);
506 n = selaGetCount(sela);
507 if (n == 0)
508 return (PIX *)ERROR_PTR("no sels in sela", __func__, NULL);
509 if (type != L_MORPH_DILATE && type != L_MORPH_ERODE &&
510 type != L_MORPH_OPEN && type != L_MORPH_CLOSE &&
511 type != L_MORPH_HMT)
512 return (PIX *)ERROR_PTR("invalid type", __func__, NULL);
513
514 pixd = pixCreateTemplate(pixs);
515 for (i = 0; i < n; i++) {
516 sel = selaGetSel(sela, i);
517 if (type == L_MORPH_DILATE)
518 pixt = pixDilate(NULL, pixs, sel);
519 else if (type == L_MORPH_ERODE)
520 pixt = pixErode(NULL, pixs, sel);
521 else if (type == L_MORPH_OPEN)
522 pixt = pixOpen(NULL, pixs, sel);
523 else if (type == L_MORPH_CLOSE)
524 pixt = pixClose(NULL, pixs, sel);
525 else /* type == L_MORPH_HMT */
526 pixt = pixHMT(NULL, pixs, sel);
527 pixOr(pixd, pixd, pixt);
528 pixDestroy(&pixt);
529 }
530
531 return pixd;
532 }
533
534
535 /*!
536 * \brief pixIntersectionOfMorphOps()
537 *
538 * \param[in] pixs 1 bpp
539 * \param[in] sela
540 * \param[in] type L_MORPH_DILATE, etc.
541 * \return pixd intersection of the specified morphological operation
542 * on pixs for each Sel in the Sela, or NULL on error
543 */
544 PIX *
545 pixIntersectionOfMorphOps(PIX *pixs,
546 SELA *sela,
547 l_int32 type)
548 {
549 l_int32 n, i;
550 PIX *pixt, *pixd;
551 SEL *sel;
552
553 if (!pixs || pixGetDepth(pixs) != 1)
554 return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL);
555 if (!sela)
556 return (PIX *)ERROR_PTR("sela not defined", __func__, NULL);
557 n = selaGetCount(sela);
558 if (n == 0)
559 return (PIX *)ERROR_PTR("no sels in sela", __func__, NULL);
560 if (type != L_MORPH_DILATE && type != L_MORPH_ERODE &&
561 type != L_MORPH_OPEN && type != L_MORPH_CLOSE &&
562 type != L_MORPH_HMT)
563 return (PIX *)ERROR_PTR("invalid type", __func__, NULL);
564
565 pixd = pixCreateTemplate(pixs);
566 pixSetAll(pixd);
567 for (i = 0; i < n; i++) {
568 sel = selaGetSel(sela, i);
569 if (type == L_MORPH_DILATE)
570 pixt = pixDilate(NULL, pixs, sel);
571 else if (type == L_MORPH_ERODE)
572 pixt = pixErode(NULL, pixs, sel);
573 else if (type == L_MORPH_OPEN)
574 pixt = pixOpen(NULL, pixs, sel);
575 else if (type == L_MORPH_CLOSE)
576 pixt = pixClose(NULL, pixs, sel);
577 else /* type == L_MORPH_HMT */
578 pixt = pixHMT(NULL, pixs, sel);
579 pixAnd(pixd, pixd, pixt);
580 pixDestroy(&pixt);
581 }
582
583 return pixd;
584 }
585
586
587
588 /*-----------------------------------------------------------------*
589 * Selective connected component filling *
590 *-----------------------------------------------------------------*/
591 /*!
592 * \brief pixSelectiveConnCompFill()
593 *
594 * \param[in] pixs 1 bpp
595 * \param[in] connectivity 4 or 8
596 * \param[in] minw min width to consider; use 0 or 1 for any width
597 * \param[in] minh min height to consider; use 0 or 1 for any height
598 * \return pix with holes filled in selected c.c., or NULL on error
599 */
600 PIX *
601 pixSelectiveConnCompFill(PIX *pixs,
602 l_int32 connectivity,
603 l_int32 minw,
604 l_int32 minh)
605 {
606 l_int32 n, i, x, y, w, h;
607 BOXA *boxa;
608 PIX *pix1, *pix2, *pixd;
609 PIXA *pixa;
610
611 if (!pixs)
612 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
613 if (pixGetDepth(pixs) != 1)
614 return (PIX *)ERROR_PTR("pixs not 1 bpp", __func__, NULL);
615 if (minw <= 0) minw = 1;
616 if (minh <= 0) minh = 1;
617
618 if ((boxa = pixConnComp(pixs, &pixa, connectivity)) == NULL)
619 return (PIX *)ERROR_PTR("boxa not made", __func__, NULL);
620 n = boxaGetCount(boxa);
621 pixd = pixCopy(NULL, pixs);
622 for (i = 0; i < n; i++) {
623 boxaGetBoxGeometry(boxa, i, &x, &y, &w, &h);
624 if (w >= minw && h >= minh) {
625 pix1 = pixaGetPix(pixa, i, L_CLONE);
626 if ((pix2 = pixHolesByFilling(pix1, 12 - connectivity)) == NULL) {
627 L_ERROR("pix2 not made in iter %d\n", __func__, i);
628 pixDestroy(&pix1);
629 continue;
630 }
631 pixRasterop(pixd, x, y, w, h, PIX_PAINT, pix2, 0, 0);
632 pixDestroy(&pix1);
633 pixDestroy(&pix2);
634 }
635 }
636 pixaDestroy(&pixa);
637 boxaDestroy(&boxa);
638
639 return pixd;
640 }
641
642
643 /*-----------------------------------------------------------------*
644 * Removal of matched patterns *
645 *-----------------------------------------------------------------*/
646 /*!
647 * \brief pixRemoveMatchedPattern()
648 *
649 * \param[in] pixs input image, 1 bpp
650 * \param[in] pixp pattern to be removed from image, 1 bpp
651 * \param[in] pixe image after erosion by Sel that approximates pixp
652 * \param[in] x0, y0 center of Sel
653 * \param[in] dsize number of pixels on each side by which pixp is
654 * dilated before being subtracted from pixs;
655 * valid values are {0, 1, 2, 3, 4}
656 * \return 0 if OK, 1 on error
657 *
658 * <pre>
659 * Notes:
660 * (1) This is in-place.
661 * (2) You can use various functions in selgen to create a Sel
662 * that is used to generate pixe from pixs.
663 * (3) This function is applied after pixe has been computed.
664 * It finds the centroid of each c.c., and subtracts
665 * (the appropriately dilated version of) pixp, with the center
666 * of the Sel used to align pixp with pixs.
667 * </pre>
668 */
669 l_ok
670 pixRemoveMatchedPattern(PIX *pixs,
671 PIX *pixp,
672 PIX *pixe,
673 l_int32 x0,
674 l_int32 y0,
675 l_int32 dsize)
676 {
677 l_int32 i, nc, x, y, w, h, xb, yb;
678 BOXA *boxa;
679 PIX *pix1, *pix2;
680 PIXA *pixa;
681 PTA *pta;
682 SEL *sel;
683
684 if (!pixs)
685 return ERROR_INT("pixs not defined", __func__, 1);
686 if (!pixp)
687 return ERROR_INT("pixp not defined", __func__, 1);
688 if (!pixe)
689 return ERROR_INT("pixe not defined", __func__, 1);
690 if (pixGetDepth(pixs) != 1 || pixGetDepth(pixp) != 1 ||
691 pixGetDepth(pixe) != 1)
692 return ERROR_INT("all input pix not 1 bpp", __func__, 1);
693 if (dsize < 0 || dsize > 4)
694 return ERROR_INT("dsize not in {0,1,2,3,4}", __func__, 1);
695
696 /* Find the connected components and their centroids */
697 boxa = pixConnComp(pixe, &pixa, 8);
698 if ((nc = boxaGetCount(boxa)) == 0) {
699 L_WARNING("no matched patterns\n", __func__);
700 boxaDestroy(&boxa);
701 pixaDestroy(&pixa);
702 return 0;
703 }
704 pta = pixaCentroids(pixa);
705 pixaDestroy(&pixa);
706
707 /* Optionally dilate the pattern, first adding a border that
708 * is large enough to accommodate the dilated pixels */
709 sel = NULL;
710 if (dsize > 0) {
711 sel = selCreateBrick(2 * dsize + 1, 2 * dsize + 1, dsize, dsize,
712 SEL_HIT);
713 pix1 = pixAddBorder(pixp, dsize, 0);
714 pix2 = pixDilate(NULL, pix1, sel);
715 selDestroy(&sel);
716 pixDestroy(&pix1);
717 } else {
718 pix2 = pixClone(pixp);
719 }
720
721 /* Subtract out each dilated pattern. The centroid of each
722 * component is located at:
723 * (box->x + x, box->y + y)
724 * and the 'center' of the pattern used in making pixe is located at
725 * (x0 + dsize, (y0 + dsize)
726 * relative to the UL corner of the pattern. The center of the
727 * pattern is placed at the center of the component. */
728 pixGetDimensions(pix2, &w, &h, NULL);
729 for (i = 0; i < nc; i++) {
730 ptaGetIPt(pta, i, &x, &y);
731 boxaGetBoxGeometry(boxa, i, &xb, &yb, NULL, NULL);
732 pixRasterop(pixs, xb + x - x0 - dsize, yb + y - y0 - dsize,
733 w, h, PIX_DST & PIX_NOT(PIX_SRC), pix2, 0, 0);
734 }
735
736 boxaDestroy(&boxa);
737 ptaDestroy(&pta);
738 pixDestroy(&pix2);
739 return 0;
740 }
741
742
743 /*-----------------------------------------------------------------*
744 * Display of matched patterns *
745 *-----------------------------------------------------------------*/
746 /*!
747 * \brief pixDisplayMatchedPattern()
748 *
749 * \param[in] pixs input image, 1 bpp
750 * \param[in] pixp pattern to be removed from image, 1 bpp
751 * \param[in] pixe image after erosion by Sel that approximates pixp
752 * \param[in] x0, y0 center of Sel
753 * \param[in] color to paint the matched patterns; 0xrrggbb00
754 * \param[in] scale reduction factor for output pixd
755 * \param[in] nlevels if scale < 1.0, threshold to this number of levels
756 * \return pixd 8 bpp, colormapped, or NULL on error
757 *
758 * <pre>
759 * Notes:
760 * (1) A 4 bpp colormapped image is generated.
761 * (2) If scale <= 1.0, do scale to gray for the output, and threshold
762 * to nlevels of gray.
763 * (3) You can use various functions in selgen to create a Sel
764 * that will generate pixe from pixs.
765 * (4) This function is applied after pixe has been computed.
766 * It finds the centroid of each c.c., and colors the output
767 * pixels using pixp (appropriately aligned) as a stencil.
768 * Alignment is done using the origin of the Sel and the
769 * centroid of the eroded image to place the stencil pixp.
770 * </pre>
771 */
772 PIX *
773 pixDisplayMatchedPattern(PIX *pixs,
774 PIX *pixp,
775 PIX *pixe,
776 l_int32 x0,
777 l_int32 y0,
778 l_uint32 color,
779 l_float32 scale,
780 l_int32 nlevels)
781 {
782 l_int32 i, nc, xb, yb, x, y, xi, yi, rval, gval, bval;
783 BOXA *boxa;
784 PIX *pixd, *pixt, *pixps;
785 PIXA *pixa;
786 PTA *pta;
787 PIXCMAP *cmap;
788
789 if (!pixs)
790 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
791 if (!pixp)
792 return (PIX *)ERROR_PTR("pixp not defined", __func__, NULL);
793 if (!pixe)
794 return (PIX *)ERROR_PTR("pixe not defined", __func__, NULL);
795 if (pixGetDepth(pixs) != 1 || pixGetDepth(pixp) != 1 ||
796 pixGetDepth(pixe) != 1)
797 return (PIX *)ERROR_PTR("all input pix not 1 bpp", __func__, NULL);
798 if (scale > 1.0 || scale <= 0.0) {
799 L_WARNING("scale > 1.0 or < 0.0; setting to 1.0\n", __func__);
800 scale = 1.0;
801 }
802
803 /* Find the connected components and their centroids */
804 boxa = pixConnComp(pixe, &pixa, 8);
805 if ((nc = boxaGetCount(boxa)) == 0) {
806 L_WARNING("no matched patterns\n", __func__);
807 boxaDestroy(&boxa);
808 pixaDestroy(&pixa);
809 return 0;
810 }
811 pta = pixaCentroids(pixa);
812
813 extractRGBValues(color, &rval, &gval, &bval);
814 if (scale == 1.0) { /* output 4 bpp at full resolution */
815 pixd = pixConvert1To4(NULL, pixs, 0, 1);
816 cmap = pixcmapCreate(4);
817 pixcmapAddColor(cmap, 255, 255, 255);
818 pixcmapAddColor(cmap, 0, 0, 0);
819 pixSetColormap(pixd, cmap);
820
821 /* Paint through pixp for each match location. The centroid of each
822 * component in pixe is located at:
823 * (box->x + x, box->y + y)
824 * and the 'center' of the pattern used in making pixe is located at
825 * (x0, y0)
826 * relative to the UL corner of the pattern. The center of the
827 * pattern is placed at the center of the component. */
828 for (i = 0; i < nc; i++) {
829 ptaGetIPt(pta, i, &x, &y);
830 boxaGetBoxGeometry(boxa, i, &xb, &yb, NULL, NULL);
831 pixSetMaskedCmap(pixd, pixp, xb + x - x0, yb + y - y0,
832 rval, gval, bval);
833 }
834 } else { /* output 4 bpp downscaled */
835 pixt = pixScaleToGray(pixs, scale);
836 pixd = pixThresholdTo4bpp(pixt, nlevels, 1);
837 pixps = pixScaleBySampling(pixp, scale, scale);
838
839 for (i = 0; i < nc; i++) {
840 ptaGetIPt(pta, i, &x, &y);
841 boxaGetBoxGeometry(boxa, i, &xb, &yb, NULL, NULL);
842 xi = (l_int32)(scale * (xb + x - x0));
843 yi = (l_int32)(scale * (yb + y - y0));
844 pixSetMaskedCmap(pixd, pixps, xi, yi, rval, gval, bval);
845 }
846 pixDestroy(&pixt);
847 pixDestroy(&pixps);
848 }
849
850 boxaDestroy(&boxa);
851 pixaDestroy(&pixa);
852 ptaDestroy(&pta);
853 return pixd;
854 }
855
856
857 /*------------------------------------------------------------------------*
858 * Extension of pixa by iterative erosion or dilation (and by scaling) *
859 *------------------------------------------------------------------------*/
860 /*!
861 * \brief pixaExtendByMorph()
862 *
863 * \param[in] pixas
864 * \param[in] type L_MORPH_DILATE, L_MORPH_ERODE
865 * \param[in] niters
866 * \param[in] sel used for dilation, erosion; uses 2x2 if null
867 * \param[in] include 1 to include a copy of the input pixas in pixad;
868 * 0 to omit
869 * \return pixad with derived pix, using all iterations, or NULL on error
870 *
871 * <pre>
872 * Notes:
873 * (1) This dilates or erodes every pix in %pixas, iteratively,
874 * using the input Sel (or, if null, a 2x2 Sel by default),
875 * and puts the results in %pixad.
876 * (2) If %niters <= 0, this is a no-op; it returns a clone of pixas.
877 * (3) If %include == 1, the output %pixad contains all the pix
878 * in %pixas. Otherwise, it doesn't, but pixaJoin() can be
879 * used later to join pixas with pixad.
880 * </pre>
881 */
882 PIXA *
883 pixaExtendByMorph(PIXA *pixas,
884 l_int32 type,
885 l_int32 niters,
886 SEL *sel,
887 l_int32 include)
888 {
889 l_int32 maxdepth, i, j, n;
890 PIX *pix0, *pix1, *pix2;
891 SEL *selt;
892 PIXA *pixad;
893
894 if (!pixas)
895 return (PIXA *)ERROR_PTR("pixas undefined", __func__, NULL);
896 if (niters <= 0) {
897 L_INFO("niters = %d; nothing to do\n", __func__, niters);
898 return pixaCopy(pixas, L_CLONE);
899 }
900 if (type != L_MORPH_DILATE && type != L_MORPH_ERODE)
901 return (PIXA *)ERROR_PTR("invalid type", __func__, NULL);
902 pixaGetDepthInfo(pixas, &maxdepth, NULL);
903 if (maxdepth > 1)
904 return (PIXA *)ERROR_PTR("some pix have bpp > 1", __func__, NULL);
905
906 if (!sel)
907 selt = selCreateBrick(2, 2, 0, 0, SEL_HIT); /* default */
908 else
909 selt = selCopy(sel);
910 n = pixaGetCount(pixas);
911 pixad = pixaCreate(n * niters);
912 for (i = 0; i < n; i++) {
913 pix1 = pixaGetPix(pixas, i, L_CLONE);
914 if (include) pixaAddPix(pixad, pix1, L_COPY);
915 pix0 = pix1; /* need to keep the handle to destroy the clone */
916 for (j = 0; j < niters; j++) {
917 if (type == L_MORPH_DILATE) {
918 pix2 = pixDilate(NULL, pix1, selt);
919 } else { /* L_MORPH_ERODE */
920 pix2 = pixErode(NULL, pix1, selt);
921 }
922 pixaAddPix(pixad, pix2, L_INSERT);
923 pix1 = pix2; /* owned by pixad; do not destroy */
924 }
925 pixDestroy(&pix0);
926 }
927
928 selDestroy(&selt);
929 return pixad;
930 }
931
932
933 /*!
934 * \brief pixaExtendByScaling()
935 *
936 * \param[in] pixas
937 * \param[in] nasc numa of scaling factors
938 * \param[in] type L_HORIZ, L_VERT, L_BOTH_DIRECTIONS
939 * \param[in] include 1 to include a copy of the input pixas in pixad;
940 * 0 to omit
941 * \return pixad with derived pix, using all scalings, or NULL on error
942 *
943 * <pre>
944 * Notes:
945 * (1) This scales every pix in %pixas by each factor in %nasc.
946 * and puts the results in %pixad.
947 * (2) If %include == 1, the output %pixad contains all the pix
948 * in %pixas. Otherwise, it doesn't, but pixaJoin() can be
949 * used later to join pixas with pixad.
950 * </pre>
951 */
952 PIXA *
953 pixaExtendByScaling(PIXA *pixas,
954 NUMA *nasc,
955 l_int32 type,
956 l_int32 include)
957 {
958 l_int32 i, j, n, nsc, w, h, scalew, scaleh;
959 l_float32 scalefact;
960 PIX *pix1, *pix2;
961 PIXA *pixad;
962
963 if (!pixas)
964 return (PIXA *)ERROR_PTR("pixas undefined", __func__, NULL);
965 if (!nasc || numaGetCount(nasc) == 0)
966 return (PIXA *)ERROR_PTR("nasc undefined or empty", __func__, NULL);
967 if (type != L_HORIZ && type != L_VERT && type != L_BOTH_DIRECTIONS)
968 return (PIXA *)ERROR_PTR("invalid type", __func__, NULL);
969
970 n = pixaGetCount(pixas);
971 nsc = numaGetCount(nasc);
972 if ((pixad = pixaCreate(n * (nsc + 1))) == NULL) {
973 L_ERROR("pixad not made: n = %d, nsc = %d\n", __func__, n, nsc);
974 return NULL;
975 }
976 for (i = 0; i < n; i++) {
977 pix1 = pixaGetPix(pixas, i, L_CLONE);
978 if (include) pixaAddPix(pixad, pix1, L_COPY);
979 pixGetDimensions(pix1, &w, &h, NULL);
980 for (j = 0; j < nsc; j++) {
981 numaGetFValue(nasc, j, &scalefact);
982 scalew = w;
983 scaleh = h;
984 if (type == L_HORIZ || type == L_BOTH_DIRECTIONS)
985 scalew = w * scalefact;
986 if (type == L_VERT || type == L_BOTH_DIRECTIONS)
987 scaleh = h * scalefact;
988 pix2 = pixScaleToSize(pix1, scalew, scaleh);
989 pixaAddPix(pixad, pix2, L_INSERT);
990 }
991 pixDestroy(&pix1);
992 }
993 return pixad;
994 }
995
996
997 /*-----------------------------------------------------------------*
998 * Iterative morphological seed filling *
999 *-----------------------------------------------------------------*/
1000 /*!
1001 * \brief pixSeedfillMorph()
1002 *
1003 * \param[in] pixs seed
1004 * \param[in] pixm mask
1005 * \param[in] maxiters use 0 to go to completion
1006 * \param[in] connectivity 4 or 8
1007 * \return pixd after filling into the mask or NULL on error
1008 *
1009 * <pre>
1010 * Notes:
1011 * (1) This is in general a very inefficient method for filling
1012 * from a seed into a mask. Use it for a small number of iterations,
1013 * but if you expect more than a few iterations, use
1014 * pixSeedfillBinary().
1015 * (2) We use a 3x3 brick SEL for 8-cc filling and a 3x3 plus SEL for 4-cc.
1016 * </pre>
1017 */
1018 PIX *
1019 pixSeedfillMorph(PIX *pixs,
1020 PIX *pixm,
1021 l_int32 maxiters,
1022 l_int32 connectivity)
1023 {
1024 l_int32 same, i;
1025 PIX *pixt, *pixd, *temp;
1026 SEL *sel_3;
1027
1028 if (!pixs || pixGetDepth(pixs) != 1)
1029 return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL);
1030 if (!pixm)
1031 return (PIX *)ERROR_PTR("mask pix not defined", __func__, NULL);
1032 if (connectivity != 4 && connectivity != 8)
1033 return (PIX *)ERROR_PTR("connectivity not in {4,8}", __func__, NULL);
1034 if (maxiters <= 0) maxiters = 1000;
1035 if (pixSizesEqual(pixs, pixm) == 0)
1036 return (PIX *)ERROR_PTR("pix sizes unequal", __func__, NULL);
1037
1038 if ((sel_3 = selCreateBrick(3, 3, 1, 1, SEL_HIT)) == NULL)
1039 return (PIX *)ERROR_PTR("sel_3 not made", __func__, NULL);
1040 if (connectivity == 4) { /* remove corner hits to make a '+' */
1041 selSetElement(sel_3, 0, 0, SEL_DONT_CARE);
1042 selSetElement(sel_3, 2, 2, SEL_DONT_CARE);
1043 selSetElement(sel_3, 2, 0, SEL_DONT_CARE);
1044 selSetElement(sel_3, 0, 2, SEL_DONT_CARE);
1045 }
1046
1047 pixt = pixCopy(NULL, pixs);
1048 pixd = pixCreateTemplate(pixs);
1049 for (i = 1; i <= maxiters; i++) {
1050 pixDilate(pixd, pixt, sel_3);
1051 pixAnd(pixd, pixd, pixm);
1052 pixEqual(pixd, pixt, &same);
1053 if (same || i == maxiters)
1054 break;
1055 else
1056 SWAP(pixt, pixd);
1057 }
1058 lept_stderr(" Num iters in binary reconstruction = %d\n", i);
1059
1060 pixDestroy(&pixt);
1061 selDestroy(&sel_3);
1062 return pixd;
1063 }
1064
1065
1066 /*-----------------------------------------------------------------*
1067 * Granulometry on binary images *
1068 *-----------------------------------------------------------------*/
1069 /*!
1070 * \brief pixRunHistogramMorph()
1071 *
1072 * \param[in] pixs 1 bpp
1073 * \param[in] runtype L_RUN_OFF, L_RUN_ON
1074 * \param[in] direction L_HORIZ, L_VERT
1075 * \param[in] maxsize size of largest runlength counted
1076 * \return numa of run-lengths
1077 */
1078 NUMA *
1079 pixRunHistogramMorph(PIX *pixs,
1080 l_int32 runtype,
1081 l_int32 direction,
1082 l_int32 maxsize)
1083 {
1084 l_int32 count, i, size;
1085 l_float32 val;
1086 NUMA *na, *nah;
1087 PIX *pix1, *pix2, *pix3;
1088 SEL *sel_2a;
1089
1090 if (!pixs)
1091 return (NUMA *)ERROR_PTR("seed pix not defined", __func__, NULL);
1092 if (runtype != L_RUN_OFF && runtype != L_RUN_ON)
1093 return (NUMA *)ERROR_PTR("invalid run type", __func__, NULL);
1094 if (direction != L_HORIZ && direction != L_VERT)
1095 return (NUMA *)ERROR_PTR("direction not in {L_HORIZ, L_VERT}",
1096 __func__, NULL);
1097 if (pixGetDepth(pixs) != 1)
1098 return (NUMA *)ERROR_PTR("pixs must be binary", __func__, NULL);
1099
1100 if (direction == L_HORIZ)
1101 sel_2a = selCreateBrick(1, 2, 0, 0, SEL_HIT);
1102 else /* direction == L_VERT */
1103 sel_2a = selCreateBrick(2, 1, 0, 0, SEL_HIT);
1104 if (!sel_2a)
1105 return (NUMA *)ERROR_PTR("sel_2a not made", __func__, NULL);
1106
1107 if (runtype == L_RUN_OFF) {
1108 if ((pix1 = pixCopy(NULL, pixs)) == NULL) {
1109 selDestroy(&sel_2a);
1110 return (NUMA *)ERROR_PTR("pix1 not made", __func__, NULL);
1111 }
1112 pixInvert(pix1, pix1);
1113 } else { /* runtype == L_RUN_ON */
1114 pix1 = pixClone(pixs);
1115 }
1116
1117 /* Get pixel counts at different stages of erosion */
1118 na = numaCreate(0);
1119 pix2 = pixCreateTemplate(pixs);
1120 pix3 = pixCreateTemplate(pixs);
1121 pixCountPixels(pix1, &count, NULL);
1122 numaAddNumber(na, count);
1123 pixErode(pix2, pix1, sel_2a);
1124 pixCountPixels(pix2, &count, NULL);
1125 numaAddNumber(na, count);
1126 for (i = 0; i < maxsize / 2; i++) {
1127 pixErode(pix3, pix2, sel_2a);
1128 pixCountPixels(pix3, &count, NULL);
1129 numaAddNumber(na, count);
1130 pixErode(pix2, pix3, sel_2a);
1131 pixCountPixels(pix2, &count, NULL);
1132 numaAddNumber(na, count);
1133 }
1134
1135 /* Compute length histogram */
1136 size = numaGetCount(na);
1137 nah = numaCreate(size);
1138 numaAddNumber(nah, 0); /* number at length 0 */
1139 for (i = 1; i < size - 1; i++) {
1140 val = na->array[i+1] - 2 * na->array[i] + na->array[i-1];
1141 numaAddNumber(nah, val);
1142 }
1143
1144 pixDestroy(&pix1);
1145 pixDestroy(&pix2);
1146 pixDestroy(&pix3);
1147 selDestroy(&sel_2a);
1148 numaDestroy(&na);
1149 return nah;
1150 }
1151
1152
1153 /*-----------------------------------------------------------------*
1154 * Composite operations on grayscale images *
1155 *-----------------------------------------------------------------*/
1156 /*!
1157 * \brief pixTophat()
1158 *
1159 * \param[in] pixs 1 bpp
1160 * \param[in] hsize of Sel; must be odd; origin implicitly in center
1161 * \param[in] vsize ditto
1162 * \param[in] type L_TOPHAT_WHITE: image - opening
1163 * L_TOPHAT_BLACK: closing - image
1164 * \return pixd, or NULL on error
1165 *
1166 * <pre>
1167 * Notes:
1168 * (1) Sel is a brick with all elements being hits
1169 * (2) If hsize = vsize = 1, returns an image with all 0 data.
1170 * (3) The L_TOPHAT_WHITE flag emphasizes small bright regions,
1171 * whereas the L_TOPHAT_BLACK flag emphasizes small dark regions.
1172 * The L_TOPHAT_WHITE tophat can be accomplished by doing a
1173 * L_TOPHAT_BLACK tophat on the inverse, or v.v.
1174 * </pre>
1175 */
1176 PIX *
1177 pixTophat(PIX *pixs,
1178 l_int32 hsize,
1179 l_int32 vsize,
1180 l_int32 type)
1181 {
1182 PIX *pixt, *pixd;
1183
1184 if (!pixs)
1185 return (PIX *)ERROR_PTR("seed pix not defined", __func__, NULL);
1186 if (pixGetDepth(pixs) != 8)
1187 return (PIX *)ERROR_PTR("pixs not 8 bpp", __func__, NULL);
1188 if (hsize < 1 || vsize < 1)
1189 return (PIX *)ERROR_PTR("hsize or vsize < 1", __func__, NULL);
1190 if ((hsize & 1) == 0 ) {
1191 L_WARNING("horiz sel size must be odd; increasing by 1\n", __func__);
1192 hsize++;
1193 }
1194 if ((vsize & 1) == 0 ) {
1195 L_WARNING("vert sel size must be odd; increasing by 1\n", __func__);
1196 vsize++;
1197 }
1198 if (type != L_TOPHAT_WHITE && type != L_TOPHAT_BLACK)
1199 return (PIX *)ERROR_PTR("type must be L_TOPHAT_BLACK or L_TOPHAT_WHITE",
1200 __func__, NULL);
1201
1202 if (hsize == 1 && vsize == 1)
1203 return pixCreateTemplate(pixs);
1204
1205 switch (type)
1206 {
1207 case L_TOPHAT_WHITE:
1208 if ((pixt = pixOpenGray(pixs, hsize, vsize)) == NULL)
1209 return (PIX *)ERROR_PTR("pixt not made", __func__, NULL);
1210 pixd = pixSubtractGray(NULL, pixs, pixt);
1211 pixDestroy(&pixt);
1212 break;
1213 case L_TOPHAT_BLACK:
1214 if ((pixd = pixCloseGray(pixs, hsize, vsize)) == NULL)
1215 return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
1216 pixSubtractGray(pixd, pixd, pixs);
1217 break;
1218 default:
1219 return (PIX *)ERROR_PTR("invalid type", __func__, NULL);
1220 }
1221
1222 return pixd;
1223 }
1224
1225
1226 /*!
1227 * \brief pixHDome()
1228 *
1229 * \param[in] pixs 8 bpp, filling mask
1230 * \param[in] height of seed below the filling maskhdome; must be >= 0
1231 * \param[in] connectivity 4 or 8
1232 * \return pixd 8 bpp, or NULL on error
1233 *
1234 * <pre>
1235 * Notes:
1236 * (1) It is more efficient to use a connectivity of 4 for the fill.
1237 * (2) This fills bumps to some level, and extracts the unfilled
1238 * part of the bump. To extract the troughs of basins, first
1239 * invert pixs and then apply pixHDome().
1240 * (3) It is useful to compare the HDome operation with the TopHat.
1241 * The latter extracts peaks or valleys that have a width
1242 * not exceeding the size of the structuring element used
1243 * in the opening or closing, rsp. The height of the peak is
1244 * irrelevant. By contrast, for the HDome, the gray seedfill
1245 * is used to extract all peaks that have a height not exceeding
1246 * a given value, regardless of their width!
1247 * (4) Slightly more precisely, suppose you set 'height' = 40.
1248 * Then all bumps in pixs with a height greater than or equal
1249 * to 40 become, in pixd, bumps with a max value of exactly 40.
1250 * All shorter bumps have a max value in pixd equal to the height
1251 * of the bump.
1252 * (5) The method: the filling mask, pixs, is the image whose peaks
1253 * are to be extracted. The height of a peak is the distance
1254 * between the top of the peak and the highest "leak" to the
1255 * outside -- think of a sombrero, where the leak occurs
1256 * at the highest point on the rim.
1257 * (a) Generate a seed, pixd, by subtracting some value, p, from
1258 * each pixel in the filling mask, pixs. The value p is
1259 * the 'height' input to this function.
1260 * (b) Fill in pixd starting with this seed, clipping by pixs,
1261 * in the way described in seedfillGrayLow(). The filling
1262 * stops before the peaks in pixs are filled.
1263 * For peaks that have a height > p, pixd is filled to
1264 * the level equal to the (top-of-the-peak - p).
1265 * For peaks of height < p, the peak is left unfilled
1266 * from its highest saddle point (the leak to the outside).
1267 * (c) Subtract the filled seed (pixd) from the filling mask (pixs).
1268 * Note that in this procedure, everything is done starting
1269 * with the filling mask, pixs.
1270 * (6) For segmentation, the resulting image, pixd, can be thresholded
1271 * and used as a seed for another filling operation.
1272 * </pre>
1273 */
1274 PIX *
1275 pixHDome(PIX *pixs,
1276 l_int32 height,
1277 l_int32 connectivity)
1278 {
1279 PIX *pixsd, *pixd;
1280
1281 if (!pixs)
1282 return (PIX *)ERROR_PTR("src pix not defined", __func__, NULL);
1283 if (pixGetDepth(pixs) != 8)
1284 return (PIX *)ERROR_PTR("pixs not 8 bpp", __func__, NULL);
1285 if (height < 0)
1286 return (PIX *)ERROR_PTR("height not >= 0", __func__, NULL);
1287 if (height == 0)
1288 return pixCreateTemplate(pixs);
1289
1290 if ((pixsd = pixCopy(NULL, pixs)) == NULL)
1291 return (PIX *)ERROR_PTR("pixsd not made", __func__, NULL);
1292 pixAddConstantGray(pixsd, -height);
1293 pixSeedfillGray(pixsd, pixs, connectivity);
1294 pixd = pixSubtractGray(NULL, pixs, pixsd);
1295 pixDestroy(&pixsd);
1296 return pixd;
1297 }
1298
1299
1300 /*!
1301 * \brief pixFastTophat()
1302 *
1303 * \param[in] pixs 8 bpp
1304 * \param[in] xsize width of max/min op, smoothing; any integer >= 1
1305 * \param[in] ysize height of max/min op, smoothing; any integer >= 1
1306 * \param[in] type L_TOPHAT_WHITE: image - min
1307 * L_TOPHAT_BLACK: max - image
1308 * \return pixd, or NULL on error
1309 *
1310 * <pre>
1311 * Notes:
1312 * (1) Don't be fooled. This is NOT a tophat. It is a tophat-like
1313 * operation, where the result is similar to what you'd get
1314 * if you used an erosion instead of an opening, or a dilation
1315 * instead of a closing.
1316 * (2) Instead of opening or closing at full resolution, it does
1317 * a fast downscale/minmax operation, then a quick small smoothing
1318 * at low res, a replicative expansion of the "background"
1319 * to full res, and finally a removal of the background level
1320 * from the input image. The smoothing step may not be important.
1321 * (3) It does not remove noise as well as a tophat, but it is
1322 * 5 to 10 times faster.
1323 * If you need the preciseness of the tophat, don't use this.
1324 * (4) The L_TOPHAT_WHITE flag emphasizes small bright regions,
1325 * whereas the L_TOPHAT_BLACK flag emphasizes small dark regions.
1326 * </pre>
1327 */
1328 PIX *
1329 pixFastTophat(PIX *pixs,
1330 l_int32 xsize,
1331 l_int32 ysize,
1332 l_int32 type)
1333 {
1334 PIX *pix1, *pix2, *pix3, *pixd;
1335
1336 if (!pixs)
1337 return (PIX *)ERROR_PTR("seed pix not defined", __func__, NULL);
1338 if (pixGetDepth(pixs) != 8)
1339 return (PIX *)ERROR_PTR("pixs not 8 bpp", __func__, NULL);
1340 if (xsize < 1 || ysize < 1)
1341 return (PIX *)ERROR_PTR("size < 1", __func__, NULL);
1342 if (type != L_TOPHAT_WHITE && type != L_TOPHAT_BLACK)
1343 return (PIX *)ERROR_PTR("type must be L_TOPHAT_BLACK or L_TOPHAT_WHITE",
1344 __func__, NULL);
1345
1346 if (xsize == 1 && ysize == 1)
1347 return pixCreateTemplate(pixs);
1348
1349 switch (type)
1350 {
1351 case L_TOPHAT_WHITE:
1352 if ((pix1 = pixScaleGrayMinMax(pixs, xsize, ysize, L_CHOOSE_MIN))
1353 == NULL)
1354 return (PIX *)ERROR_PTR("pix1 not made", __func__, NULL);
1355 pix2 = pixBlockconv(pix1, 1, 1); /* small smoothing */
1356 pix3 = pixScaleBySampling(pix2, xsize, ysize);
1357 pixd = pixSubtractGray(NULL, pixs, pix3);
1358 pixDestroy(&pix3);
1359 break;
1360 case L_TOPHAT_BLACK:
1361 if ((pix1 = pixScaleGrayMinMax(pixs, xsize, ysize, L_CHOOSE_MAX))
1362 == NULL)
1363 return (PIX *)ERROR_PTR("pix1 not made", __func__, NULL);
1364 pix2 = pixBlockconv(pix1, 1, 1); /* small smoothing */
1365 pixd = pixScaleBySampling(pix2, xsize, ysize);
1366 pixSubtractGray(pixd, pixd, pixs);
1367 break;
1368 default:
1369 return (PIX *)ERROR_PTR("invalid type", __func__, NULL);
1370 }
1371
1372 pixDestroy(&pix1);
1373 pixDestroy(&pix2);
1374 return pixd;
1375 }
1376
1377
1378 /*!
1379 * \brief pixMorphGradient()
1380 *
1381 * \param[in] pixs 8 bpp
1382 * \param[in] hsize sel width; must be odd; origin implicitly in center
1383 * \param[in] vsize sel height
1384 * \param[in] smoothing half-width of convolution smoothing filter.
1385 * The width is (2 * smoothing + 1, so 0 is no-op.
1386 * \return pixd, or NULL on error
1387 */
1388 PIX *
1389 pixMorphGradient(PIX *pixs,
1390 l_int32 hsize,
1391 l_int32 vsize,
1392 l_int32 smoothing)
1393 {
1394 PIX *pixg, *pixd;
1395
1396 if (!pixs)
1397 return (PIX *)ERROR_PTR("seed pix not defined", __func__, NULL);
1398 if (pixGetDepth(pixs) != 8)
1399 return (PIX *)ERROR_PTR("pixs not 8 bpp", __func__, NULL);
1400 if (hsize < 1 || vsize < 1)
1401 return (PIX *)ERROR_PTR("hsize or vsize < 1", __func__, NULL);
1402 if ((hsize & 1) == 0 ) {
1403 L_WARNING("horiz sel size must be odd; increasing by 1\n", __func__);
1404 hsize++;
1405 }
1406 if ((vsize & 1) == 0 ) {
1407 L_WARNING("vert sel size must be odd; increasing by 1\n", __func__);
1408 vsize++;
1409 }
1410
1411 /* Optionally smooth first to remove noise.
1412 * If smoothing is 0, just get a copy */
1413 pixg = pixBlockconvGray(pixs, NULL, smoothing, smoothing);
1414
1415 /* This gives approximately the gradient of a transition */
1416 pixd = pixDilateGray(pixg, hsize, vsize);
1417 pixSubtractGray(pixd, pixd, pixg);
1418 pixDestroy(&pixg);
1419 return pixd;
1420 }
1421
1422
1423 /*-----------------------------------------------------------------*
1424 * Centroid of component *
1425 *-----------------------------------------------------------------*/
1426 /*!
1427 * \brief pixaCentroids()
1428 *
1429 * \param[in] pixa of components; 1 or 8 bpp
1430 * \return pta of centroids relative to the UL corner of
1431 * each pix, or NULL on error
1432 *
1433 * <pre>
1434 * Notes:
1435 * (1) An error message is returned if any pix has something other
1436 * than 1 bpp or 8 bpp depth, and the centroid from that pix
1437 * is saved as (0, 0).
1438 * </pre>
1439 */
1440 PTA *
1441 pixaCentroids(PIXA *pixa)
1442 {
1443 l_int32 i, n;
1444 l_int32 *centtab = NULL;
1445 l_int32 *sumtab = NULL;
1446 l_float32 x, y;
1447 PIX *pix;
1448 PTA *pta;
1449
1450 if (!pixa)
1451 return (PTA *)ERROR_PTR("pixa not defined", __func__, NULL);
1452 if ((n = pixaGetCount(pixa)) == 0)
1453 return (PTA *)ERROR_PTR("no pix in pixa", __func__, NULL);
1454
1455 if ((pta = ptaCreate(n)) == NULL)
1456 return (PTA *)ERROR_PTR("pta not defined", __func__, NULL);
1457 centtab = makePixelCentroidTab8();
1458 sumtab = makePixelSumTab8();
1459
1460 for (i = 0; i < n; i++) {
1461 pix = pixaGetPix(pixa, i, L_CLONE);
1462 if (pixCentroid(pix, centtab, sumtab, &x, &y) == 1)
1463 L_ERROR("centroid failure for pix %d\n", __func__, i);
1464 pixDestroy(&pix);
1465 ptaAddPt(pta, x, y);
1466 }
1467
1468 LEPT_FREE(centtab);
1469 LEPT_FREE(sumtab);
1470 return pta;
1471 }
1472
1473
1474 /*!
1475 * \brief pixCentroid()
1476 *
1477 * \param[in] pix 1 or 8 bpp
1478 * \param[in] centtab [optional] table for finding centroids; can be null
1479 * \param[in] sumtab [optional] table for finding pixel sums; can be null
1480 * \param[out] pxave x coordinate of centroid, relative to the UL corner
1481 * of the pix
1482 * \param[out] pyave y coordinate of centroid, relative to the UL corner
1483 * of the pix
1484 * \return 0 if OK, 1 on error
1485 *
1486 * <pre>
1487 * Notes:
1488 * (1) The sum and centroid tables are only used for 1 bpp.
1489 * (2) Any table not passed in will be made internally and destroyed
1490 * after use.
1491 * </pre>
1492 */
1493 l_ok
1494 pixCentroid(PIX *pix,
1495 l_int32 *centtab,
1496 l_int32 *sumtab,
1497 l_float32 *pxave,
1498 l_float32 *pyave)
1499 {
1500 l_int32 w, h, d, i, j, wpl, pixsum, rowsum, val;
1501 l_float32 xsum, ysum;
1502 l_uint32 *data, *line;
1503 l_uint32 word;
1504 l_uint8 byte;
1505 l_int32 *ctab, *stab;
1506
1507 if (!pxave || !pyave)
1508 return ERROR_INT("&pxave and &pyave not defined", __func__, 1);
1509 *pxave = *pyave = 0.0;
1510 if (!pix)
1511 return ERROR_INT("pix not defined", __func__, 1);
1512 pixGetDimensions(pix, &w, &h, &d);
1513 if (d != 1 && d != 8)
1514 return ERROR_INT("pix not 1 or 8 bpp", __func__, 1);
1515
1516 ctab = centtab;
1517 stab = sumtab;
1518 if (d == 1) {
1519 pixSetPadBits(pix, 0);
1520 if (!centtab)
1521 ctab = makePixelCentroidTab8();
1522 if (!sumtab)
1523 stab = makePixelSumTab8();
1524 }
1525
1526 data = pixGetData(pix);
1527 wpl = pixGetWpl(pix);
1528 xsum = ysum = 0.0;
1529 pixsum = 0;
1530 if (d == 1) {
1531 for (i = 0; i < h; i++) {
1532 /* The body of this loop computes the sum of the set
1533 * (1) bits on this row, weighted by their distance
1534 * from the left edge of pix, and accumulates that into
1535 * xsum; it accumulates their distance from the top
1536 * edge of pix into ysum, and their total count into
1537 * pixsum. It's equivalent to
1538 * for (j = 0; j < w; j++) {
1539 * if (GET_DATA_BIT(line, j)) {
1540 * xsum += j;
1541 * ysum += i;
1542 * pixsum++;
1543 * }
1544 * }
1545 */
1546 line = data + wpl * i;
1547 rowsum = 0;
1548 for (j = 0; j < wpl; j++) {
1549 word = line[j];
1550 if (word) {
1551 byte = word & 0xff;
1552 rowsum += stab[byte];
1553 xsum += ctab[byte] + (j * 32 + 24) * stab[byte];
1554 byte = (word >> 8) & 0xff;
1555 rowsum += stab[byte];
1556 xsum += ctab[byte] + (j * 32 + 16) * stab[byte];
1557 byte = (word >> 16) & 0xff;
1558 rowsum += stab[byte];
1559 xsum += ctab[byte] + (j * 32 + 8) * stab[byte];
1560 byte = (word >> 24) & 0xff;
1561 rowsum += stab[byte];
1562 xsum += ctab[byte] + j * 32 * stab[byte];
1563 }
1564 }
1565 pixsum += rowsum;
1566 ysum += rowsum * i;
1567 }
1568 if (pixsum == 0) {
1569 L_WARNING("no ON pixels in pix\n", __func__);
1570 } else {
1571 *pxave = xsum / (l_float32)pixsum;
1572 *pyave = ysum / (l_float32)pixsum;
1573 }
1574 } else { /* d == 8 */
1575 for (i = 0; i < h; i++) {
1576 line = data + wpl * i;
1577 for (j = 0; j < w; j++) {
1578 val = GET_DATA_BYTE(line, j);
1579 xsum += val * j;
1580 ysum += val * i;
1581 pixsum += val;
1582 }
1583 }
1584 if (pixsum == 0) {
1585 L_WARNING("all pixels are 0\n", __func__);
1586 } else {
1587 *pxave = xsum / (l_float32)pixsum;
1588 *pyave = ysum / (l_float32)pixsum;
1589 }
1590 }
1591
1592 if (d == 1) {
1593 if (!centtab) LEPT_FREE(ctab);
1594 if (!sumtab) LEPT_FREE(stab);
1595 }
1596 return 0;
1597 }