/*====================================================================* - Copyright (C) 2001 Leptonica. All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials - provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *====================================================================*/ /*! * \file morphseq.c *
 *
 *      Run a sequence of binary rasterop morphological operations
 *            PIX     *pixMorphSequence()
 *
 *      Run a sequence of binary composite rasterop morphological operations
 *            PIX     *pixMorphCompSequence()
 *
 *      Run a sequence of binary dwa morphological operations
 *            PIX     *pixMorphSequenceDwa()
 *
 *      Run a sequence of binary composite dwa morphological operations
 *            PIX     *pixMorphCompSequenceDwa()
 *
 *      Parser verifier for binary morphological operations
 *            l_int32  morphSequenceVerify()
 *
 *      Run a sequence of grayscale morphological operations
 *            PIX     *pixGrayMorphSequence()
 *
 *      Run a sequence of color morphological operations
 *            PIX     *pixColorMorphSequence()
 * 
*/ #ifdef HAVE_CONFIG_H #include #endif /* HAVE_CONFIG_H */ #include #include "allheaders.h" /*-------------------------------------------------------------------------* * Run a sequence of binary rasterop morphological operations * *-------------------------------------------------------------------------*/ /*! * \brief pixMorphSequence() * * \param[in] pixs * \param[in] sequence string specifying sequence * \param[in] dispsep controls debug display results in the sequence: * 0: no output * > 0: gives horizontal separation in pixels between * successive displays * < 0: pdf output; abs(dispsep) is used for naming * \return pixd, or NULL on error * *
 * Notes:
 *      (1) This does rasterop morphology on binary images.
 *      (2) This runs a pipeline of operations; no branching is allowed.
 *      (3) This only uses brick Sels, which are created on the fly.
 *          In the future this will be generalized to extract Sels from
 *          a Sela by name.
 *      (4) A new image is always produced; the input image is not changed.
 *      (5) This contains an interpreter, allowing sequences to be
 *          generated and run.
 *      (6) The format of the sequence string is defined below.
 *      (7) In addition to morphological operations, rank order reduction
 *          and replicated expansion allow operations to take place
 *          downscaled by a power of 2.
 *      (8) Intermediate results can optionally be displayed.
 *      (9) Thanks to Dar-Shyang Lee, who had the idea for this and
 *          built the first implementation.
 *      (10) The sequence string is formatted as follows:
 *            ~ An arbitrary number of operations,  each separated
 *              by a '+' character.  White space is ignored.
 *            ~ Each operation begins with a case-independent character
 *              specifying the operation:
 *                 d or D  (dilation)
 *                 e or E  (erosion)
 *                 o or O  (opening)
 *                 c or C  (closing)
 *                 r or R  (rank binary reduction)
 *                 x or X  (replicative binary expansion)
 *                 b or B  (add a border of 0 pixels of this size)
 *            ~ The args to the morphological operations are bricks of hits,
 *              and are formatted as a.b, where a and b are horizontal and
 *              vertical dimensions, rsp.
 *            ~ The args to the reduction are a sequence of up to 4 integers,
 *              each from 1 to 4.
 *            ~ The arg to the expansion is a power of two, in the set
 *              {2, 4, 8, 16}.
 *      (11) An example valid sequence is:
 *               "b32 + o1.3 + C3.1 + r23 + e2.2 + D3.2 + X4"
 *           In this example, the following operation sequence is carried out:
 *             * b32: Add a 32 pixel border around the input image
 *             * o1.3: Opening with vert sel of length 3 (e.g., 1 x 3)
 *             * C3.1: Closing with horiz sel of length 3  (e.g., 3 x 1)
 *             * r23: Two successive 2x2 reductions with rank 2 in the first
 *                    and rank 3 in the second.  The result is a 4x reduced pix.
 *             * e2.2: Erosion with a 2x2 sel (origin will be at x,y: 0,0)
 *             * d3.2: Dilation with a 3x2 sel (origin will be at x,y: 1,0)
 *             * X4: 4x replicative expansion, back to original resolution
 *      (12) The safe closing is used.  However, if you implement a
 *           closing as separable dilations followed by separable erosions,
 *           it will not be safe.  For that situation, you need to add
 *           a sufficiently large border as the first operation in
 *           the sequence.  This will be removed automatically at the
 *           end.  There are two cautions:
 *              ~ When computing what is sufficient, remember that if
 *                reductions are carried out, the border is also reduced.
 *              ~ The border is removed at the end, so if a border is
 *                added at the beginning, the result must be at the
 *                same resolution as the input!
 * 
*/ PIX * pixMorphSequence(PIX *pixs, const char *sequence, l_int32 dispsep) { char *rawop, *op; char fname[256]; l_int32 nops, i, j, nred, fact, w, h, x, border, pdfout; l_int32 level[4]; PIX *pix1, *pix2; PIXA *pixa; SARRAY *sa; PROCNAME("pixMorphSequence"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (!sequence) return (PIX *)ERROR_PTR("sequence not defined", procName, NULL); /* Split sequence into individual operations */ sa = sarrayCreate(0); sarraySplitString(sa, sequence, "+"); nops = sarrayGetCount(sa); pdfout = (dispsep < 0) ? 1 : 0; if (!morphSequenceVerify(sa)) { sarrayDestroy(&sa); return (PIX *)ERROR_PTR("sequence not valid", procName, NULL); } /* Parse and operate */ pixa = NULL; if (pdfout) { pixa = pixaCreate(0); pixaAddPix(pixa, pixs, L_CLONE); } border = 0; pix1 = pixCopy(NULL, pixs); pix2 = NULL; x = 0; for (i = 0; i < nops; i++) { rawop = sarrayGetString(sa, i, L_NOCOPY); op = stringRemoveChars(rawop, " \n\t"); switch (op[0]) { case 'd': case 'D': sscanf(&op[1], "%d.%d", &w, &h); pix2 = pixDilateBrick(NULL, pix1, w, h); pixSwapAndDestroy(&pix1, &pix2); break; case 'e': case 'E': sscanf(&op[1], "%d.%d", &w, &h); pix2 = pixErodeBrick(NULL, pix1, w, h); pixSwapAndDestroy(&pix1, &pix2); break; case 'o': case 'O': sscanf(&op[1], "%d.%d", &w, &h); pixOpenBrick(pix1, pix1, w, h); break; case 'c': case 'C': sscanf(&op[1], "%d.%d", &w, &h); pixCloseSafeBrick(pix1, pix1, w, h); break; case 'r': case 'R': nred = strlen(op) - 1; for (j = 0; j < nred; j++) level[j] = op[j + 1] - '0'; for (j = nred; j < 4; j++) level[j] = 0; pix2 = pixReduceRankBinaryCascade(pix1, level[0], level[1], level[2], level[3]); pixSwapAndDestroy(&pix1, &pix2); break; case 'x': case 'X': sscanf(&op[1], "%d", &fact); pix2 = pixExpandReplicate(pix1, fact); pixSwapAndDestroy(&pix1, &pix2); break; case 'b': case 'B': sscanf(&op[1], "%d", &border); pix2 = pixAddBorder(pix1, border, 0); pixSwapAndDestroy(&pix1, &pix2); break; default: /* All invalid ops are caught in the first pass */ break; } LEPT_FREE(op); /* Debug output */ if (dispsep > 0) { pixDisplay(pix1, x, 0); x += dispsep; } if (pdfout) pixaAddPix(pixa, pix1, L_COPY); } if (border > 0) { pix2 = pixRemoveBorder(pix1, border); pixSwapAndDestroy(&pix1, &pix2); } if (pdfout) { snprintf(fname, sizeof(fname), "/tmp/lept/seq_output_%d.pdf", L_ABS(dispsep)); pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname); pixaDestroy(&pixa); } sarrayDestroy(&sa); return pix1; } /*-------------------------------------------------------------------------* * Run a sequence of binary composite rasterop morphological operations * *-------------------------------------------------------------------------*/ /*! * \brief pixMorphCompSequence() * * \param[in] pixs * \param[in] sequence string specifying sequence * \param[in] dispsep controls debug display of results in the sequence: * 0: no output * > 0: gives horizontal separation in pixels between * successive displays * < 0: pdf output; abs(dispsep) is used for naming * \return pixd, or NULL on error * *
 * Notes:
 *      (1) This does rasterop morphology on binary images, using composite
 *          operations for extra speed on large Sels.
 *      (2) Safe closing is used atomically.  However, if you implement a
 *          closing as a sequence with a dilation followed by an
 *          erosion, it will not be safe, and to ensure that you have
 *          no boundary effects you must add a border in advance and
 *          remove it at the end.
 *      (3) For other usage details, see the notes for pixMorphSequence().
 *      (4) The sequence string is formatted as follows:
 *            ~ An arbitrary number of operations,  each separated
 *              by a '+' character.  White space is ignored.
 *            ~ Each operation begins with a case-independent character
 *              specifying the operation:
 *                 d or D  (dilation)
 *                 e or E  (erosion)
 *                 o or O  (opening)
 *                 c or C  (closing)
 *                 r or R  (rank binary reduction)
 *                 x or X  (replicative binary expansion)
 *                 b or B  (add a border of 0 pixels of this size)
 *            ~ The args to the morphological operations are bricks of hits,
 *              and are formatted as a.b, where a and b are horizontal and
 *              vertical dimensions, rsp.
 *            ~ The args to the reduction are a sequence of up to 4 integers,
 *              each from 1 to 4.
 *            ~ The arg to the expansion is a power of two, in the set
 *              {2, 4, 8, 16}.
 * 
*/ PIX * pixMorphCompSequence(PIX *pixs, const char *sequence, l_int32 dispsep) { char *rawop, *op; char fname[256]; l_int32 nops, i, j, nred, fact, w, h, x, border, pdfout; l_int32 level[4]; PIX *pix1, *pix2; PIXA *pixa; SARRAY *sa; PROCNAME("pixMorphCompSequence"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (!sequence) return (PIX *)ERROR_PTR("sequence not defined", procName, NULL); /* Split sequence into individual operations */ sa = sarrayCreate(0); sarraySplitString(sa, sequence, "+"); nops = sarrayGetCount(sa); pdfout = (dispsep < 0) ? 1 : 0; if (!morphSequenceVerify(sa)) { sarrayDestroy(&sa); return (PIX *)ERROR_PTR("sequence not valid", procName, NULL); } /* Parse and operate */ pixa = NULL; if (pdfout) { pixa = pixaCreate(0); pixaAddPix(pixa, pixs, L_CLONE); } border = 0; pix1 = pixCopy(NULL, pixs); pix2 = NULL; x = 0; for (i = 0; i < nops; i++) { rawop = sarrayGetString(sa, i, L_NOCOPY); op = stringRemoveChars(rawop, " \n\t"); switch (op[0]) { case 'd': case 'D': sscanf(&op[1], "%d.%d", &w, &h); pix2 = pixDilateCompBrick(NULL, pix1, w, h); pixSwapAndDestroy(&pix1, &pix2); break; case 'e': case 'E': sscanf(&op[1], "%d.%d", &w, &h); pix2 = pixErodeCompBrick(NULL, pix1, w, h); pixSwapAndDestroy(&pix1, &pix2); break; case 'o': case 'O': sscanf(&op[1], "%d.%d", &w, &h); pixOpenCompBrick(pix1, pix1, w, h); break; case 'c': case 'C': sscanf(&op[1], "%d.%d", &w, &h); pixCloseSafeCompBrick(pix1, pix1, w, h); break; case 'r': case 'R': nred = strlen(op) - 1; for (j = 0; j < nred; j++) level[j] = op[j + 1] - '0'; for (j = nred; j < 4; j++) level[j] = 0; pix2 = pixReduceRankBinaryCascade(pix1, level[0], level[1], level[2], level[3]); pixSwapAndDestroy(&pix1, &pix2); break; case 'x': case 'X': sscanf(&op[1], "%d", &fact); pix2 = pixExpandReplicate(pix1, fact); pixSwapAndDestroy(&pix1, &pix2); break; case 'b': case 'B': sscanf(&op[1], "%d", &border); pix2 = pixAddBorder(pix1, border, 0); pixSwapAndDestroy(&pix1, &pix2); break; default: /* All invalid ops are caught in the first pass */ break; } LEPT_FREE(op); /* Debug output */ if (dispsep > 0) { pixDisplay(pix1, x, 0); x += dispsep; } if (pdfout) pixaAddPix(pixa, pix1, L_COPY); } if (border > 0) { pix2 = pixRemoveBorder(pix1, border); pixSwapAndDestroy(&pix1, &pix2); } if (pdfout) { snprintf(fname, sizeof(fname), "/tmp/lept/seq_output_%d.pdf", L_ABS(dispsep)); pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname); pixaDestroy(&pixa); } sarrayDestroy(&sa); return pix1; } /*-------------------------------------------------------------------------* * Run a sequence of binary dwa morphological operations * *-------------------------------------------------------------------------*/ /*! * \brief pixMorphSequenceDwa() * * \param[in] pixs * \param[in] sequence string specifying sequence * \param[in] dispsep controls debug display of results in the sequence: * 0: no output * > 0: gives horizontal separation in pixels between * successive displays * < 0: pdf output; abs(dispsep) is used for naming * \return pixd, or NULL on error * *
 * Notes:
 *      (1) This does dwa morphology on binary images.
 *      (2) This runs a pipeline of operations; no branching is allowed.
 *      (3) This only uses brick Sels that have been pre-compiled with
 *          dwa code.
 *      (4) A new image is always produced; the input image is not changed.
 *      (5) This contains an interpreter, allowing sequences to be
 *          generated and run.
 *      (6) See pixMorphSequence() for further information about usage.
 * 
*/ PIX * pixMorphSequenceDwa(PIX *pixs, const char *sequence, l_int32 dispsep) { char *rawop, *op; char fname[256]; l_int32 nops, i, j, nred, fact, w, h, x, border, pdfout; l_int32 level[4]; PIX *pix1, *pix2; PIXA *pixa; SARRAY *sa; PROCNAME("pixMorphSequenceDwa"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (!sequence) return (PIX *)ERROR_PTR("sequence not defined", procName, NULL); /* Split sequence into individual operations */ sa = sarrayCreate(0); sarraySplitString(sa, sequence, "+"); nops = sarrayGetCount(sa); pdfout = (dispsep < 0) ? 1 : 0; if (!morphSequenceVerify(sa)) { sarrayDestroy(&sa); return (PIX *)ERROR_PTR("sequence not valid", procName, NULL); } /* Parse and operate */ pixa = NULL; if (pdfout) { pixa = pixaCreate(0); pixaAddPix(pixa, pixs, L_CLONE); } border = 0; pix1 = pixCopy(NULL, pixs); pix2 = NULL; x = 0; for (i = 0; i < nops; i++) { rawop = sarrayGetString(sa, i, L_NOCOPY); op = stringRemoveChars(rawop, " \n\t"); switch (op[0]) { case 'd': case 'D': sscanf(&op[1], "%d.%d", &w, &h); pix2 = pixDilateBrickDwa(NULL, pix1, w, h); pixSwapAndDestroy(&pix1, &pix2); break; case 'e': case 'E': sscanf(&op[1], "%d.%d", &w, &h); pix2 = pixErodeBrickDwa(NULL, pix1, w, h); pixSwapAndDestroy(&pix1, &pix2); break; case 'o': case 'O': sscanf(&op[1], "%d.%d", &w, &h); pixOpenBrickDwa(pix1, pix1, w, h); break; case 'c': case 'C': sscanf(&op[1], "%d.%d", &w, &h); pixCloseBrickDwa(pix1, pix1, w, h); break; case 'r': case 'R': nred = strlen(op) - 1; for (j = 0; j < nred; j++) level[j] = op[j + 1] - '0'; for (j = nred; j < 4; j++) level[j] = 0; pix2 = pixReduceRankBinaryCascade(pix1, level[0], level[1], level[2], level[3]); pixSwapAndDestroy(&pix1, &pix2); break; case 'x': case 'X': sscanf(&op[1], "%d", &fact); pix2 = pixExpandReplicate(pix1, fact); pixSwapAndDestroy(&pix1, &pix2); break; case 'b': case 'B': sscanf(&op[1], "%d", &border); pix2 = pixAddBorder(pix1, border, 0); pixSwapAndDestroy(&pix1, &pix2); break; default: /* All invalid ops are caught in the first pass */ break; } LEPT_FREE(op); /* Debug output */ if (dispsep > 0) { pixDisplay(pix1, x, 0); x += dispsep; } if (pdfout) pixaAddPix(pixa, pix1, L_COPY); } if (border > 0) { pix2 = pixRemoveBorder(pix1, border); pixSwapAndDestroy(&pix1, &pix2); } if (pdfout) { snprintf(fname, sizeof(fname), "/tmp/lept/seq_output_%d.pdf", L_ABS(dispsep)); pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname); pixaDestroy(&pixa); } sarrayDestroy(&sa); return pix1; } /*-------------------------------------------------------------------------* * Run a sequence of binary composite dwa morphological operations * *-------------------------------------------------------------------------*/ /*! * \brief pixMorphCompSequenceDwa() * * \param[in] pixs * \param[in] sequence string specifying sequence * \param[in] dispsep controls debug display of results in the sequence: * 0: no output * > 0: gives horizontal separation in pixels between * successive displays * < 0: pdf output; abs(dispsep) is used for naming * \return pixd, or NULL on error * *
 * Notes:
 *      (1) This does dwa morphology on binary images, using brick Sels.
 *      (2) This runs a pipeline of operations; no branching is allowed.
 *      (3) It implements all brick Sels that have dimensions up to 63
 *          on each side, using a composite (linear + comb) when useful.
 *      (4) A new image is always produced; the input image is not changed.
 *      (5) This contains an interpreter, allowing sequences to be
 *          generated and run.
 *      (6) See pixMorphSequence() for further information about usage.
 * 
*/ PIX * pixMorphCompSequenceDwa(PIX *pixs, const char *sequence, l_int32 dispsep) { char *rawop, *op; char fname[256]; l_int32 nops, i, j, nred, fact, w, h, x, border, pdfout; l_int32 level[4]; PIX *pix1, *pix2; PIXA *pixa; SARRAY *sa; PROCNAME("pixMorphCompSequenceDwa"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (!sequence) return (PIX *)ERROR_PTR("sequence not defined", procName, NULL); /* Split sequence into individual operations */ sa = sarrayCreate(0); sarraySplitString(sa, sequence, "+"); nops = sarrayGetCount(sa); pdfout = (dispsep < 0) ? 1 : 0; if (!morphSequenceVerify(sa)) { sarrayDestroy(&sa); return (PIX *)ERROR_PTR("sequence not valid", procName, NULL); } /* Parse and operate */ pixa = NULL; if (pdfout) { pixa = pixaCreate(0); pixaAddPix(pixa, pixs, L_CLONE); } border = 0; pix1 = pixCopy(NULL, pixs); pix2 = NULL; x = 0; for (i = 0; i < nops; i++) { rawop = sarrayGetString(sa, i, L_NOCOPY); op = stringRemoveChars(rawop, " \n\t"); switch (op[0]) { case 'd': case 'D': sscanf(&op[1], "%d.%d", &w, &h); pix2 = pixDilateCompBrickDwa(NULL, pix1, w, h); pixSwapAndDestroy(&pix1, &pix2); break; case 'e': case 'E': sscanf(&op[1], "%d.%d", &w, &h); pix2 = pixErodeCompBrickDwa(NULL, pix1, w, h); pixSwapAndDestroy(&pix1, &pix2); break; case 'o': case 'O': sscanf(&op[1], "%d.%d", &w, &h); pixOpenCompBrickDwa(pix1, pix1, w, h); break; case 'c': case 'C': sscanf(&op[1], "%d.%d", &w, &h); pixCloseCompBrickDwa(pix1, pix1, w, h); break; case 'r': case 'R': nred = strlen(op) - 1; for (j = 0; j < nred; j++) level[j] = op[j + 1] - '0'; for (j = nred; j < 4; j++) level[j] = 0; pix2 = pixReduceRankBinaryCascade(pix1, level[0], level[1], level[2], level[3]); pixSwapAndDestroy(&pix1, &pix2); break; case 'x': case 'X': sscanf(&op[1], "%d", &fact); pix2 = pixExpandReplicate(pix1, fact); pixSwapAndDestroy(&pix1, &pix2); break; case 'b': case 'B': sscanf(&op[1], "%d", &border); pix2 = pixAddBorder(pix1, border, 0); pixSwapAndDestroy(&pix1, &pix2); break; default: /* All invalid ops are caught in the first pass */ break; } LEPT_FREE(op); /* Debug output */ if (dispsep > 0) { pixDisplay(pix1, x, 0); x += dispsep; } if (pdfout) pixaAddPix(pixa, pix1, L_COPY); } if (border > 0) { pix2 = pixRemoveBorder(pix1, border); pixSwapAndDestroy(&pix1, &pix2); } if (pdfout) { snprintf(fname, sizeof(fname), "/tmp/lept/seq_output_%d.pdf", L_ABS(dispsep)); pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname); pixaDestroy(&pixa); } sarrayDestroy(&sa); return pix1; } /*-------------------------------------------------------------------------* * Parser verifier for binary morphological operations * *-------------------------------------------------------------------------*/ /*! * \brief morphSequenceVerify() * * \param[in] sa string array of operation sequence * \return TRUE if valid; FALSE otherwise or on error * *
 * Notes:
 *      (1) This does verification of valid binary morphological
 *          operation sequences.
 *      (2) See pixMorphSequence() for notes on valid operations
 *          in the sequence.
 * 
*/ l_int32 morphSequenceVerify(SARRAY *sa) { char *rawop, *op; l_int32 nops, i, j, nred, fact, valid, w, h, netred, border; l_int32 level[4]; l_int32 intlogbase2[5] = {1, 2, 3, 0, 4}; /* of arg/4 */ PROCNAME("morphSequenceVerify"); if (!sa) return ERROR_INT("sa not defined", procName, FALSE); nops = sarrayGetCount(sa); valid = TRUE; netred = 0; border = 0; for (i = 0; i < nops; i++) { rawop = sarrayGetString(sa, i, L_NOCOPY); op = stringRemoveChars(rawop, " \n\t"); switch (op[0]) { case 'd': case 'D': case 'e': case 'E': case 'o': case 'O': case 'c': case 'C': if (sscanf(&op[1], "%d.%d", &w, &h) != 2) { lept_stderr("*** op: %s invalid\n", op); valid = FALSE; break; } if (w <= 0 || h <= 0) { lept_stderr("*** op: %s; w = %d, h = %d; must both be > 0\n", op, w, h); valid = FALSE; break; } /* lept_stderr("op = %s; w = %d, h = %d\n", op, w, h); */ break; case 'r': case 'R': nred = strlen(op) - 1; netred += nred; if (nred < 1 || nred > 4) { lept_stderr( "*** op = %s; num reduct = %d; must be in {1,2,3,4}\n", op, nred); valid = FALSE; break; } for (j = 0; j < nred; j++) { level[j] = op[j + 1] - '0'; if (level[j] < 1 || level[j] > 4) { lept_stderr("*** op = %s; level[%d] = %d is invalid\n", op, j, level[j]); valid = FALSE; break; } } if (!valid) break; /* lept_stderr("op = %s", op); */ for (j = 0; j < nred; j++) { level[j] = op[j + 1] - '0'; /* lept_stderr(", level[%d] = %d", j, level[j]); */ } /* lept_stderr("\n"); */ break; case 'x': case 'X': if (sscanf(&op[1], "%d", &fact) != 1) { lept_stderr("*** op: %s; fact invalid\n", op); valid = FALSE; break; } if (fact != 2 && fact != 4 && fact != 8 && fact != 16) { lept_stderr("*** op = %s; invalid fact = %d\n", op, fact); valid = FALSE; break; } netred -= intlogbase2[fact / 4]; /* lept_stderr("op = %s; fact = %d\n", op, fact); */ break; case 'b': case 'B': if (sscanf(&op[1], "%d", &fact) != 1) { lept_stderr("*** op: %s; fact invalid\n", op); valid = FALSE; break; } if (i > 0) { lept_stderr("*** op = %s; must be first op\n", op); valid = FALSE; break; } if (fact < 1) { lept_stderr("*** op = %s; invalid fact = %d\n", op, fact); valid = FALSE; break; } border = fact; /* lept_stderr("op = %s; fact = %d\n", op, fact); */ break; default: lept_stderr("*** nonexistent op = %s\n", op); valid = FALSE; } LEPT_FREE(op); } if (border != 0 && netred != 0) { lept_stderr("*** op = %s; border added but net reduction not 0\n", op); valid = FALSE; } return valid; } /*-----------------------------------------------------------------* * Run a sequence of grayscale morphological operations * *-----------------------------------------------------------------*/ /*! * \brief pixGrayMorphSequence() * * \param[in] pixs * \param[in] sequence string specifying sequence * \param[in] dispsep controls debug display of results in the sequence: * 0: no output * > 0: gives horizontal separation in pixels between * successive displays * < 0: pdf output; abs(dispsep) is used for naming * \param[in] dispy if dispsep > 0, this gives the y-value of the * UL corner for display; otherwise it is ignored * \return pixd, or NULL on error * *
 * Notes:
 *      (1) This works on 8 bpp grayscale images.
 *      (2) This runs a pipeline of operations; no branching is allowed.
 *      (3) This only uses brick SELs.
 *      (4) A new image is always produced; the input image is not changed.
 *      (5) This contains an interpreter, allowing sequences to be
 *          generated and run.
 *      (6) The format of the sequence string is defined below.
 *      (7) In addition to morphological operations, the composite
 *          morph/subtract tophat can be performed.
 *      (8) Sel sizes (width, height) must each be odd numbers.
 *      (9) Intermediate results can optionally be displayed
 *      (10) The sequence string is formatted as follows:
 *            ~ An arbitrary number of operations,  each separated
 *              by a '+' character.  White space is ignored.
 *            ~ Each operation begins with a case-independent character
 *              specifying the operation:
 *                 d or D  (dilation)
 *                 e or E  (erosion)
 *                 o or O  (opening)
 *                 c or C  (closing)
 *                 t or T  (tophat)
 *            ~ The args to the morphological operations are bricks of hits,
 *              and are formatted as a.b, where a and b are horizontal and
 *              vertical dimensions, rsp. (each must be an odd number)
 *            ~ The args to the tophat are w or W (for white tophat)
 *              or b or B (for black tophat), followed by a.b as for
 *              the dilation, erosion, opening and closing.
 *           Example valid sequences are:
 *             "c5.3 + o7.5"
 *             "c9.9 + tw9.9"
 * 
*/ PIX * pixGrayMorphSequence(PIX *pixs, const char *sequence, l_int32 dispsep, l_int32 dispy) { char *rawop, *op; char fname[256]; l_int32 nops, i, valid, w, h, x, pdfout; PIX *pix1, *pix2; PIXA *pixa; SARRAY *sa; PROCNAME("pixGrayMorphSequence"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (!sequence) return (PIX *)ERROR_PTR("sequence not defined", procName, NULL); /* Split sequence into individual operations */ sa = sarrayCreate(0); sarraySplitString(sa, sequence, "+"); nops = sarrayGetCount(sa); pdfout = (dispsep < 0) ? 1 : 0; /* Verify that the operation sequence is valid */ valid = TRUE; for (i = 0; i < nops; i++) { rawop = sarrayGetString(sa, i, L_NOCOPY); op = stringRemoveChars(rawop, " \n\t"); switch (op[0]) { case 'd': case 'D': case 'e': case 'E': case 'o': case 'O': case 'c': case 'C': if (sscanf(&op[1], "%d.%d", &w, &h) != 2) { lept_stderr("*** op: %s invalid\n", op); valid = FALSE; break; } if (w < 1 || (w & 1) == 0 || h < 1 || (h & 1) == 0 ) { lept_stderr("*** op: %s; w = %d, h = %d; must both be odd\n", op, w, h); valid = FALSE; break; } /* lept_stderr("op = %s; w = %d, h = %d\n", op, w, h); */ break; case 't': case 'T': if (op[1] != 'w' && op[1] != 'W' && op[1] != 'b' && op[1] != 'B') { lept_stderr( "*** op = %s; arg %c must be 'w' or 'b'\n", op, op[1]); valid = FALSE; break; } sscanf(&op[2], "%d.%d", &w, &h); if (w < 1 || (w & 1) == 0 || h < 1 || (h & 1) == 0 ) { lept_stderr("*** op: %s; w = %d, h = %d; must both be odd\n", op, w, h); valid = FALSE; break; } /* lept_stderr("op = %s", op); */ break; default: lept_stderr("*** nonexistent op = %s\n", op); valid = FALSE; } LEPT_FREE(op); } if (!valid) { sarrayDestroy(&sa); return (PIX *)ERROR_PTR("sequence invalid", procName, NULL); } /* Parse and operate */ pixa = NULL; if (pdfout) { pixa = pixaCreate(0); pixaAddPix(pixa, pixs, L_CLONE); } pix1 = pixCopy(NULL, pixs); pix2 = NULL; x = 0; for (i = 0; i < nops; i++) { rawop = sarrayGetString(sa, i, L_NOCOPY); op = stringRemoveChars(rawop, " \n\t"); switch (op[0]) { case 'd': case 'D': sscanf(&op[1], "%d.%d", &w, &h); pix2 = pixDilateGray(pix1, w, h); pixSwapAndDestroy(&pix1, &pix2); break; case 'e': case 'E': sscanf(&op[1], "%d.%d", &w, &h); pix2 = pixErodeGray(pix1, w, h); pixSwapAndDestroy(&pix1, &pix2); break; case 'o': case 'O': sscanf(&op[1], "%d.%d", &w, &h); pix2 = pixOpenGray(pix1, w, h); pixSwapAndDestroy(&pix1, &pix2); break; case 'c': case 'C': sscanf(&op[1], "%d.%d", &w, &h); pix2 = pixCloseGray(pix1, w, h); pixSwapAndDestroy(&pix1, &pix2); break; case 't': case 'T': sscanf(&op[2], "%d.%d", &w, &h); if (op[1] == 'w' || op[1] == 'W') pix2 = pixTophat(pix1, w, h, L_TOPHAT_WHITE); else /* 'b' or 'B' */ pix2 = pixTophat(pix1, w, h, L_TOPHAT_BLACK); pixSwapAndDestroy(&pix1, &pix2); break; default: /* All invalid ops are caught in the first pass */ break; } LEPT_FREE(op); /* Debug output */ if (dispsep > 0) { pixDisplay(pix1, x, dispy); x += dispsep; } if (pdfout) pixaAddPix(pixa, pix1, L_COPY); } if (pdfout) { snprintf(fname, sizeof(fname), "/tmp/lept/seq_output_%d.pdf", L_ABS(dispsep)); pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname); pixaDestroy(&pixa); } sarrayDestroy(&sa); return pix1; } /*-----------------------------------------------------------------* * Run a sequence of color morphological operations * *-----------------------------------------------------------------*/ /*! * \brief pixColorMorphSequence() * * \param[in] pixs * \param[in] sequence string specifying sequence * \param[in] dispsep controls debug display of results in the sequence: * 0: no output * > 0: gives horizontal separation in pixels between * successive displays * < 0: pdf output; abs(dispsep) is used for naming * \param[in] dispy if dispsep > 0, this gives the y-value of the * UL corner for display; otherwise it is ignored * \return pixd, or NULL on error * *
 * Notes:
 *      (1) This works on 32 bpp rgb images.
 *      (2) Each component is processed separately.
 *      (3) This runs a pipeline of operations; no branching is allowed.
 *      (4) This only uses brick SELs.
 *      (5) A new image is always produced; the input image is not changed.
 *      (6) This contains an interpreter, allowing sequences to be
 *          generated and run.
 *      (7) Sel sizes (width, height) must each be odd numbers.
 *      (8) The format of the sequence string is defined below.
 *      (9) Intermediate results can optionally be displayed.
 *      (10) The sequence string is formatted as follows:
 *            ~ An arbitrary number of operations,  each separated
 *              by a '+' character.  White space is ignored.
 *            ~ Each operation begins with a case-independent character
 *              specifying the operation:
 *                 d or D  (dilation)
 *                 e or E  (erosion)
 *                 o or O  (opening)
 *                 c or C  (closing)
 *            ~ The args to the morphological operations are bricks of hits,
 *              and are formatted as a.b, where a and b are horizontal and
 *              vertical dimensions, rsp. (each must be an odd number)
 *           Example valid sequences are:
 *             "c5.3 + o7.5"
 *             "D9.1"
 * 
*/ PIX * pixColorMorphSequence(PIX *pixs, const char *sequence, l_int32 dispsep, l_int32 dispy) { char *rawop, *op; char fname[256]; l_int32 nops, i, valid, w, h, x, pdfout; PIX *pix1, *pix2; PIXA *pixa; SARRAY *sa; PROCNAME("pixColorMorphSequence"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (!sequence) return (PIX *)ERROR_PTR("sequence not defined", procName, NULL); /* Split sequence into individual operations */ sa = sarrayCreate(0); sarraySplitString(sa, sequence, "+"); nops = sarrayGetCount(sa); pdfout = (dispsep < 0) ? 1 : 0; /* Verify that the operation sequence is valid */ valid = TRUE; for (i = 0; i < nops; i++) { rawop = sarrayGetString(sa, i, L_NOCOPY); op = stringRemoveChars(rawop, " \n\t"); switch (op[0]) { case 'd': case 'D': case 'e': case 'E': case 'o': case 'O': case 'c': case 'C': if (sscanf(&op[1], "%d.%d", &w, &h) != 2) { lept_stderr("*** op: %s invalid\n", op); valid = FALSE; break; } if (w < 1 || (w & 1) == 0 || h < 1 || (h & 1) == 0 ) { lept_stderr("*** op: %s; w = %d, h = %d; must both be odd\n", op, w, h); valid = FALSE; break; } /* lept_stderr("op = %s; w = %d, h = %d\n", op, w, h); */ break; default: lept_stderr("*** nonexistent op = %s\n", op); valid = FALSE; } LEPT_FREE(op); } if (!valid) { sarrayDestroy(&sa); return (PIX *)ERROR_PTR("sequence invalid", procName, NULL); } /* Parse and operate */ pixa = NULL; if (pdfout) { pixa = pixaCreate(0); pixaAddPix(pixa, pixs, L_CLONE); } pix1 = pixCopy(NULL, pixs); pix2 = NULL; x = 0; for (i = 0; i < nops; i++) { rawop = sarrayGetString(sa, i, L_NOCOPY); op = stringRemoveChars(rawop, " \n\t"); switch (op[0]) { case 'd': case 'D': sscanf(&op[1], "%d.%d", &w, &h); pix2 = pixColorMorph(pix1, L_MORPH_DILATE, w, h); pixSwapAndDestroy(&pix1, &pix2); break; case 'e': case 'E': sscanf(&op[1], "%d.%d", &w, &h); pix2 = pixColorMorph(pix1, L_MORPH_ERODE, w, h); pixSwapAndDestroy(&pix1, &pix2); break; case 'o': case 'O': sscanf(&op[1], "%d.%d", &w, &h); pix2 = pixColorMorph(pix1, L_MORPH_OPEN, w, h); pixSwapAndDestroy(&pix1, &pix2); break; case 'c': case 'C': sscanf(&op[1], "%d.%d", &w, &h); pix2 = pixColorMorph(pix1, L_MORPH_CLOSE, w, h); pixSwapAndDestroy(&pix1, &pix2); break; default: /* All invalid ops are caught in the first pass */ break; } LEPT_FREE(op); /* Debug output */ if (dispsep > 0) { pixDisplay(pix1, x, dispy); x += dispsep; } if (pdfout) pixaAddPix(pixa, pix1, L_COPY); } if (pdfout) { snprintf(fname, sizeof(fname), "/tmp/lept/seq_output_%d.pdf", L_ABS(dispsep)); pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname); pixaDestroy(&pixa); } sarrayDestroy(&sa); return pix1; }