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