Coverage Report

Created: 2025-09-27 06:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/leptonica/src/ccthin.c
Line
Count
Source
1
/*====================================================================*
2
 -  Copyright (C) 2001 Leptonica.  All rights reserved.
3
 -
4
 -  Redistribution and use in source and binary forms, with or without
5
 -  modification, are permitted provided that the following conditions
6
 -  are met:
7
 -  1. Redistributions of source code must retain the above copyright
8
 -     notice, this list of conditions and the following disclaimer.
9
 -  2. Redistributions in binary form must reproduce the above
10
 -     copyright notice, this list of conditions and the following
11
 -     disclaimer in the documentation and/or other materials
12
 -     provided with the distribution.
13
 -
14
 -  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15
 -  ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16
 -  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17
 -  A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL ANY
18
 -  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19
 -  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20
 -  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21
 -  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22
 -  OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23
 -  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24
 -  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
 *====================================================================*/
26
27
/*!
28
 * \file ccthin.c
29
 * <pre>
30
 *
31
 *     PIXA   *pixaThinConnected()
32
 *     PIX    *pixThinConnected()
33
 *     PIX    *pixThinConnectedBySet()
34
 *     SELA   *selaMakeThinSets()
35
 * </pre>
36
 */
37
38
#ifdef HAVE_CONFIG_H
39
#include <config_auto.h>
40
#endif  /* HAVE_CONFIG_H */
41
42
#include "allheaders.h"
43
44
    /* ------------------------------------------------------------
45
     * The sels used here (and their rotated counterparts) are the
46
     * useful 3x3 Sels for thinning.   They are defined in sel2.c,
47
     * and the sets are constructed in selaMakeThinSets().
48
     * The notation is based on "Connectivity-preserving morphological
49
     * image transformations", a version of which can be found at
50
     *           http://www.leptonica.com/papers/conn.pdf
51
     * ------------------------------------------------------------ */
52
53
/*----------------------------------------------------------------*
54
 *                      CC-preserving thinning                    *
55
 *----------------------------------------------------------------*/
56
/*!
57
 * \brief   pixaThinConnected()
58
 *
59
 * \param[in]   pixas          of 1 bpp pix
60
 * \param[in]   type           L_THIN_FG, L_THIN_BG
61
 * \param[in]   connectivity   4 or 8
62
 * \param[in]   maxiters       max number of iters allowed;
63
 *                             use 0 to iterate until completion
64
 * \return  pixds, or NULL on error
65
 *
66
 * <pre>
67
 * Notes:
68
 *      (1) See notes in pixThinConnected().
69
 * </pre>
70
 */
71
PIXA *
72
pixaThinConnected(PIXA    *pixas,
73
                  l_int32  type,
74
                  l_int32  connectivity,
75
                  l_int32  maxiters)
76
1.70k
{
77
1.70k
l_int32  i, n, d, same;
78
1.70k
PIX     *pix1, *pix2;
79
1.70k
PIXA    *pixad;
80
1.70k
SELA    *sela;
81
82
1.70k
    if (!pixas)
83
1.70k
        return (PIXA *)ERROR_PTR("pixas not defined", __func__, NULL);
84
0
    if (type != L_THIN_FG && type != L_THIN_BG)
85
0
        return (PIXA *)ERROR_PTR("invalid fg/bg type", __func__, NULL);
86
0
    if (connectivity != 4 && connectivity != 8)
87
0
        return (PIXA *)ERROR_PTR("connectivity not 4 or 8", __func__, NULL);
88
0
    if (maxiters == 0) maxiters = 10000;
89
90
0
    pixaVerifyDepth(pixas, &same, &d);
91
0
    if (d != 1)
92
0
        return (PIXA *)ERROR_PTR("pix are not all 1 bpp", __func__, NULL);
93
94
0
    if (connectivity == 4)
95
0
        sela = selaMakeThinSets(1, 0);
96
0
    else  /* connectivity == 8 */
97
0
        sela = selaMakeThinSets(5, 0);
98
99
0
    n = pixaGetCount(pixas);
100
0
    pixad = pixaCreate(n);
101
0
    for (i = 0; i < n; i++) {
102
0
        pix1 = pixaGetPix(pixas, i, L_CLONE);
103
0
        pix2 = pixThinConnectedBySet(pix1, type, sela, maxiters);
104
0
        pixaAddPix(pixad, pix2, L_INSERT);
105
0
        pixDestroy(&pix1);
106
0
    }
107
108
0
    selaDestroy(&sela);
109
0
    return pixad;
110
0
}
111
112
113
/*!
114
 * \brief   pixThinConnected()
115
 *
116
 * \param[in]   pixs           1 bpp
117
 * \param[in]   type           L_THIN_FG, L_THIN_BG
118
 * \param[in]   connectivity   4 or 8
119
 * \param[in]   maxiters       max number of iters allowed;
120
 *                             use 0 to iterate until completion
121
 * \return  pixd, or NULL on error
122
 *
123
 * <pre>
124
 * Notes:
125
 *      (1) See "Connectivity-preserving morphological image transformations,"
126
 *          Dan S. Bloomberg, in SPIE Visual Communications and Image
127
 *          Processing, Conference 1606, pp. 320-334, November 1991,
128
 *          Boston, MA.   A web version is available at
129
 *              http://www.leptonica.com/papers/conn.pdf
130
 *      (2) This is a simple interface for two of the best iterative
131
 *          morphological thinning algorithms, for 4-c.c and 8-c.c.
132
 *          Each iteration uses a mixture of parallel operations
133
 *          (using several different 3x3 Sels) and serial operations.
134
 *          Specifically, each thinning iteration consists of
135
 *          four sequential thinnings from each of four directions.
136
 *          Each of these thinnings is a parallel composite
137
 *          operation, where the union of a set of HMTs are set
138
 *          subtracted from the input.  For 4-cc thinning, we
139
 *          use 3 HMTs in parallel, and for 8-cc thinning we use 4 HMTs.
140
 *      (3) A "good" thinning algorithm is one that generates a skeleton
141
 *          that is near the medial axis and has neither pruned
142
 *          real branches nor left extra dendritic branches.
143
 *      (4) Duality between operations on fg and bg require switching
144
 *          the connectivity.  To thin the foreground, which is the usual
145
 *          situation, use type == L_THIN_FG.  Thickening the foreground
146
 *          is equivalent to thinning the background (type == L_THIN_BG),
147
 *          where the alternate connectivity gets preserved.
148
 *          For example, to thicken the fg with 2 rounds of iterations
149
 *          using 4-c.c., thin the bg using Sels that preserve 8-connectivity:
150
 *             Pix *pix = pixThinConnected(pixs, L_THIN_BG, 8, 2);
151
 *      (5) This makes and destroys the sela set each time. It's not a large
152
 *          overhead, but if you are calling this thousands of times on
153
 *          very small images, you can avoid the overhead; e.g.
154
 *             Sela *sela = selaMakeThinSets(1, 0);  // for 4-c.c.
155
 *             Pix *pix = pixThinConnectedBySet(pixs, L_THIN_FG, sela, 0);
156
 *          using set 1 for 4-c.c. and set 5 for 8-c.c operations.
157
 * </pre>
158
 */
159
PIX *
160
pixThinConnected(PIX     *pixs,
161
                 l_int32  type,
162
                 l_int32  connectivity,
163
                 l_int32  maxiters)
164
231k
{
165
231k
PIX   *pixd;
166
231k
SELA  *sela;
167
168
231k
    if (!pixs)
169
41
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
170
231k
    if (pixGetDepth(pixs) != 1)
171
0
        return (PIX *)ERROR_PTR("pixs not 1 bpp", __func__, NULL);
172
231k
    if (type != L_THIN_FG && type != L_THIN_BG)
173
0
        return (PIX *)ERROR_PTR("invalid fg/bg type", __func__, NULL);
174
231k
    if (connectivity != 4 && connectivity != 8)
175
0
        return (PIX *)ERROR_PTR("connectivity not 4 or 8", __func__, NULL);
176
231k
    if (maxiters == 0) maxiters = 10000;
177
178
231k
    if (connectivity == 4)
179
0
        sela = selaMakeThinSets(1, 0);
180
231k
    else  /* connectivity == 8 */
181
231k
        sela = selaMakeThinSets(5, 0);
182
183
231k
    pixd = pixThinConnectedBySet(pixs, type, sela, maxiters);
184
185
231k
    selaDestroy(&sela);
186
231k
    return pixd;
187
231k
}
188
189
190
/*!
191
 * \brief   pixThinConnectedBySet()
192
 *
193
 * \param[in]   pixs       1 bpp
194
 * \param[in]   type       L_THIN_FG, L_THIN_BG
195
 * \param[in]   sela       of Sels for parallel composite HMTs
196
 * \param[in]   maxiters   max number of iters allowed;
197
 *                         use 0 to iterate until completion
198
 * \return  pixd, or NULL on error
199
 *
200
 * <pre>
201
 * Notes:
202
 *      (1) See notes in pixThinConnected().
203
 *      (2) This takes a sela representing one of 11 sets of HMT Sels.
204
 *          The HMTs from this set are run in parallel and the result
205
 *          is OR'd before being subtracted from the source.  For each
206
 *          iteration, this "parallel" thin is performed four times
207
 *          sequentially, for sels rotated by 90 degrees in all four
208
 *          directions.
209
 *      (3) The "parallel" and "sequential" nomenclature is standard
210
 *          in digital filtering.  Here, "parallel" operations work on the
211
 *          same source (pixd), and accumulate the results in a temp
212
 *          image before actually applying them to the source (in this
213
 *          case, using an in-place subtraction).  "Sequential" operations
214
 *          operate directly on the source (pixd) to produce the result
215
 *          (in this case, with four sequential thinning operations, one
216
 *          from each of four directions).
217
 * </pre>
218
 */
219
PIX *
220
pixThinConnectedBySet(PIX     *pixs,
221
                      l_int32  type,
222
                      SELA    *sela,
223
                      l_int32  maxiters)
224
231k
{
225
231k
l_int32  i, j, r, nsels, same;
226
231k
PIXA    *pixahmt;
227
231k
PIX    **pixhmt;  /* array owned by pixahmt; do not destroy! */
228
231k
PIX     *pix1, *pix2, *pixd;
229
231k
SEL     *sel, *selr;
230
231
231k
    if (!pixs)
232
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
233
231k
    if (pixGetDepth(pixs) != 1)
234
0
        return (PIX *)ERROR_PTR("pixs not 1 bpp", __func__, NULL);
235
231k
    if (type != L_THIN_FG && type != L_THIN_BG)
236
0
        return (PIX *)ERROR_PTR("invalid fg/bg type", __func__, NULL);
237
231k
    if (!sela)
238
0
        return (PIX *)ERROR_PTR("sela not defined", __func__, NULL);
239
231k
    if (maxiters == 0) maxiters = 10000;
240
241
        /* Set up array of temp pix to hold hmts */
242
231k
    nsels = selaGetCount(sela);
243
231k
    pixahmt = pixaCreate(nsels);
244
1.15M
    for (i = 0; i < nsels; i++) {
245
925k
        pix1 = pixCreateTemplate(pixs);
246
925k
        pixaAddPix(pixahmt, pix1, L_INSERT);
247
925k
    }
248
231k
    pixhmt = pixaGetPixArray(pixahmt);
249
231k
    if (!pixhmt) {
250
0
        pixaDestroy(&pixahmt);
251
0
        return (PIX *)ERROR_PTR("pixhmt array not made", __func__, NULL);
252
0
    }
253
254
        /* Set up initial image for fg thinning */
255
231k
    if (type == L_THIN_FG)
256
231k
        pixd = pixCopy(NULL, pixs);
257
0
    else  /* bg thinning */
258
0
        pixd = pixInvert(NULL, pixs);
259
260
        /* Thin the fg, with up to maxiters iterations */
261
624k
    for (i = 0; i < maxiters; i++) {
262
624k
        pix1 = pixCopy(NULL, pixd);  /* test for completion */
263
3.12M
        for (r = 0; r < 4; r++) {  /* over 90 degree rotations of Sels */
264
12.4M
            for (j = 0; j < nsels; j++) {  /* over individual sels in sela */
265
9.99M
                sel = selaGetSel(sela, j);  /* not a copy */
266
9.99M
                selr = selRotateOrth(sel, r);
267
9.99M
                pixHMT(pixhmt[j], pixd, selr);
268
9.99M
                selDestroy(&selr);
269
9.99M
                if (j > 0)
270
7.49M
                    pixOr(pixhmt[0], pixhmt[0], pixhmt[j]);  /* accum result */
271
9.99M
            }
272
2.49M
            pixSubtract(pixd, pixd, pixhmt[0]);  /* remove result */
273
2.49M
        }
274
624k
        pixEqual(pixd, pix1, &same);
275
624k
        pixDestroy(&pix1);
276
624k
        if (same) {
277
/*            L_INFO("%d iterations to completion\n", __func__, i); */
278
231k
            break;
279
231k
        }
280
624k
    }
281
282
        /* This is a bit tricky. If we're thickening the foreground, then
283
         * we get a fg border of thickness equal to the number of
284
         * iterations.  This border is connected to all components that
285
         * were initially touching the border, but as it grows, it does
286
         * not touch other growing components -- it leaves a 1 pixel wide
287
         * background between it and the growing components, and that
288
         * thin background prevents the components from growing further.
289
         * This border can be entirely removed as follows:
290
         * (1) Subtract the original (unthickened) image pixs from the
291
         *     thickened image. This removes the pixels that were originally
292
         *     touching the border.
293
         * (2) Get all remaining pixels that are connected to the border.
294
         * (3) Remove those pixels from the thickened image.  */
295
231k
    if (type == L_THIN_BG) {
296
0
        pixInvert(pixd, pixd);  /* finish with duality */
297
0
        pix1 = pixSubtract(NULL, pixd, pixs);
298
0
        pix2 = pixExtractBorderConnComps(pix1, 4);
299
0
        pixSubtract(pixd, pixd, pix2);
300
0
        pixDestroy(&pix1);
301
0
        pixDestroy(&pix2);
302
0
    }
303
304
231k
    pixaDestroy(&pixahmt);
305
231k
    return pixd;
306
231k
}
307
308
309
/*!
310
 * \brief   selaMakeThinSets()
311
 *
312
 * \param[in]   index   into specific sets
313
 * \param[in]   debug   1 to output display of sela
314
 * \return  sela, or NULL on error
315
 *
316
 * <pre>
317
 * Notes:
318
 *      (1) These are specific sets of HMTs to be used in parallel for
319
 *          for thinning from each of four directions.
320
 *      (2) The sets are indexed as follows:
321
 *          For thinning (e.g., run to completion):
322
 *              index = 1     sel_4_1, sel_4_2, sel_4_3
323
 *              index = 2     sel_4_1, sel_4_5, sel_4_6
324
 *              index = 3     sel_4_1, sel_4_7, sel_4_7_rot
325
 *              index = 4     sel_48_1, sel_48_1_rot, sel_48_2
326
 *              index = 5     sel_8_2, sel_8_3, sel_8_5, sel_8_6
327
 *              index = 6     sel_8_2, sel_8_3, sel_48_2
328
 *              index = 7     sel_8_1, sel_8_5, sel_8_6
329
 *              index = 8     sel_8_2, sel_8_3, sel_8_8, sel_8_9
330
 *              index = 9     sel_8_5, sel_8_6, sel_8_7, sel_8_7_rot
331
 *          For thickening (e.g., just a few iterations):
332
 *              index = 10    sel_4_2, sel_4_3
333
 *              index = 11    sel_8_4
334
 *      (3) For a very smooth skeleton, use set 1 for 4 connected and
335
 *          set 5 for 8 connected thins.
336
 * </pre>
337
 */
338
SELA *
339
selaMakeThinSets(l_int32  index,
340
                 l_int32  debug)
341
231k
{
342
231k
SEL   *sel;
343
231k
SELA  *sela1, *sela2, *sela3;
344
345
231k
    if (index < 1 || index > 11)
346
0
        return (SELA *)ERROR_PTR("invalid index", __func__, NULL);
347
348
231k
    sela2 = selaCreate(4);
349
231k
    switch(index)
350
231k
    {
351
0
    case 1:
352
0
        sela1 = sela4ccThin(NULL);
353
0
        selaFindSelByName(sela1, "sel_4_1", NULL, &sel);
354
0
        selaAddSel(sela2, sel, NULL, L_COPY);
355
0
        selaFindSelByName(sela1, "sel_4_2", NULL, &sel);
356
0
        selaAddSel(sela2, sel, NULL, L_COPY);
357
0
        selaFindSelByName(sela1, "sel_4_3", NULL, &sel);
358
0
        selaAddSel(sela2, sel, NULL, L_COPY);
359
0
        break;
360
0
    case 2:
361
0
        sela1 = sela4ccThin(NULL);
362
0
        selaFindSelByName(sela1, "sel_4_1", NULL, &sel);
363
0
        selaAddSel(sela2, sel, NULL, L_COPY);
364
0
        selaFindSelByName(sela1, "sel_4_5", NULL, &sel);
365
0
        selaAddSel(sela2, sel, NULL, L_COPY);
366
0
        selaFindSelByName(sela1, "sel_4_6", NULL, &sel);
367
0
        selaAddSel(sela2, sel, NULL, L_COPY);
368
0
        break;
369
0
    case 3:
370
0
        sela1 = sela4ccThin(NULL);
371
0
        selaFindSelByName(sela1, "sel_4_1", NULL, &sel);
372
0
        selaAddSel(sela2, sel, NULL, L_COPY);
373
0
        selaFindSelByName(sela1, "sel_4_7", NULL, &sel);
374
0
        selaAddSel(sela2, sel, NULL, L_COPY);
375
0
        sel = selRotateOrth(sel, 1);
376
0
        selaAddSel(sela2, sel, "sel_4_7_rot", L_INSERT);
377
0
        break;
378
0
    case 4:
379
0
        sela1 = sela4and8ccThin(NULL);
380
0
        selaFindSelByName(sela1, "sel_48_1", NULL, &sel);
381
0
        selaAddSel(sela2, sel, NULL, L_COPY);
382
0
        sel = selRotateOrth(sel, 1);
383
0
        selaAddSel(sela2, sel, "sel_48_1_rot", L_INSERT);
384
0
        selaFindSelByName(sela1, "sel_48_2", NULL, &sel);
385
0
        selaAddSel(sela2, sel, NULL, L_COPY);
386
0
        break;
387
231k
    case 5:
388
231k
        sela1 = sela8ccThin(NULL);
389
231k
        selaFindSelByName(sela1, "sel_8_2", NULL, &sel);
390
231k
        selaAddSel(sela2, sel, NULL, L_COPY);
391
231k
        selaFindSelByName(sela1, "sel_8_3", NULL, &sel);
392
231k
        selaAddSel(sela2, sel, NULL, L_COPY);
393
231k
        selaFindSelByName(sela1, "sel_8_5", NULL, &sel);
394
231k
        selaAddSel(sela2, sel, NULL, L_COPY);
395
231k
        selaFindSelByName(sela1, "sel_8_6", NULL, &sel);
396
231k
        selaAddSel(sela2, sel, NULL, L_COPY);
397
231k
        break;
398
0
    case 6:
399
0
        sela1 = sela8ccThin(NULL);
400
0
        sela3 = sela4and8ccThin(NULL);
401
0
        selaFindSelByName(sela1, "sel_8_2", NULL, &sel);
402
0
        selaAddSel(sela2, sel, NULL, L_COPY);
403
0
        selaFindSelByName(sela1, "sel_8_3", NULL, &sel);
404
0
        selaAddSel(sela2, sel, NULL, L_COPY);
405
0
        selaFindSelByName(sela3, "sel_48_2", NULL, &sel);
406
0
        selaAddSel(sela2, sel, NULL, L_COPY);
407
0
        selaDestroy(&sela3);
408
0
        break;
409
0
    case 7:
410
0
        sela1 = sela8ccThin(NULL);
411
0
        selaFindSelByName(sela1, "sel_8_1", NULL, &sel);
412
0
        selaAddSel(sela2, sel, NULL, L_COPY);
413
0
        selaFindSelByName(sela1, "sel_8_5", NULL, &sel);
414
0
        selaAddSel(sela2, sel, NULL, L_COPY);
415
0
        selaFindSelByName(sela1, "sel_8_6", NULL, &sel);
416
0
        selaAddSel(sela2, sel, NULL, L_COPY);
417
0
        break;
418
0
    case 8:
419
0
        sela1 = sela8ccThin(NULL);
420
0
        selaFindSelByName(sela1, "sel_8_2", NULL, &sel);
421
0
        selaAddSel(sela2, sel, NULL, L_COPY);
422
0
        selaFindSelByName(sela1, "sel_8_3", NULL, &sel);
423
0
        selaAddSel(sela2, sel, NULL, L_COPY);
424
0
        selaFindSelByName(sela1, "sel_8_8", NULL, &sel);
425
0
        selaAddSel(sela2, sel, NULL, L_COPY);
426
0
        selaFindSelByName(sela1, "sel_8_9", NULL, &sel);
427
0
        selaAddSel(sela2, sel, NULL, L_COPY);
428
0
        break;
429
0
    case 9:
430
0
        sela1 = sela8ccThin(NULL);
431
0
        selaFindSelByName(sela1, "sel_8_5", NULL, &sel);
432
0
        selaAddSel(sela2, sel, NULL, L_COPY);
433
0
        selaFindSelByName(sela1, "sel_8_6", NULL, &sel);
434
0
        selaAddSel(sela2, sel, NULL, L_COPY);
435
0
        selaFindSelByName(sela1, "sel_8_7", NULL, &sel);
436
0
        selaAddSel(sela2, sel, NULL, L_COPY);
437
0
        sel = selRotateOrth(sel, 1);
438
0
        selaAddSel(sela2, sel, "sel_8_7_rot", L_INSERT);
439
0
        break;
440
0
    case 10:  /* thicken for this one; use just a few iterations */
441
0
        sela1 = sela4ccThin(NULL);
442
0
        selaFindSelByName(sela1, "sel_4_2", NULL, &sel);
443
0
        selaAddSel(sela2, sel, NULL, L_COPY);
444
0
        selaFindSelByName(sela1, "sel_4_3", NULL, &sel);
445
0
        selaAddSel(sela2, sel, NULL, L_COPY);
446
0
        break;
447
0
    case 11:  /* thicken for this one; use just a few iterations */
448
0
        sela1 = sela8ccThin(NULL);
449
0
        selaFindSelByName(sela1, "sel_8_4", NULL, &sel);
450
0
        selaAddSel(sela2, sel, NULL, L_COPY);
451
0
        break;
452
231k
    }
453
454
        /* Optionally display the sel set */
455
231k
    if (debug) {
456
0
        PIX  *pix1;
457
0
        char  buf[32];
458
0
        lept_mkdir("/lept/sels");
459
0
        pix1 = selaDisplayInPix(sela2, 35, 3, 15, 4);
460
0
        snprintf(buf, sizeof(buf), "/tmp/lept/sels/set%d.png", index);
461
0
        pixWrite(buf, pix1, IFF_PNG);
462
0
        pixDisplay(pix1, 100, 100);
463
0
        pixDestroy(&pix1);
464
0
    }
465
466
231k
    selaDestroy(&sela1);
467
231k
    return sela2;
468
231k
}