/*====================================================================* - Copyright (C) 2001 Leptonica. All rights reserved. - This software is distributed in the hope that it will be - useful, but with NO WARRANTY OF ANY KIND. - No author or distributor accepts responsibility to anyone for the - consequences of using this software, or for whether it serves any - particular purpose or works at all, unless he or she says so in - writing. Everyone is granted permission to copy, modify and - redistribute this source code, for commercial or non-commercial - purposes, with the following restrictions: (1) the origin of this - source code must not be misrepresented; (2) modified versions must - be plainly marked as such; and (3) this notice may not be removed - or altered from any source or modified source distribution. *====================================================================*/ /* * 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 <stdio.h> #include <stdlib.h> #include <string.h> #include "allheaders.h" /*-------------------------------------------------------------------------* * Run a sequence of binary rasterop morphological operations * *-------------------------------------------------------------------------*/ /*! * pixMorphSequence() * * Input: pixs * sequence (string specifying sequence) * dispsep (horizontal separation in pixels between * successive displays; use zero to suppress display) * 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; l_int32 nops, i, j, nred, fact, w, h, x, y, border; l_int32 level[4]; PIX *pixt1, *pixt2; 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); if (!morphSequenceVerify(sa)) { sarrayDestroy(&sa); return (PIX *)ERROR_PTR("sequence not valid", procName, NULL); } /* Parse and operate */ border = 0; pixt1 = pixCopy(NULL, pixs); pixt2 = NULL; x = y = 0; for (i = 0; i < nops; i++) { rawop = sarrayGetString(sa, i, 0); 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); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; case 'e': case 'E': sscanf(&op[1], "%d.%d", &w, &h); pixt2 = pixErodeBrick(NULL, pixt1, w, h); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; case 'o': case 'O': sscanf(&op[1], "%d.%d", &w, &h); pixOpenBrick(pixt1, pixt1, w, h); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; case 'c': case 'C': sscanf(&op[1], "%d.%d", &w, &h); pixCloseSafeBrick(pixt1, pixt1, w, h); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } 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]); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; case 'x': case 'X': sscanf(&op[1], "%d", &fact); pixt2 = pixExpandReplicate(pixt1, fact); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; case 'b': case 'B': sscanf(&op[1], "%d", &border); pixt2 = pixAddBorder(pixt1, border, 0); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; default: /* All invalid ops are caught in the first pass */ break; } FREE(op); } if (border > 0) { pixt2 = pixRemoveBorder(pixt1, border); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); } sarrayDestroy(&sa); return pixt1; } /*-------------------------------------------------------------------------* * Run a sequence of binary composite rasterop morphological operations * *-------------------------------------------------------------------------*/ /*! * pixMorphCompSequence() * * Input: pixs * sequence (string specifying sequence) * dispsep (horizontal separation in pixels between * successive displays; use zero to suppress display) * 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; l_int32 nops, i, j, nred, fact, w, h, x, y, border; l_int32 level[4]; PIX *pixt1, *pixt2; 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); if (!morphSequenceVerify(sa)) { sarrayDestroy(&sa); return (PIX *)ERROR_PTR("sequence not valid", procName, NULL); } /* Parse and operate */ border = 0; pixt1 = pixCopy(NULL, pixs); pixt2 = NULL; x = y = 0; for (i = 0; i < nops; i++) { rawop = sarrayGetString(sa, i, 0); 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); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; case 'e': case 'E': sscanf(&op[1], "%d.%d", &w, &h); pixt2 = pixErodeCompBrick(NULL, pixt1, w, h); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; case 'o': case 'O': sscanf(&op[1], "%d.%d", &w, &h); pixOpenCompBrick(pixt1, pixt1, w, h); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; case 'c': case 'C': sscanf(&op[1], "%d.%d", &w, &h); pixCloseSafeCompBrick(pixt1, pixt1, w, h); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } 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]); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; case 'x': case 'X': sscanf(&op[1], "%d", &fact); pixt2 = pixExpandReplicate(pixt1, fact); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; case 'b': case 'B': sscanf(&op[1], "%d", &border); pixt2 = pixAddBorder(pixt1, border, 0); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; default: /* All invalid ops are caught in the first pass */ break; } FREE(op); } if (border > 0) { pixt2 = pixRemoveBorder(pixt1, border); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); } sarrayDestroy(&sa); return pixt1; } /*-------------------------------------------------------------------------* * Run a sequence of binary dwa morphological operations * *-------------------------------------------------------------------------*/ /*! * pixMorphSequenceDwa() * * Input: pixs * sequence (string specifying sequence) * dispsep (horizontal separation in pixels between * successive displays; use zero to suppress display) * 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; l_int32 nops, i, j, nred, fact, w, h, x, y, border; l_int32 level[4]; PIX *pixt1, *pixt2; 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); if (!morphSequenceVerify(sa)) { sarrayDestroy(&sa); return (PIX *)ERROR_PTR("sequence not valid", procName, NULL); } /* Parse and operate */ border = 0; pixt1 = pixCopy(NULL, pixs); pixt2 = NULL; x = y = 0; for (i = 0; i < nops; i++) { rawop = sarrayGetString(sa, i, 0); 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); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; case 'e': case 'E': sscanf(&op[1], "%d.%d", &w, &h); pixt2 = pixErodeBrickDwa(NULL, pixt1, w, h); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; case 'o': case 'O': sscanf(&op[1], "%d.%d", &w, &h); pixOpenBrickDwa(pixt1, pixt1, w, h); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; case 'c': case 'C': sscanf(&op[1], "%d.%d", &w, &h); pixCloseBrickDwa(pixt1, pixt1, w, h); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } 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]); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; case 'x': case 'X': sscanf(&op[1], "%d", &fact); pixt2 = pixExpandReplicate(pixt1, fact); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; case 'b': case 'B': sscanf(&op[1], "%d", &border); pixt2 = pixAddBorder(pixt1, border, 0); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; default: /* All invalid ops are caught in the first pass */ break; } FREE(op); } if (border > 0) { pixt2 = pixRemoveBorder(pixt1, border); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); } sarrayDestroy(&sa); return pixt1; } /*-------------------------------------------------------------------------* * Run a sequence of binary composite dwa morphological operations * *-------------------------------------------------------------------------*/ /*! * pixMorphCompSequenceDwa() * * Input: pixs * sequence (string specifying sequence) * dispsep (horizontal separation in pixels between * successive displays; use zero to suppress display) * 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; l_int32 nops, i, j, nred, fact, w, h, x, y, border; l_int32 level[4]; PIX *pixt1, *pixt2; 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); if (!morphSequenceVerify(sa)) { sarrayDestroy(&sa); return (PIX *)ERROR_PTR("sequence not valid", procName, NULL); } /* Parse and operate */ border = 0; pixt1 = pixCopy(NULL, pixs); pixt2 = NULL; x = y = 0; for (i = 0; i < nops; i++) { rawop = sarrayGetString(sa, i, 0); 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); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; case 'e': case 'E': sscanf(&op[1], "%d.%d", &w, &h); pixt2 = pixErodeCompBrickDwa(NULL, pixt1, w, h); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; case 'o': case 'O': sscanf(&op[1], "%d.%d", &w, &h); pixOpenCompBrickDwa(pixt1, pixt1, w, h); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; case 'c': case 'C': sscanf(&op[1], "%d.%d", &w, &h); pixCloseCompBrickDwa(pixt1, pixt1, w, h); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } 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]); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; case 'x': case 'X': sscanf(&op[1], "%d", &fact); pixt2 = pixExpandReplicate(pixt1, fact); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; case 'b': case 'B': sscanf(&op[1], "%d", &border); pixt2 = pixAddBorder(pixt1, border, 0); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; default: /* All invalid ops are caught in the first pass */ break; } FREE(op); } if (border > 0) { pixt2 = pixRemoveBorder(pixt1, border); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); } 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, 0); 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; } 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 (horizontal separation in pixels between * successive displays; use zero to suppress display) * 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; l_int32 nops, i, valid, w, h, x; PIX *pixt1, *pixt2; 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); /* Verify that the operation sequence is valid */ valid = TRUE; for (i = 0; i < nops; i++) { rawop = sarrayGetString(sa, i, 0); 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; } FREE(op); } if (!valid) { sarrayDestroy(&sa); return (PIX *)ERROR_PTR("sequence invalid", procName, NULL); } /* Parse and operate */ pixt1 = pixCopy(NULL, pixs); pixt2 = NULL; x = 0; for (i = 0; i < nops; i++) { rawop = sarrayGetString(sa, i, 0); op = stringRemoveChars(rawop, " \n\t"); switch (op[0]) { case 'd': case 'D': sscanf(&op[1], "%d.%d", &w, &h); pixt2 = pixDilateGray(pixt1, w, h); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, dispy); x += dispsep; } break; case 'e': case 'E': sscanf(&op[1], "%d.%d", &w, &h); pixt2 = pixErodeGray(pixt1, w, h); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, dispy); x += dispsep; } break; case 'o': case 'O': sscanf(&op[1], "%d.%d", &w, &h); pixt2 = pixOpenGray(pixt1, w, h); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, dispy); x += dispsep; } break; case 'c': case 'C': sscanf(&op[1], "%d.%d", &w, &h); pixt2 = pixCloseGray(pixt1, w, h); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, dispy); x += dispsep; } 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); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, dispy); x += dispsep; } break; default: /* All invalid ops are caught in the first pass */ break; } FREE(op); } sarrayDestroy(&sa); return pixt1; } /*-----------------------------------------------------------------* * Run a sequence of color morphological operations * *-----------------------------------------------------------------*/ /*! * pixColorMorphSequence() * * Input: pixs * sequence (string specifying sequence) * dispsep (horizontal separation in pixels between * successive displays; use zero to suppress display) * 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; l_int32 nops, i, valid, w, h, x; PIX *pixt1, *pixt2; 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); /* Verify that the operation sequence is valid */ valid = TRUE; for (i = 0; i < nops; i++) { rawop = sarrayGetString(sa, i, 0); 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; } FREE(op); } if (!valid) { sarrayDestroy(&sa); return (PIX *)ERROR_PTR("sequence invalid", procName, NULL); } /* Parse and operate */ pixt1 = pixCopy(NULL, pixs); pixt2 = NULL; x = 0; for (i = 0; i < nops; i++) { rawop = sarrayGetString(sa, i, 0); 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); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, dispy); x += dispsep; } break; case 'e': case 'E': sscanf(&op[1], "%d.%d", &w, &h); pixt2 = pixColorMorph(pixt1, L_MORPH_ERODE, w, h); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, dispy); x += dispsep; } break; case 'o': case 'O': sscanf(&op[1], "%d.%d", &w, &h); pixt2 = pixColorMorph(pixt1, L_MORPH_OPEN, w, h); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, dispy); x += dispsep; } break; case 'c': case 'C': sscanf(&op[1], "%d.%d", &w, &h); pixt2 = pixColorMorph(pixt1, L_MORPH_CLOSE, w, h); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, dispy); x += dispsep; } break; default: /* All invalid ops are caught in the first pass */ break; } FREE(op); } sarrayDestroy(&sa); return pixt1; }