Coverage Report

Created: 2024-06-18 06:05

/src/leptonica/src/morphseq.c
Line
Count
Source (jump to first uncovered line)
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 morphseq.c
29
 * <pre>
30
 *
31
 *      Run a sequence of binary rasterop morphological operations
32
 *            PIX     *pixMorphSequence()
33
 *
34
 *      Run a sequence of binary composite rasterop morphological operations
35
 *            PIX     *pixMorphCompSequence()
36
 *
37
 *      Run a sequence of binary dwa morphological operations
38
 *            PIX     *pixMorphSequenceDwa()
39
 *
40
 *      Run a sequence of binary composite dwa morphological operations
41
 *            PIX     *pixMorphCompSequenceDwa()
42
 *
43
 *      Parser verifier for binary morphological operations
44
 *            l_int32  morphSequenceVerify()
45
 *
46
 *      Run a sequence of grayscale morphological operations
47
 *            PIX     *pixGrayMorphSequence()
48
 *
49
 *      Run a sequence of color morphological operations
50
 *            PIX     *pixColorMorphSequence()
51
 * </pre>
52
 */
53
54
#ifdef HAVE_CONFIG_H
55
#include <config_auto.h>
56
#endif  /* HAVE_CONFIG_H */
57
58
#include <string.h>
59
#include "allheaders.h"
60
61
/*-------------------------------------------------------------------------*
62
 *         Run a sequence of binary rasterop morphological operations      *
63
 *-------------------------------------------------------------------------*/
64
/*!
65
 * \brief   pixMorphSequence()
66
 *
67
 * \param[in]    pixs
68
 * \param[in]    sequence   string specifying sequence
69
 * \param[in]    dispsep    controls debug display results in the sequence:
70
 *                          0: no output
71
 *                          > 0: gives horizontal separation in pixels between
72
 *                               successive displays
73
 *                          < 0: pdf output; abs(dispsep) is used for naming
74
 * \return  pixd, or NULL on error
75
 *
76
 * <pre>
77
 * Notes:
78
 *      (1) This does rasterop morphology on binary images.
79
 *      (2) This runs a pipeline of operations; no branching is allowed.
80
 *      (3) This only uses brick Sels, which are created on the fly.
81
 *          In the future this will be generalized to extract Sels from
82
 *          a Sela by name.
83
 *      (4) A new image is always produced; the input image is not changed.
84
 *      (5) This contains an interpreter, allowing sequences to be
85
 *          generated and run.
86
 *      (6) The format of the sequence string is defined below.
87
 *      (7) In addition to morphological operations, rank order reduction
88
 *          and replicated expansion allow operations to take place
89
 *          downscaled by a power of 2.
90
 *      (8) Intermediate results can optionally be displayed.
91
 *      (9) Thanks to Dar-Shyang Lee, who had the idea for this and
92
 *          built the first implementation.
93
 *      (10) The sequence string is formatted as follows:
94
 *            ~ An arbitrary number of operations,  each separated
95
 *              by a '+' character.  White space is ignored.
96
 *            ~ Each operation begins with a case-independent character
97
 *              specifying the operation:
98
 *                 d or D  (dilation)
99
 *                 e or E  (erosion)
100
 *                 o or O  (opening)
101
 *                 c or C  (closing)
102
 *                 r or R  (rank binary reduction)
103
 *                 x or X  (replicative binary expansion)
104
 *                 b or B  (add a border of 0 pixels of this size)
105
 *            ~ The args to the morphological operations are bricks of hits,
106
 *              and are formatted as a.b, where a and b are horizontal and
107
 *              vertical dimensions, rsp.
108
 *            ~ The args to the reduction are a sequence of up to 4 integers,
109
 *              each from 1 to 4.
110
 *            ~ The arg to the expansion is a power of two, in the set
111
 *              {2, 4, 8, 16}.
112
 *      (11) An example valid sequence is:
113
 *               "b32 + o1.3 + C3.1 + r23 + e2.2 + D3.2 + X4"
114
 *           In this example, the following operation sequence is carried out:
115
 *             * b32: Add a 32 pixel border around the input image
116
 *             * o1.3: Opening with vert sel of length 3 (e.g., 1 x 3)
117
 *             * C3.1: Closing with horiz sel of length 3  (e.g., 3 x 1)
118
 *             * r23: Two successive 2x2 reductions with rank 2 in the first
119
 *                    and rank 3 in the second.  The result is a 4x reduced pix.
120
 *             * e2.2: Erosion with a 2x2 sel (origin will be at x,y: 0,0)
121
 *             * d3.2: Dilation with a 3x2 sel (origin will be at x,y: 1,0)
122
 *             * X4: 4x replicative expansion, back to original resolution
123
 *      (12) The safe closing is used.  However, if you implement a
124
 *           closing as separable dilations followed by separable erosions,
125
 *           it will not be safe.  For that situation, you need to add
126
 *           a sufficiently large border as the first operation in
127
 *           the sequence.  This will be removed automatically at the
128
 *           end.  There are two cautions:
129
 *              ~ When computing what is sufficient, remember that if
130
 *                reductions are carried out, the border is also reduced.
131
 *              ~ The border is removed at the end, so if a border is
132
 *                added at the beginning, the result must be at the
133
 *                same resolution as the input!
134
 * </pre>
135
 */
136
PIX *
137
pixMorphSequence(PIX         *pixs,
138
                 const char  *sequence,
139
                 l_int32      dispsep)
140
847
{
141
847
char    *rawop, *op;
142
847
char     fname[256];
143
847
l_int32  nops, i, j, nred, fact, w, h, x, border, pdfout;
144
847
l_int32  level[4];
145
847
PIX     *pix1, *pix2;
146
847
PIXA    *pixa;
147
847
SARRAY  *sa;
148
149
847
    if (!pixs)
150
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
151
847
    if (!sequence)
152
0
        return (PIX *)ERROR_PTR("sequence not defined", __func__, NULL);
153
154
        /* Split sequence into individual operations */
155
847
    sa = sarrayCreate(0);
156
847
    sarraySplitString(sa, sequence, "+");
157
847
    nops = sarrayGetCount(sa);
158
847
    pdfout = (dispsep < 0) ? 1 : 0;
159
847
    if (!morphSequenceVerify(sa)) {
160
0
        sarrayDestroy(&sa);
161
0
        return (PIX *)ERROR_PTR("sequence not valid", __func__, NULL);
162
0
    }
163
164
        /* Parse and operate */
165
847
    pixa = NULL;
166
847
    if (pdfout) {
167
0
        pixa = pixaCreate(0);
168
0
        pixaAddPix(pixa, pixs, L_CLONE);
169
0
    }
170
847
    border = 0;
171
847
    pix1 = pixCopy(NULL, pixs);
172
847
    pix2 = NULL;
173
847
    x = 0;
174
2.54k
    for (i = 0; i < nops; i++) {
175
1.69k
        rawop = sarrayGetString(sa, i, L_NOCOPY);
176
1.69k
        op = stringRemoveChars(rawop, " \n\t");
177
1.69k
        switch (op[0])
178
1.69k
        {
179
1.69k
        case 'd':
180
1.69k
        case 'D':
181
1.69k
            sscanf(&op[1], "%d.%d", &w, &h);
182
1.69k
            pix2 = pixDilateBrick(NULL, pix1, w, h);
183
1.69k
            pixSwapAndDestroy(&pix1, &pix2);
184
1.69k
            break;
185
0
        case 'e':
186
0
        case 'E':
187
0
            sscanf(&op[1], "%d.%d", &w, &h);
188
0
            pix2 = pixErodeBrick(NULL, pix1, w, h);
189
0
            pixSwapAndDestroy(&pix1, &pix2);
190
0
            break;
191
0
        case 'o':
192
0
        case 'O':
193
0
            sscanf(&op[1], "%d.%d", &w, &h);
194
0
            pixOpenBrick(pix1, pix1, w, h);
195
0
            break;
196
0
        case 'c':
197
0
        case 'C':
198
0
            sscanf(&op[1], "%d.%d", &w, &h);
199
0
            pixCloseSafeBrick(pix1, pix1, w, h);
200
0
            break;
201
0
        case 'r':
202
0
        case 'R':
203
0
            nred = strlen(op) - 1;
204
0
            for (j = 0; j < nred; j++)
205
0
                level[j] = op[j + 1] - '0';
206
0
            for (j = nred; j < 4; j++)
207
0
                level[j] = 0;
208
0
            pix2 = pixReduceRankBinaryCascade(pix1, level[0], level[1],
209
0
                                               level[2], level[3]);
210
0
            pixSwapAndDestroy(&pix1, &pix2);
211
0
            break;
212
0
        case 'x':
213
0
        case 'X':
214
0
            sscanf(&op[1], "%d", &fact);
215
0
            pix2 = pixExpandReplicate(pix1, fact);
216
0
            pixSwapAndDestroy(&pix1, &pix2);
217
0
            break;
218
0
        case 'b':
219
0
        case 'B':
220
0
            sscanf(&op[1], "%d", &border);
221
0
            pix2 = pixAddBorder(pix1, border, 0);
222
0
            pixSwapAndDestroy(&pix1, &pix2);
223
0
            break;
224
0
        default:
225
            /* All invalid ops are caught in the first pass */
226
0
            break;
227
1.69k
        }
228
1.69k
        LEPT_FREE(op);
229
230
            /* Debug output */
231
1.69k
        if (dispsep > 0) {
232
0
            pixDisplay(pix1, x, 0);
233
0
            x += dispsep;
234
0
        }
235
1.69k
        if (pdfout)
236
0
            pixaAddPix(pixa, pix1, L_COPY);
237
1.69k
    }
238
847
    if (border > 0) {
239
0
        pix2 = pixRemoveBorder(pix1, border);
240
0
        pixSwapAndDestroy(&pix1, &pix2);
241
0
    }
242
243
847
    if (pdfout) {
244
0
        snprintf(fname, sizeof(fname), "/tmp/lept/seq_output_%d.pdf",
245
0
                 L_ABS(dispsep));
246
0
        pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname);
247
0
        pixaDestroy(&pixa);
248
0
    }
249
250
847
    sarrayDestroy(&sa);
251
847
    return pix1;
252
847
}
253
254
255
/*-------------------------------------------------------------------------*
256
 *   Run a sequence of binary composite rasterop morphological operations  *
257
 *-------------------------------------------------------------------------*/
258
/*!
259
 * \brief   pixMorphCompSequence()
260
 *
261
 * \param[in]    pixs
262
 * \param[in]    sequence   string specifying sequence
263
 * \param[in]    dispsep    controls debug display of results in the sequence:
264
 *                          0: no output
265
 *                          > 0: gives horizontal separation in pixels between
266
 *                               successive displays
267
 *                          < 0: pdf output; abs(dispsep) is used for naming
268
 * \return  pixd, or NULL on error
269
 *
270
 * <pre>
271
 * Notes:
272
 *      (1) This does rasterop morphology on binary images, using composite
273
 *          operations for extra speed on large Sels.
274
 *      (2) Safe closing is used atomically.  However, if you implement a
275
 *          closing as a sequence with a dilation followed by an
276
 *          erosion, it will not be safe, and to ensure that you have
277
 *          no boundary effects you must add a border in advance and
278
 *          remove it at the end.
279
 *      (3) For other usage details, see the notes for pixMorphSequence().
280
 *      (4) The sequence string is formatted as follows:
281
 *            ~ An arbitrary number of operations,  each separated
282
 *              by a '+' character.  White space is ignored.
283
 *            ~ Each operation begins with a case-independent character
284
 *              specifying the operation:
285
 *                 d or D  (dilation)
286
 *                 e or E  (erosion)
287
 *                 o or O  (opening)
288
 *                 c or C  (closing)
289
 *                 r or R  (rank binary reduction)
290
 *                 x or X  (replicative binary expansion)
291
 *                 b or B  (add a border of 0 pixels of this size)
292
 *            ~ The args to the morphological operations are bricks of hits,
293
 *              and are formatted as a.b, where a and b are horizontal and
294
 *              vertical dimensions, rsp.
295
 *            ~ The args to the reduction are a sequence of up to 4 integers,
296
 *              each from 1 to 4.
297
 *            ~ The arg to the expansion is a power of two, in the set
298
 *              {2, 4, 8, 16}.
299
 * </pre>
300
 */
301
PIX *
302
pixMorphCompSequence(PIX         *pixs,
303
                     const char  *sequence,
304
                     l_int32      dispsep)
305
0
{
306
0
char    *rawop, *op;
307
0
char     fname[256];
308
0
l_int32  nops, i, j, nred, fact, w, h, x, border, pdfout;
309
0
l_int32  level[4];
310
0
PIX     *pix1, *pix2;
311
0
PIXA    *pixa;
312
0
SARRAY  *sa;
313
314
0
    if (!pixs)
315
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
316
0
    if (!sequence)
317
0
        return (PIX *)ERROR_PTR("sequence not defined", __func__, NULL);
318
319
        /* Split sequence into individual operations */
320
0
    sa = sarrayCreate(0);
321
0
    sarraySplitString(sa, sequence, "+");
322
0
    nops = sarrayGetCount(sa);
323
0
    pdfout = (dispsep < 0) ? 1 : 0;
324
325
0
    if (!morphSequenceVerify(sa)) {
326
0
        sarrayDestroy(&sa);
327
0
        return (PIX *)ERROR_PTR("sequence not valid", __func__, NULL);
328
0
    }
329
330
        /* Parse and operate */
331
0
    pixa = NULL;
332
0
    if (pdfout) {
333
0
        pixa = pixaCreate(0);
334
0
        pixaAddPix(pixa, pixs, L_CLONE);
335
0
    }
336
0
    border = 0;
337
0
    pix1 = pixCopy(NULL, pixs);
338
0
    pix2 = NULL;
339
0
    x = 0;
340
0
    for (i = 0; i < nops; i++) {
341
0
        rawop = sarrayGetString(sa, i, L_NOCOPY);
342
0
        op = stringRemoveChars(rawop, " \n\t");
343
0
        switch (op[0])
344
0
        {
345
0
        case 'd':
346
0
        case 'D':
347
0
            sscanf(&op[1], "%d.%d", &w, &h);
348
0
            pix2 = pixDilateCompBrick(NULL, pix1, w, h);
349
0
            pixSwapAndDestroy(&pix1, &pix2);
350
0
            break;
351
0
        case 'e':
352
0
        case 'E':
353
0
            sscanf(&op[1], "%d.%d", &w, &h);
354
0
            pix2 = pixErodeCompBrick(NULL, pix1, w, h);
355
0
            pixSwapAndDestroy(&pix1, &pix2);
356
0
            break;
357
0
        case 'o':
358
0
        case 'O':
359
0
            sscanf(&op[1], "%d.%d", &w, &h);
360
0
            pixOpenCompBrick(pix1, pix1, w, h);
361
0
            break;
362
0
        case 'c':
363
0
        case 'C':
364
0
            sscanf(&op[1], "%d.%d", &w, &h);
365
0
            pixCloseSafeCompBrick(pix1, pix1, w, h);
366
0
            break;
367
0
        case 'r':
368
0
        case 'R':
369
0
            nred = strlen(op) - 1;
370
0
            for (j = 0; j < nred; j++)
371
0
                level[j] = op[j + 1] - '0';
372
0
            for (j = nred; j < 4; j++)
373
0
                level[j] = 0;
374
0
            pix2 = pixReduceRankBinaryCascade(pix1, level[0], level[1],
375
0
                                               level[2], level[3]);
376
0
            pixSwapAndDestroy(&pix1, &pix2);
377
0
            break;
378
0
        case 'x':
379
0
        case 'X':
380
0
            sscanf(&op[1], "%d", &fact);
381
0
            pix2 = pixExpandReplicate(pix1, fact);
382
0
            pixSwapAndDestroy(&pix1, &pix2);
383
0
            break;
384
0
        case 'b':
385
0
        case 'B':
386
0
            sscanf(&op[1], "%d", &border);
387
0
            pix2 = pixAddBorder(pix1, border, 0);
388
0
            pixSwapAndDestroy(&pix1, &pix2);
389
0
            break;
390
0
        default:
391
            /* All invalid ops are caught in the first pass */
392
0
            break;
393
0
        }
394
0
        LEPT_FREE(op);
395
396
            /* Debug output */
397
0
        if (dispsep > 0) {
398
0
            pixDisplay(pix1, x, 0);
399
0
            x += dispsep;
400
0
        }
401
0
        if (pdfout)
402
0
            pixaAddPix(pixa, pix1, L_COPY);
403
0
    }
404
0
    if (border > 0) {
405
0
        pix2 = pixRemoveBorder(pix1, border);
406
0
        pixSwapAndDestroy(&pix1, &pix2);
407
0
    }
408
409
0
    if (pdfout) {
410
0
        snprintf(fname, sizeof(fname), "/tmp/lept/seq_output_%d.pdf",
411
0
                 L_ABS(dispsep));
412
0
        pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname);
413
0
        pixaDestroy(&pixa);
414
0
    }
415
416
0
    sarrayDestroy(&sa);
417
0
    return pix1;
418
0
}
419
420
421
/*-------------------------------------------------------------------------*
422
 *           Run a sequence of binary dwa morphological operations         *
423
 *-------------------------------------------------------------------------*/
424
/*!
425
 * \brief   pixMorphSequenceDwa()
426
 *
427
 * \param[in]    pixs
428
 * \param[in]    sequence   string specifying sequence
429
 * \param[in]    dispsep    controls debug display of results in the sequence:
430
 *                          0: no output
431
 *                          > 0: gives horizontal separation in pixels between
432
 *                               successive displays
433
 *                          < 0: pdf output; abs(dispsep) is used for naming
434
 * \return  pixd, or NULL on error
435
 *
436
 * <pre>
437
 * Notes:
438
 *      (1) This does dwa morphology on binary images.
439
 *      (2) This runs a pipeline of operations; no branching is allowed.
440
 *      (3) This only uses brick Sels that have been pre-compiled with
441
 *          dwa code.
442
 *      (4) A new image is always produced; the input image is not changed.
443
 *      (5) This contains an interpreter, allowing sequences to be
444
 *          generated and run.
445
 *      (6) See pixMorphSequence() for further information about usage.
446
 * </pre>
447
 */
448
PIX *
449
pixMorphSequenceDwa(PIX         *pixs,
450
                    const char  *sequence,
451
                    l_int32      dispsep)
452
0
{
453
0
char    *rawop, *op;
454
0
char     fname[256];
455
0
l_int32  nops, i, j, nred, fact, w, h, x, border, pdfout;
456
0
l_int32  level[4];
457
0
PIX     *pix1, *pix2;
458
0
PIXA    *pixa;
459
0
SARRAY  *sa;
460
461
0
    if (!pixs)
462
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
463
0
    if (!sequence)
464
0
        return (PIX *)ERROR_PTR("sequence not defined", __func__, NULL);
465
466
        /* Split sequence into individual operations */
467
0
    sa = sarrayCreate(0);
468
0
    sarraySplitString(sa, sequence, "+");
469
0
    nops = sarrayGetCount(sa);
470
0
    pdfout = (dispsep < 0) ? 1 : 0;
471
472
0
    if (!morphSequenceVerify(sa)) {
473
0
        sarrayDestroy(&sa);
474
0
        return (PIX *)ERROR_PTR("sequence not valid", __func__, NULL);
475
0
    }
476
477
        /* Parse and operate */
478
0
    pixa = NULL;
479
0
    if (pdfout) {
480
0
        pixa = pixaCreate(0);
481
0
        pixaAddPix(pixa, pixs, L_CLONE);
482
0
    }
483
0
    border = 0;
484
0
    pix1 = pixCopy(NULL, pixs);
485
0
    pix2 = NULL;
486
0
    x = 0;
487
0
    for (i = 0; i < nops; i++) {
488
0
        rawop = sarrayGetString(sa, i, L_NOCOPY);
489
0
        op = stringRemoveChars(rawop, " \n\t");
490
0
        switch (op[0])
491
0
        {
492
0
        case 'd':
493
0
        case 'D':
494
0
            sscanf(&op[1], "%d.%d", &w, &h);
495
0
            pix2 = pixDilateBrickDwa(NULL, pix1, w, h);
496
0
            pixSwapAndDestroy(&pix1, &pix2);
497
0
            break;
498
0
        case 'e':
499
0
        case 'E':
500
0
            sscanf(&op[1], "%d.%d", &w, &h);
501
0
            pix2 = pixErodeBrickDwa(NULL, pix1, w, h);
502
0
            pixSwapAndDestroy(&pix1, &pix2);
503
0
            break;
504
0
        case 'o':
505
0
        case 'O':
506
0
            sscanf(&op[1], "%d.%d", &w, &h);
507
0
            pixOpenBrickDwa(pix1, pix1, w, h);
508
0
            break;
509
0
        case 'c':
510
0
        case 'C':
511
0
            sscanf(&op[1], "%d.%d", &w, &h);
512
0
            pixCloseBrickDwa(pix1, pix1, w, h);
513
0
            break;
514
0
        case 'r':
515
0
        case 'R':
516
0
            nred = strlen(op) - 1;
517
0
            for (j = 0; j < nred; j++)
518
0
                level[j] = op[j + 1] - '0';
519
0
            for (j = nred; j < 4; j++)
520
0
                level[j] = 0;
521
0
            pix2 = pixReduceRankBinaryCascade(pix1, level[0], level[1],
522
0
                                               level[2], level[3]);
523
0
            pixSwapAndDestroy(&pix1, &pix2);
524
0
            break;
525
0
        case 'x':
526
0
        case 'X':
527
0
            sscanf(&op[1], "%d", &fact);
528
0
            pix2 = pixExpandReplicate(pix1, fact);
529
0
            pixSwapAndDestroy(&pix1, &pix2);
530
0
            break;
531
0
        case 'b':
532
0
        case 'B':
533
0
            sscanf(&op[1], "%d", &border);
534
0
            pix2 = pixAddBorder(pix1, border, 0);
535
0
            pixSwapAndDestroy(&pix1, &pix2);
536
0
            break;
537
0
        default:
538
            /* All invalid ops are caught in the first pass */
539
0
            break;
540
0
        }
541
0
        LEPT_FREE(op);
542
543
            /* Debug output */
544
0
        if (dispsep > 0) {
545
0
            pixDisplay(pix1, x, 0);
546
0
            x += dispsep;
547
0
        }
548
0
        if (pdfout)
549
0
            pixaAddPix(pixa, pix1, L_COPY);
550
0
    }
551
0
    if (border > 0) {
552
0
        pix2 = pixRemoveBorder(pix1, border);
553
0
        pixSwapAndDestroy(&pix1, &pix2);
554
0
    }
555
556
0
    if (pdfout) {
557
0
        snprintf(fname, sizeof(fname), "/tmp/lept/seq_output_%d.pdf",
558
0
                 L_ABS(dispsep));
559
0
        pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname);
560
0
        pixaDestroy(&pixa);
561
0
    }
562
563
0
    sarrayDestroy(&sa);
564
0
    return pix1;
565
0
}
566
567
568
/*-------------------------------------------------------------------------*
569
 *      Run a sequence of binary composite dwa morphological operations    *
570
 *-------------------------------------------------------------------------*/
571
/*!
572
 * \brief   pixMorphCompSequenceDwa()
573
 *
574
 * \param[in]    pixs
575
 * \param[in]    sequence   string specifying sequence
576
 * \param[in]    dispsep    controls debug display of results in the sequence:
577
 *                          0: no output
578
 *                          > 0: gives horizontal separation in pixels between
579
 *                               successive displays
580
 *                          < 0: pdf output; abs(dispsep) is used for naming
581
 * \return  pixd, or NULL on error
582
 *
583
 * <pre>
584
 * Notes:
585
 *      (1) This does dwa morphology on binary images, using brick Sels.
586
 *      (2) This runs a pipeline of operations; no branching is allowed.
587
 *      (3) It implements all brick Sels that have dimensions up to 63
588
 *          on each side, using a composite (linear + comb) when useful.
589
 *      (4) A new image is always produced; the input image is not changed.
590
 *      (5) This contains an interpreter, allowing sequences to be
591
 *          generated and run.
592
 *      (6) See pixMorphSequence() for further information about usage.
593
 * </pre>
594
 */
595
PIX *
596
pixMorphCompSequenceDwa(PIX         *pixs,
597
                        const char  *sequence,
598
                        l_int32      dispsep)
599
0
{
600
0
char    *rawop, *op;
601
0
char     fname[256];
602
0
l_int32  nops, i, j, nred, fact, w, h, x, border, pdfout;
603
0
l_int32  level[4];
604
0
PIX     *pix1, *pix2;
605
0
PIXA    *pixa;
606
0
SARRAY  *sa;
607
608
0
    if (!pixs)
609
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
610
0
    if (!sequence)
611
0
        return (PIX *)ERROR_PTR("sequence not defined", __func__, NULL);
612
613
        /* Split sequence into individual operations */
614
0
    sa = sarrayCreate(0);
615
0
    sarraySplitString(sa, sequence, "+");
616
0
    nops = sarrayGetCount(sa);
617
0
    pdfout = (dispsep < 0) ? 1 : 0;
618
619
0
    if (!morphSequenceVerify(sa)) {
620
0
        sarrayDestroy(&sa);
621
0
        return (PIX *)ERROR_PTR("sequence not valid", __func__, NULL);
622
0
    }
623
624
        /* Parse and operate */
625
0
    pixa = NULL;
626
0
    if (pdfout) {
627
0
        pixa = pixaCreate(0);
628
0
        pixaAddPix(pixa, pixs, L_CLONE);
629
0
    }
630
0
    border = 0;
631
0
    pix1 = pixCopy(NULL, pixs);
632
0
    pix2 = NULL;
633
0
    x = 0;
634
0
    for (i = 0; i < nops; i++) {
635
0
        rawop = sarrayGetString(sa, i, L_NOCOPY);
636
0
        op = stringRemoveChars(rawop, " \n\t");
637
0
        switch (op[0])
638
0
        {
639
0
        case 'd':
640
0
        case 'D':
641
0
            sscanf(&op[1], "%d.%d", &w, &h);
642
0
            pix2 = pixDilateCompBrickDwa(NULL, pix1, w, h);
643
0
            pixSwapAndDestroy(&pix1, &pix2);
644
0
            break;
645
0
        case 'e':
646
0
        case 'E':
647
0
            sscanf(&op[1], "%d.%d", &w, &h);
648
0
            pix2 = pixErodeCompBrickDwa(NULL, pix1, w, h);
649
0
            pixSwapAndDestroy(&pix1, &pix2);
650
0
            break;
651
0
        case 'o':
652
0
        case 'O':
653
0
            sscanf(&op[1], "%d.%d", &w, &h);
654
0
            pixOpenCompBrickDwa(pix1, pix1, w, h);
655
0
            break;
656
0
        case 'c':
657
0
        case 'C':
658
0
            sscanf(&op[1], "%d.%d", &w, &h);
659
0
            pixCloseCompBrickDwa(pix1, pix1, w, h);
660
0
            break;
661
0
        case 'r':
662
0
        case 'R':
663
0
            nred = strlen(op) - 1;
664
0
            for (j = 0; j < nred; j++)
665
0
                level[j] = op[j + 1] - '0';
666
0
            for (j = nred; j < 4; j++)
667
0
                level[j] = 0;
668
0
            pix2 = pixReduceRankBinaryCascade(pix1, level[0], level[1],
669
0
                                               level[2], level[3]);
670
0
            pixSwapAndDestroy(&pix1, &pix2);
671
0
            break;
672
0
        case 'x':
673
0
        case 'X':
674
0
            sscanf(&op[1], "%d", &fact);
675
0
            pix2 = pixExpandReplicate(pix1, fact);
676
0
            pixSwapAndDestroy(&pix1, &pix2);
677
0
            break;
678
0
        case 'b':
679
0
        case 'B':
680
0
            sscanf(&op[1], "%d", &border);
681
0
            pix2 = pixAddBorder(pix1, border, 0);
682
0
            pixSwapAndDestroy(&pix1, &pix2);
683
0
            break;
684
0
        default:
685
            /* All invalid ops are caught in the first pass */
686
0
            break;
687
0
        }
688
0
        LEPT_FREE(op);
689
690
            /* Debug output */
691
0
        if (dispsep > 0) {
692
0
            pixDisplay(pix1, x, 0);
693
0
            x += dispsep;
694
0
        }
695
0
        if (pdfout)
696
0
            pixaAddPix(pixa, pix1, L_COPY);
697
0
    }
698
0
    if (border > 0) {
699
0
        pix2 = pixRemoveBorder(pix1, border);
700
0
        pixSwapAndDestroy(&pix1, &pix2);
701
0
    }
702
703
0
    if (pdfout) {
704
0
        snprintf(fname, sizeof(fname), "/tmp/lept/seq_output_%d.pdf",
705
0
                 L_ABS(dispsep));
706
0
        pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname);
707
0
        pixaDestroy(&pixa);
708
0
    }
709
710
0
    sarrayDestroy(&sa);
711
0
    return pix1;
712
0
}
713
714
715
/*-------------------------------------------------------------------------*
716
 *            Parser verifier for binary morphological operations          *
717
 *-------------------------------------------------------------------------*/
718
/*!
719
 * \brief   morphSequenceVerify()
720
 *
721
 * \param[in]    sa    string array of operation sequence
722
 * \return  TRUE if valid; FALSE otherwise or on error
723
 *
724
 * <pre>
725
 * Notes:
726
 *      (1) This does verification of valid binary morphological
727
 *          operation sequences.
728
 *      (2) See pixMorphSequence() for notes on valid operations
729
 *          in the sequence.
730
 * </pre>
731
 */
732
l_int32
733
morphSequenceVerify(SARRAY  *sa)
734
847
{
735
847
char    *rawop, *op = NULL;
736
847
l_int32  nops, i, j, nred, fact, valid, w, h, netred, border;
737
847
l_int32  level[4];
738
847
l_int32  intlogbase2[5] = {1, 2, 3, 0, 4};  /* of arg/4 */
739
740
847
    if (!sa)
741
0
        return ERROR_INT("sa not defined", __func__, FALSE);
742
743
847
    nops = sarrayGetCount(sa);
744
847
    valid = TRUE;
745
847
    netred = 0;
746
847
    border = 0;
747
2.54k
    for (i = 0; i < nops; i++) {
748
1.69k
        rawop = sarrayGetString(sa, i, L_NOCOPY);
749
1.69k
        op = stringRemoveChars(rawop, " \n\t");
750
1.69k
        switch (op[0])
751
1.69k
        {
752
1.69k
        case 'd':
753
1.69k
        case 'D':
754
1.69k
        case 'e':
755
1.69k
        case 'E':
756
1.69k
        case 'o':
757
1.69k
        case 'O':
758
1.69k
        case 'c':
759
1.69k
        case 'C':
760
1.69k
            if (sscanf(&op[1], "%d.%d", &w, &h) != 2) {
761
0
                lept_stderr("*** op: %s invalid\n", op);
762
0
                valid = FALSE;
763
0
                break;
764
0
            }
765
1.69k
            if (w <= 0 || h <= 0) {
766
0
                lept_stderr("*** op: %s; w = %d, h = %d; must both be > 0\n",
767
0
                            op, w, h);
768
0
                valid = FALSE;
769
0
                break;
770
0
            }
771
/*            lept_stderr("op = %s; w = %d, h = %d\n", op, w, h); */
772
1.69k
            break;
773
1.69k
        case 'r':
774
0
        case 'R':
775
0
            nred = strlen(op) - 1;
776
0
            netred += nred;
777
0
            if (nred < 1 || nred > 4) {
778
0
                lept_stderr(
779
0
                        "*** op = %s; num reduct = %d; must be in {1,2,3,4}\n",
780
0
                        op, nred);
781
0
                valid = FALSE;
782
0
                break;
783
0
            }
784
0
            for (j = 0; j < nred; j++) {
785
0
                level[j] = op[j + 1] - '0';
786
0
                if (level[j] < 1 || level[j] > 4) {
787
0
                    lept_stderr("*** op = %s; level[%d] = %d is invalid\n",
788
0
                                op, j, level[j]);
789
0
                    valid = FALSE;
790
0
                    break;
791
0
                }
792
0
            }
793
0
            if (!valid)
794
0
                break;
795
/*            lept_stderr("op = %s", op); */
796
0
            for (j = 0; j < nred; j++) {
797
0
                level[j] = op[j + 1] - '0';
798
/*                lept_stderr(", level[%d] = %d", j, level[j]); */
799
0
            }
800
/*            lept_stderr("\n"); */
801
0
            break;
802
0
        case 'x':
803
0
        case 'X':
804
0
            if (sscanf(&op[1], "%d", &fact) != 1) {
805
0
                lept_stderr("*** op: %s; fact invalid\n", op);
806
0
                valid = FALSE;
807
0
                break;
808
0
            }
809
0
            if (fact != 2 && fact != 4 && fact != 8 && fact != 16) {
810
0
                lept_stderr("*** op = %s; invalid fact = %d\n", op, fact);
811
0
                valid = FALSE;
812
0
                break;
813
0
            }
814
0
            netred -= intlogbase2[fact / 4];
815
/*            lept_stderr("op = %s; fact = %d\n", op, fact); */
816
0
            break;
817
0
        case 'b':
818
0
        case 'B':
819
0
            if (sscanf(&op[1], "%d", &fact) != 1) {
820
0
                lept_stderr("*** op: %s; fact invalid\n", op);
821
0
                valid = FALSE;
822
0
                break;
823
0
            }
824
0
            if (i > 0) {
825
0
                lept_stderr("*** op = %s; must be first op\n", op);
826
0
                valid = FALSE;
827
0
                break;
828
0
            }
829
0
            if (fact < 1) {
830
0
                lept_stderr("*** op = %s; invalid fact = %d\n", op, fact);
831
0
                valid = FALSE;
832
0
                break;
833
0
            }
834
0
            border = fact;
835
/*            lept_stderr("op = %s; fact = %d\n", op, fact); */
836
0
            break;
837
0
        default:
838
0
            lept_stderr("*** nonexistent op = %s\n", op);
839
0
            valid = FALSE;
840
1.69k
        }
841
1.69k
        LEPT_FREE(op);
842
1.69k
    }
843
844
847
    if (border != 0 && netred != 0) {
845
0
        lept_stderr("*** op = %s; border added but net reduction not 0\n", op);
846
0
        valid = FALSE;
847
0
    }
848
847
    return valid;
849
847
}
850
851
852
/*-----------------------------------------------------------------*
853
 *       Run a sequence of grayscale morphological operations      *
854
 *-----------------------------------------------------------------*/
855
/*!
856
 * \brief   pixGrayMorphSequence()
857
 *
858
 * \param[in]    pixs
859
 * \param[in]    sequence   string specifying sequence
860
 * \param[in]    dispsep    controls debug display of results in the sequence:
861
 *                          0: no output
862
 *                          > 0: gives horizontal separation in pixels between
863
 *                               successive displays
864
 *                          < 0: pdf output; abs(dispsep) is used for naming
865
 * \param[in]    dispy      if dispsep > 0, this gives the y-value of the
866
 *                          UL corner for display; otherwise it is ignored
867
 * \return  pixd, or NULL on error
868
 *
869
 * <pre>
870
 * Notes:
871
 *      (1) This works on 8 bpp grayscale images.
872
 *      (2) This runs a pipeline of operations; no branching is allowed.
873
 *      (3) This only uses brick SELs.
874
 *      (4) A new image is always produced; the input image is not changed.
875
 *      (5) This contains an interpreter, allowing sequences to be
876
 *          generated and run.
877
 *      (6) The format of the sequence string is defined below.
878
 *      (7) In addition to morphological operations, the composite
879
 *          morph/subtract tophat can be performed.
880
 *      (8) Sel sizes (width, height) must each be odd numbers.
881
 *      (9) Intermediate results can optionally be displayed
882
 *      (10) The sequence string is formatted as follows:
883
 *            ~ An arbitrary number of operations,  each separated
884
 *              by a '+' character.  White space is ignored.
885
 *            ~ Each operation begins with a case-independent character
886
 *              specifying the operation:
887
 *                 d or D  (dilation)
888
 *                 e or E  (erosion)
889
 *                 o or O  (opening)
890
 *                 c or C  (closing)
891
 *                 t or T  (tophat)
892
 *            ~ The args to the morphological operations are bricks of hits,
893
 *              and are formatted as a.b, where a and b are horizontal and
894
 *              vertical dimensions, rsp. (each must be an odd number)
895
 *            ~ The args to the tophat are w or W (for white tophat)
896
 *              or b or B (for black tophat), followed by a.b as for
897
 *              the dilation, erosion, opening and closing.
898
 *           Example valid sequences are:
899
 *             "c5.3 + o7.5"
900
 *             "c9.9 + tw9.9"
901
 * </pre>
902
 */
903
PIX *
904
pixGrayMorphSequence(PIX         *pixs,
905
                     const char  *sequence,
906
                     l_int32      dispsep,
907
                     l_int32      dispy)
908
0
{
909
0
char    *rawop, *op;
910
0
char     fname[256];
911
0
l_int32  nops, i, valid, w, h, x, pdfout;
912
0
PIX     *pix1, *pix2;
913
0
PIXA    *pixa;
914
0
SARRAY  *sa;
915
916
0
    if (!pixs)
917
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
918
0
    if (!sequence)
919
0
        return (PIX *)ERROR_PTR("sequence not defined", __func__, NULL);
920
921
        /* Split sequence into individual operations */
922
0
    sa = sarrayCreate(0);
923
0
    sarraySplitString(sa, sequence, "+");
924
0
    nops = sarrayGetCount(sa);
925
0
    pdfout = (dispsep < 0) ? 1 : 0;
926
927
        /* Verify that the operation sequence is valid */
928
0
    valid = TRUE;
929
0
    for (i = 0; i < nops; i++) {
930
0
        rawop = sarrayGetString(sa, i, L_NOCOPY);
931
0
        op = stringRemoveChars(rawop, " \n\t");
932
0
        switch (op[0])
933
0
        {
934
0
        case 'd':
935
0
        case 'D':
936
0
        case 'e':
937
0
        case 'E':
938
0
        case 'o':
939
0
        case 'O':
940
0
        case 'c':
941
0
        case 'C':
942
0
            if (sscanf(&op[1], "%d.%d", &w, &h) != 2) {
943
0
                lept_stderr("*** op: %s invalid\n", op);
944
0
                valid = FALSE;
945
0
                break;
946
0
            }
947
0
            if (w < 1 || (w & 1) == 0 || h < 1 || (h & 1) == 0 ) {
948
0
                lept_stderr("*** op: %s; w = %d, h = %d; must both be odd\n",
949
0
                            op, w, h);
950
0
                valid = FALSE;
951
0
                break;
952
0
            }
953
/*            lept_stderr("op = %s; w = %d, h = %d\n", op, w, h); */
954
0
            break;
955
0
        case 't':
956
0
        case 'T':
957
0
            if (op[1] != 'w' && op[1] != 'W' &&
958
0
                op[1] != 'b' && op[1] != 'B') {
959
0
                lept_stderr(
960
0
                        "*** op = %s; arg %c must be 'w' or 'b'\n", op, op[1]);
961
0
                valid = FALSE;
962
0
                break;
963
0
            }
964
0
            sscanf(&op[2], "%d.%d", &w, &h);
965
0
            if (w < 1 || (w & 1) == 0 || h < 1 || (h & 1) == 0 ) {
966
0
                lept_stderr("*** op: %s; w = %d, h = %d; must both be odd\n",
967
0
                            op, w, h);
968
0
                valid = FALSE;
969
0
                break;
970
0
            }
971
/*            lept_stderr("op = %s", op); */
972
0
            break;
973
0
        default:
974
0
            lept_stderr("*** nonexistent op = %s\n", op);
975
0
            valid = FALSE;
976
0
        }
977
0
        LEPT_FREE(op);
978
0
    }
979
0
    if (!valid) {
980
0
        sarrayDestroy(&sa);
981
0
        return (PIX *)ERROR_PTR("sequence invalid", __func__, NULL);
982
0
    }
983
984
        /* Parse and operate */
985
0
    pixa = NULL;
986
0
    if (pdfout) {
987
0
        pixa = pixaCreate(0);
988
0
        pixaAddPix(pixa, pixs, L_CLONE);
989
0
    }
990
0
    pix1 = pixCopy(NULL, pixs);
991
0
    pix2 = NULL;
992
0
    x = 0;
993
0
    for (i = 0; i < nops; i++) {
994
0
        rawop = sarrayGetString(sa, i, L_NOCOPY);
995
0
        op = stringRemoveChars(rawop, " \n\t");
996
0
        switch (op[0])
997
0
        {
998
0
        case 'd':
999
0
        case 'D':
1000
0
            sscanf(&op[1], "%d.%d", &w, &h);
1001
0
            pix2 = pixDilateGray(pix1, w, h);
1002
0
            pixSwapAndDestroy(&pix1, &pix2);
1003
0
            break;
1004
0
        case 'e':
1005
0
        case 'E':
1006
0
            sscanf(&op[1], "%d.%d", &w, &h);
1007
0
            pix2 = pixErodeGray(pix1, w, h);
1008
0
            pixSwapAndDestroy(&pix1, &pix2);
1009
0
            break;
1010
0
        case 'o':
1011
0
        case 'O':
1012
0
            sscanf(&op[1], "%d.%d", &w, &h);
1013
0
            pix2 = pixOpenGray(pix1, w, h);
1014
0
            pixSwapAndDestroy(&pix1, &pix2);
1015
0
            break;
1016
0
        case 'c':
1017
0
        case 'C':
1018
0
            sscanf(&op[1], "%d.%d", &w, &h);
1019
0
            pix2 = pixCloseGray(pix1, w, h);
1020
0
            pixSwapAndDestroy(&pix1, &pix2);
1021
0
            break;
1022
0
        case 't':
1023
0
        case 'T':
1024
0
            sscanf(&op[2], "%d.%d", &w, &h);
1025
0
            if (op[1] == 'w' || op[1] == 'W')
1026
0
                pix2 = pixTophat(pix1, w, h, L_TOPHAT_WHITE);
1027
0
            else   /* 'b' or 'B' */
1028
0
                pix2 = pixTophat(pix1, w, h, L_TOPHAT_BLACK);
1029
0
            pixSwapAndDestroy(&pix1, &pix2);
1030
0
            break;
1031
0
        default:
1032
            /* All invalid ops are caught in the first pass */
1033
0
            break;
1034
0
        }
1035
0
        LEPT_FREE(op);
1036
1037
            /* Debug output */
1038
0
        if (dispsep > 0) {
1039
0
            pixDisplay(pix1, x, dispy);
1040
0
            x += dispsep;
1041
0
        }
1042
0
        if (pdfout)
1043
0
            pixaAddPix(pixa, pix1, L_COPY);
1044
0
    }
1045
1046
0
    if (pdfout) {
1047
0
        snprintf(fname, sizeof(fname), "/tmp/lept/seq_output_%d.pdf",
1048
0
                 L_ABS(dispsep));
1049
0
        pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname);
1050
0
        pixaDestroy(&pixa);
1051
0
    }
1052
1053
0
    sarrayDestroy(&sa);
1054
0
    return pix1;
1055
0
}
1056
1057
1058
/*-----------------------------------------------------------------*
1059
 *         Run a sequence of color morphological operations        *
1060
 *-----------------------------------------------------------------*/
1061
/*!
1062
 * \brief   pixColorMorphSequence()
1063
 *
1064
 * \param[in]    pixs
1065
 * \param[in]    sequence   string specifying sequence
1066
 * \param[in]    dispsep    controls debug display of results in the sequence:
1067
 *                          0: no output
1068
 *                          > 0: gives horizontal separation in pixels between
1069
 *                               successive displays
1070
 *                          < 0: pdf output; abs(dispsep) is used for naming
1071
 * \param[in]    dispy      if dispsep > 0, this gives the y-value of the
1072
 *                          UL corner for display; otherwise it is ignored
1073
 * \return  pixd, or NULL on error
1074
 *
1075
 * <pre>
1076
 * Notes:
1077
 *      (1) This works on 32 bpp rgb images.
1078
 *      (2) Each component is processed separately.
1079
 *      (3) This runs a pipeline of operations; no branching is allowed.
1080
 *      (4) This only uses brick SELs.
1081
 *      (5) A new image is always produced; the input image is not changed.
1082
 *      (6) This contains an interpreter, allowing sequences to be
1083
 *          generated and run.
1084
 *      (7) Sel sizes (width, height) must each be odd numbers.
1085
 *      (8) The format of the sequence string is defined below.
1086
 *      (9) Intermediate results can optionally be displayed.
1087
 *      (10) The sequence string is formatted as follows:
1088
 *            ~ An arbitrary number of operations,  each separated
1089
 *              by a '+' character.  White space is ignored.
1090
 *            ~ Each operation begins with a case-independent character
1091
 *              specifying the operation:
1092
 *                 d or D  (dilation)
1093
 *                 e or E  (erosion)
1094
 *                 o or O  (opening)
1095
 *                 c or C  (closing)
1096
 *            ~ The args to the morphological operations are bricks of hits,
1097
 *              and are formatted as a.b, where a and b are horizontal and
1098
 *              vertical dimensions, rsp. (each must be an odd number)
1099
 *           Example valid sequences are:
1100
 *             "c5.3 + o7.5"
1101
 *             "D9.1"
1102
 * </pre>
1103
 */
1104
PIX *
1105
pixColorMorphSequence(PIX         *pixs,
1106
                      const char  *sequence,
1107
                      l_int32      dispsep,
1108
                      l_int32      dispy)
1109
0
{
1110
0
char    *rawop, *op;
1111
0
char     fname[256];
1112
0
l_int32  nops, i, valid, w, h, x, pdfout;
1113
0
PIX     *pix1, *pix2;
1114
0
PIXA    *pixa;
1115
0
SARRAY  *sa;
1116
1117
0
    if (!pixs)
1118
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
1119
0
    if (!sequence)
1120
0
        return (PIX *)ERROR_PTR("sequence not defined", __func__, NULL);
1121
1122
        /* Split sequence into individual operations */
1123
0
    sa = sarrayCreate(0);
1124
0
    sarraySplitString(sa, sequence, "+");
1125
0
    nops = sarrayGetCount(sa);
1126
0
    pdfout = (dispsep < 0) ? 1 : 0;
1127
1128
        /* Verify that the operation sequence is valid */
1129
0
    valid = TRUE;
1130
0
    for (i = 0; i < nops; i++) {
1131
0
        rawop = sarrayGetString(sa, i, L_NOCOPY);
1132
0
        op = stringRemoveChars(rawop, " \n\t");
1133
0
        switch (op[0])
1134
0
        {
1135
0
        case 'd':
1136
0
        case 'D':
1137
0
        case 'e':
1138
0
        case 'E':
1139
0
        case 'o':
1140
0
        case 'O':
1141
0
        case 'c':
1142
0
        case 'C':
1143
0
            if (sscanf(&op[1], "%d.%d", &w, &h) != 2) {
1144
0
                lept_stderr("*** op: %s invalid\n", op);
1145
0
                valid = FALSE;
1146
0
                break;
1147
0
            }
1148
0
            if (w < 1 || (w & 1) == 0 || h < 1 || (h & 1) == 0 ) {
1149
0
                lept_stderr("*** op: %s; w = %d, h = %d; must both be odd\n",
1150
0
                            op, w, h);
1151
0
                valid = FALSE;
1152
0
                break;
1153
0
            }
1154
/*            lept_stderr("op = %s; w = %d, h = %d\n", op, w, h); */
1155
0
            break;
1156
0
        default:
1157
0
            lept_stderr("*** nonexistent op = %s\n", op);
1158
0
            valid = FALSE;
1159
0
        }
1160
0
        LEPT_FREE(op);
1161
0
    }
1162
0
    if (!valid) {
1163
0
        sarrayDestroy(&sa);
1164
0
        return (PIX *)ERROR_PTR("sequence invalid", __func__, NULL);
1165
0
    }
1166
1167
        /* Parse and operate */
1168
0
    pixa = NULL;
1169
0
    if (pdfout) {
1170
0
        pixa = pixaCreate(0);
1171
0
        pixaAddPix(pixa, pixs, L_CLONE);
1172
0
    }
1173
0
    pix1 = pixCopy(NULL, pixs);
1174
0
    pix2 = NULL;
1175
0
    x = 0;
1176
0
    for (i = 0; i < nops; i++) {
1177
0
        rawop = sarrayGetString(sa, i, L_NOCOPY);
1178
0
        op = stringRemoveChars(rawop, " \n\t");
1179
0
        switch (op[0])
1180
0
        {
1181
0
        case 'd':
1182
0
        case 'D':
1183
0
            sscanf(&op[1], "%d.%d", &w, &h);
1184
0
            pix2 = pixColorMorph(pix1, L_MORPH_DILATE, w, h);
1185
0
            pixSwapAndDestroy(&pix1, &pix2);
1186
0
            break;
1187
0
        case 'e':
1188
0
        case 'E':
1189
0
            sscanf(&op[1], "%d.%d", &w, &h);
1190
0
            pix2 = pixColorMorph(pix1, L_MORPH_ERODE, w, h);
1191
0
            pixSwapAndDestroy(&pix1, &pix2);
1192
0
            break;
1193
0
        case 'o':
1194
0
        case 'O':
1195
0
            sscanf(&op[1], "%d.%d", &w, &h);
1196
0
            pix2 = pixColorMorph(pix1, L_MORPH_OPEN, w, h);
1197
0
            pixSwapAndDestroy(&pix1, &pix2);
1198
0
            break;
1199
0
        case 'c':
1200
0
        case 'C':
1201
0
            sscanf(&op[1], "%d.%d", &w, &h);
1202
0
            pix2 = pixColorMorph(pix1, L_MORPH_CLOSE, w, h);
1203
0
            pixSwapAndDestroy(&pix1, &pix2);
1204
0
            break;
1205
0
        default:
1206
            /* All invalid ops are caught in the first pass */
1207
0
            break;
1208
0
        }
1209
0
        LEPT_FREE(op);
1210
1211
            /* Debug output */
1212
0
        if (dispsep > 0) {
1213
0
            pixDisplay(pix1, x, dispy);
1214
0
            x += dispsep;
1215
0
        }
1216
0
        if (pdfout)
1217
0
            pixaAddPix(pixa, pix1, L_COPY);
1218
0
    }
1219
1220
0
    if (pdfout) {
1221
0
        snprintf(fname, sizeof(fname), "/tmp/lept/seq_output_%d.pdf",
1222
0
                 L_ABS(dispsep));
1223
0
        pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname);
1224
0
        pixaDestroy(&pixa);
1225
0
    }
1226
1227
0
    sarrayDestroy(&sa);
1228
0
    return pix1;
1229
0
}