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