summaryrefslogtreecommitdiff
path: root/src/morphseq.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/morphseq.c')
-rw-r--r--src/morphseq.c1235
1 files changed, 1235 insertions, 0 deletions
diff --git a/src/morphseq.c b/src/morphseq.c
new file mode 100644
index 0000000..5b972ef
--- /dev/null
+++ b/src/morphseq.c
@@ -0,0 +1,1235 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+/*
+ * 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()
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+/*-------------------------------------------------------------------------*
+ * Run a sequence of binary rasterop morphological operations *
+ *-------------------------------------------------------------------------*/
+/*!
+ * pixMorphSequence()
+ *
+ * Input: pixs
+ * sequence (string specifying sequence)
+ * dispsep (controls debug display of each result 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, *fname;
+char buf[256];
+l_int32 nops, i, j, nred, fact, w, h, x, y, border, pdfout;
+l_int32 level[4];
+PIX *pixt1, *pixt2;
+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);
+ snprintf(buf, sizeof(buf), "/tmp/seq_output_%d.pdf", L_ABS(dispsep));
+ fname = genPathname(buf, NULL);
+ }
+ border = 0;
+ pixt1 = pixCopy(NULL, pixs);
+ pixt2 = NULL;
+ x = y = 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);
+ pixt2 = pixDilateBrick(NULL, pixt1, w, h);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ break;
+ case 'e':
+ case 'E':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pixt2 = pixErodeBrick(NULL, pixt1, w, h);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ break;
+ case 'o':
+ case 'O':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pixOpenBrick(pixt1, pixt1, w, h);
+ break;
+ case 'c':
+ case 'C':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pixCloseSafeBrick(pixt1, pixt1, 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;
+ pixt2 = pixReduceRankBinaryCascade(pixt1, level[0], level[1],
+ level[2], level[3]);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ break;
+ case 'x':
+ case 'X':
+ sscanf(&op[1], "%d", &fact);
+ pixt2 = pixExpandReplicate(pixt1, fact);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ break;
+ case 'b':
+ case 'B':
+ sscanf(&op[1], "%d", &border);
+ pixt2 = pixAddBorder(pixt1, border, 0);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ break;
+ default:
+ /* All invalid ops are caught in the first pass */
+ break;
+ }
+ LEPT_FREE(op);
+
+ /* Debug output */
+ if (dispsep > 0) {
+ pixDisplay(pixt1, x, y);
+ x += dispsep;
+ }
+ if (pdfout)
+ pixaAddPix(pixa, pixt1, L_COPY);
+ }
+ if (border > 0) {
+ pixt2 = pixRemoveBorder(pixt1, border);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ }
+
+ if (pdfout) {
+ pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname);
+ LEPT_FREE(fname);
+ pixaDestroy(&pixa);
+ }
+
+ sarrayDestroy(&sa);
+ return pixt1;
+}
+
+
+/*-------------------------------------------------------------------------*
+ * Run a sequence of binary composite rasterop morphological operations *
+ *-------------------------------------------------------------------------*/
+/*!
+ * pixMorphCompSequence()
+ *
+ * Input: pixs
+ * sequence (string specifying sequence)
+ * dispsep (controls debug display of each result 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, *fname;
+char buf[256];
+l_int32 nops, i, j, nred, fact, w, h, x, y, border, pdfout;
+l_int32 level[4];
+PIX *pixt1, *pixt2;
+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);
+ snprintf(buf, sizeof(buf), "/tmp/seq_output_%d.pdf", L_ABS(dispsep));
+ fname = genPathname(buf, NULL);
+ }
+ border = 0;
+ pixt1 = pixCopy(NULL, pixs);
+ pixt2 = NULL;
+ x = y = 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);
+ pixt2 = pixDilateCompBrick(NULL, pixt1, w, h);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ break;
+ case 'e':
+ case 'E':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pixt2 = pixErodeCompBrick(NULL, pixt1, w, h);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ break;
+ case 'o':
+ case 'O':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pixOpenCompBrick(pixt1, pixt1, w, h);
+ break;
+ case 'c':
+ case 'C':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pixCloseSafeCompBrick(pixt1, pixt1, 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;
+ pixt2 = pixReduceRankBinaryCascade(pixt1, level[0], level[1],
+ level[2], level[3]);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ break;
+ case 'x':
+ case 'X':
+ sscanf(&op[1], "%d", &fact);
+ pixt2 = pixExpandReplicate(pixt1, fact);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ break;
+ case 'b':
+ case 'B':
+ sscanf(&op[1], "%d", &border);
+ pixt2 = pixAddBorder(pixt1, border, 0);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ break;
+ default:
+ /* All invalid ops are caught in the first pass */
+ break;
+ }
+ LEPT_FREE(op);
+
+ /* Debug output */
+ if (dispsep > 0) {
+ pixDisplay(pixt1, x, y);
+ x += dispsep;
+ }
+ if (pdfout)
+ pixaAddPix(pixa, pixt1, L_COPY);
+ }
+ if (border > 0) {
+ pixt2 = pixRemoveBorder(pixt1, border);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ }
+
+ if (pdfout) {
+ pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname);
+ LEPT_FREE(fname);
+ pixaDestroy(&pixa);
+ }
+
+ sarrayDestroy(&sa);
+ return pixt1;
+}
+
+
+/*-------------------------------------------------------------------------*
+ * Run a sequence of binary dwa morphological operations *
+ *-------------------------------------------------------------------------*/
+/*!
+ * pixMorphSequenceDwa()
+ *
+ * Input: pixs
+ * sequence (string specifying sequence)
+ * dispsep (controls debug display of each result 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, *fname;
+char buf[256];
+l_int32 nops, i, j, nred, fact, w, h, x, y, border, pdfout;
+l_int32 level[4];
+PIX *pixt1, *pixt2;
+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);
+ snprintf(buf, sizeof(buf), "/tmp/seq_output_%d.pdf", L_ABS(dispsep));
+ fname = genPathname(buf, NULL);
+ }
+ border = 0;
+ pixt1 = pixCopy(NULL, pixs);
+ pixt2 = NULL;
+ x = y = 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);
+ pixt2 = pixDilateBrickDwa(NULL, pixt1, w, h);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ break;
+ case 'e':
+ case 'E':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pixt2 = pixErodeBrickDwa(NULL, pixt1, w, h);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ break;
+ case 'o':
+ case 'O':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pixOpenBrickDwa(pixt1, pixt1, w, h);
+ break;
+ case 'c':
+ case 'C':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pixCloseBrickDwa(pixt1, pixt1, 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;
+ pixt2 = pixReduceRankBinaryCascade(pixt1, level[0], level[1],
+ level[2], level[3]);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ break;
+ case 'x':
+ case 'X':
+ sscanf(&op[1], "%d", &fact);
+ pixt2 = pixExpandReplicate(pixt1, fact);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ break;
+ case 'b':
+ case 'B':
+ sscanf(&op[1], "%d", &border);
+ pixt2 = pixAddBorder(pixt1, border, 0);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ break;
+ default:
+ /* All invalid ops are caught in the first pass */
+ break;
+ }
+ LEPT_FREE(op);
+
+ /* Debug output */
+ if (dispsep > 0) {
+ pixDisplay(pixt1, x, y);
+ x += dispsep;
+ }
+ if (pdfout)
+ pixaAddPix(pixa, pixt1, L_COPY);
+ }
+ if (border > 0) {
+ pixt2 = pixRemoveBorder(pixt1, border);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ }
+
+ if (pdfout) {
+ pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname);
+ LEPT_FREE(fname);
+ pixaDestroy(&pixa);
+ }
+
+ sarrayDestroy(&sa);
+ return pixt1;
+}
+
+
+/*-------------------------------------------------------------------------*
+ * Run a sequence of binary composite dwa morphological operations *
+ *-------------------------------------------------------------------------*/
+/*!
+ * pixMorphCompSequenceDwa()
+ *
+ * Input: pixs
+ * sequence (string specifying sequence)
+ * dispsep (controls debug display of each result 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, *fname;
+char buf[256];
+l_int32 nops, i, j, nred, fact, w, h, x, y, border, pdfout;
+l_int32 level[4];
+PIX *pixt1, *pixt2;
+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);
+ snprintf(buf, sizeof(buf), "/tmp/seq_output_%d.pdf", L_ABS(dispsep));
+ fname = genPathname(buf, NULL);
+ }
+ border = 0;
+ pixt1 = pixCopy(NULL, pixs);
+ pixt2 = NULL;
+ x = y = 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);
+ pixt2 = pixDilateCompBrickDwa(NULL, pixt1, w, h);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ break;
+ case 'e':
+ case 'E':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pixt2 = pixErodeCompBrickDwa(NULL, pixt1, w, h);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ break;
+ case 'o':
+ case 'O':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pixOpenCompBrickDwa(pixt1, pixt1, w, h);
+ break;
+ case 'c':
+ case 'C':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pixCloseCompBrickDwa(pixt1, pixt1, 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;
+ pixt2 = pixReduceRankBinaryCascade(pixt1, level[0], level[1],
+ level[2], level[3]);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ break;
+ case 'x':
+ case 'X':
+ sscanf(&op[1], "%d", &fact);
+ pixt2 = pixExpandReplicate(pixt1, fact);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ break;
+ case 'b':
+ case 'B':
+ sscanf(&op[1], "%d", &border);
+ pixt2 = pixAddBorder(pixt1, border, 0);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ break;
+ default:
+ /* All invalid ops are caught in the first pass */
+ break;
+ }
+ LEPT_FREE(op);
+
+ /* Debug output */
+ if (dispsep > 0) {
+ pixDisplay(pixt1, x, y);
+ x += dispsep;
+ }
+ if (pdfout)
+ pixaAddPix(pixa, pixt1, L_COPY);
+ }
+ if (border > 0) {
+ pixt2 = pixRemoveBorder(pixt1, border);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ }
+
+ if (pdfout) {
+ pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname);
+ LEPT_FREE(fname);
+ pixaDestroy(&pixa);
+ }
+
+ sarrayDestroy(&sa);
+ return pixt1;
+}
+
+
+/*-------------------------------------------------------------------------*
+ * Parser verifier for binary morphological operations *
+ *-------------------------------------------------------------------------*/
+/*!
+ * morphSequenceVerify()
+ *
+ * Input: sarray (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) {
+ fprintf(stderr, "*** op: %s invalid\n", op);
+ valid = FALSE;
+ break;
+ }
+ if (w <= 0 || h <= 0) {
+ fprintf(stderr,
+ "*** op: %s; w = %d, h = %d; must both be > 0\n",
+ op, w, h);
+ valid = FALSE;
+ break;
+ }
+/* fprintf(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) {
+ fprintf(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) {
+ fprintf(stderr, "*** op = %s; level[%d] = %d is invalid\n",
+ op, j, level[j]);
+ valid = FALSE;
+ break;
+ }
+ }
+ if (!valid)
+ break;
+/* fprintf(stderr, "op = %s", op); */
+ for (j = 0; j < nred; j++) {
+ level[j] = op[j + 1] - '0';
+/* fprintf(stderr, ", level[%d] = %d", j, level[j]); */
+ }
+/* fprintf(stderr, "\n"); */
+ break;
+ case 'x':
+ case 'X':
+ if (sscanf(&op[1], "%d", &fact) != 1) {
+ fprintf(stderr, "*** op: %s; fact invalid\n", op);
+ valid = FALSE;
+ break;
+ }
+ if (fact != 2 && fact != 4 && fact != 8 && fact != 16) {
+ fprintf(stderr, "*** op = %s; invalid fact = %d\n", op, fact);
+ valid = FALSE;
+ break;
+ }
+ netred -= intlogbase2[fact / 4];
+/* fprintf(stderr, "op = %s; fact = %d\n", op, fact); */
+ break;
+ case 'b':
+ case 'B':
+ if (sscanf(&op[1], "%d", &fact) != 1) {
+ fprintf(stderr, "*** op: %s; fact invalid\n", op);
+ valid = FALSE;
+ break;
+ }
+ if (i > 0) {
+ fprintf(stderr, "*** op = %s; must be first op\n", op);
+ valid = FALSE;
+ break;
+ }
+ if (fact < 1) {
+ fprintf(stderr, "*** op = %s; invalid fact = %d\n", op, fact);
+ valid = FALSE;
+ break;
+ }
+ border = fact;
+/* fprintf(stderr, "op = %s; fact = %d\n", op, fact); */
+ break;
+ default:
+ fprintf(stderr, "*** nonexistent op = %s\n", op);
+ valid = FALSE;
+ }
+ LEPT_FREE(op);
+ }
+
+ if (border != 0 && netred != 0) {
+ fprintf(stderr,
+ "*** op = %s; border added but net reduction not 0\n", op);
+ valid = FALSE;
+ }
+ return valid;
+}
+
+
+/*-----------------------------------------------------------------*
+ * Run a sequence of grayscale morphological operations *
+ *-----------------------------------------------------------------*/
+/*!
+ * pixGrayMorphSequence()
+ *
+ * Input: pixs
+ * sequence (string specifying sequence)
+ * dispsep (controls debug display of each result in the sequence:
+ * 0: no output
+ * > 0: gives horizontal separation in pixels between
+ * successive displays
+ * < 0: pdf output; abs(dispsep) is used for naming)
+ * 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, *fname;
+char buf[256];
+l_int32 nops, i, valid, w, h, x, pdfout;
+PIX *pixt1, *pixt2;
+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) {
+ fprintf(stderr, "*** op: %s invalid\n", op);
+ valid = FALSE;
+ break;
+ }
+ if (w < 1 || (w & 1) == 0 || h < 1 || (h & 1) == 0 ) {
+ fprintf(stderr,
+ "*** op: %s; w = %d, h = %d; must both be odd\n",
+ op, w, h);
+ valid = FALSE;
+ break;
+ }
+/* fprintf(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') {
+ fprintf(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 ) {
+ fprintf(stderr,
+ "*** op: %s; w = %d, h = %d; must both be odd\n",
+ op, w, h);
+ valid = FALSE;
+ break;
+ }
+/* fprintf(stderr, "op = %s", op); */
+ break;
+ default:
+ fprintf(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);
+ snprintf(buf, sizeof(buf), "/tmp/seq_output_%d.pdf", L_ABS(dispsep));
+ fname = genPathname(buf, NULL);
+ }
+ pixt1 = pixCopy(NULL, pixs);
+ pixt2 = 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);
+ pixt2 = pixDilateGray(pixt1, w, h);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ break;
+ case 'e':
+ case 'E':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pixt2 = pixErodeGray(pixt1, w, h);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ break;
+ case 'o':
+ case 'O':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pixt2 = pixOpenGray(pixt1, w, h);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ break;
+ case 'c':
+ case 'C':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pixt2 = pixCloseGray(pixt1, w, h);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ break;
+ case 't':
+ case 'T':
+ sscanf(&op[2], "%d.%d", &w, &h);
+ if (op[1] == 'w' || op[1] == 'W')
+ pixt2 = pixTophat(pixt1, w, h, L_TOPHAT_WHITE);
+ else /* 'b' or 'B' */
+ pixt2 = pixTophat(pixt1, w, h, L_TOPHAT_BLACK);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ break;
+ default:
+ /* All invalid ops are caught in the first pass */
+ break;
+ }
+ LEPT_FREE(op);
+
+ /* Debug output */
+ if (dispsep > 0) {
+ pixDisplay(pixt1, x, dispy);
+ x += dispsep;
+ }
+ if (pdfout)
+ pixaAddPix(pixa, pixt1, L_COPY);
+ }
+
+ if (pdfout) {
+ pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname);
+ LEPT_FREE(fname);
+ pixaDestroy(&pixa);
+ }
+
+ sarrayDestroy(&sa);
+ return pixt1;
+}
+
+
+/*-----------------------------------------------------------------*
+ * Run a sequence of color morphological operations *
+ *-----------------------------------------------------------------*/
+/*!
+ * pixColorMorphSequence()
+ *
+ * Input: pixs
+ * sequence (string specifying sequence)
+ * dispsep (controls debug display of each result in the sequence:
+ * 0: no output
+ * > 0: gives horizontal separation in pixels between
+ * successive displays
+ * < 0: pdf output; abs(dispsep) is used for naming)
+ * 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, *fname;
+char buf[256];
+l_int32 nops, i, valid, w, h, x, pdfout;
+PIX *pixt1, *pixt2;
+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) {
+ fprintf(stderr, "*** op: %s invalid\n", op);
+ valid = FALSE;
+ break;
+ }
+ if (w < 1 || (w & 1) == 0 || h < 1 || (h & 1) == 0 ) {
+ fprintf(stderr,
+ "*** op: %s; w = %d, h = %d; must both be odd\n",
+ op, w, h);
+ valid = FALSE;
+ break;
+ }
+/* fprintf(stderr, "op = %s; w = %d, h = %d\n", op, w, h); */
+ break;
+ default:
+ fprintf(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);
+ snprintf(buf, sizeof(buf), "/tmp/seq_output_%d.pdf", L_ABS(dispsep));
+ fname = genPathname(buf, NULL);
+ }
+ pixt1 = pixCopy(NULL, pixs);
+ pixt2 = 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);
+ pixt2 = pixColorMorph(pixt1, L_MORPH_DILATE, w, h);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ break;
+ case 'e':
+ case 'E':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pixt2 = pixColorMorph(pixt1, L_MORPH_ERODE, w, h);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ break;
+ case 'o':
+ case 'O':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pixt2 = pixColorMorph(pixt1, L_MORPH_OPEN, w, h);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ break;
+ case 'c':
+ case 'C':
+ sscanf(&op[1], "%d.%d", &w, &h);
+ pixt2 = pixColorMorph(pixt1, L_MORPH_CLOSE, w, h);
+ pixSwapAndDestroy(&pixt1, &pixt2);
+ break;
+ default:
+ /* All invalid ops are caught in the first pass */
+ break;
+ }
+ LEPT_FREE(op);
+
+ /* Debug output */
+ if (dispsep > 0) {
+ pixDisplay(pixt1, x, dispy);
+ x += dispsep;
+ }
+ if (pdfout)
+ pixaAddPix(pixa, pixt1, L_COPY);
+ }
+
+ if (pdfout) {
+ pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname);
+ LEPT_FREE(fname);
+ pixaDestroy(&pixa);
+ }
+
+ sarrayDestroy(&sa);
+ return pixt1;
+}