/src/leptonica/src/pix5.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 pix5.c |
29 | | * <pre> |
30 | | * |
31 | | * This file has these operations: |
32 | | * |
33 | | * (1) Measurement of 1 bpp image properties |
34 | | * (2) Extract rectangular regions |
35 | | * (3) Clip to foreground |
36 | | * (4) Extract pixel averages, reversals and variance along lines |
37 | | * (5) Rank row and column transforms |
38 | | * |
39 | | * Measurement of properties |
40 | | * l_int32 pixaFindDimensions() |
41 | | * l_int32 pixFindAreaPerimRatio() |
42 | | * NUMA *pixaFindPerimToAreaRatio() |
43 | | * l_int32 pixFindPerimToAreaRatio() |
44 | | * NUMA *pixaFindPerimSizeRatio() |
45 | | * l_int32 pixFindPerimSizeRatio() |
46 | | * NUMA *pixaFindAreaFraction() |
47 | | * l_int32 pixFindAreaFraction() |
48 | | * NUMA *pixaFindAreaFractionMasked() |
49 | | * l_int32 pixFindAreaFractionMasked() |
50 | | * NUMA *pixaFindWidthHeightRatio() |
51 | | * NUMA *pixaFindWidthHeightProduct() |
52 | | * l_int32 pixFindOverlapFraction() |
53 | | * BOXA *pixFindRectangleComps() |
54 | | * l_int32 pixConformsToRectangle() |
55 | | * |
56 | | * Extract rectangular regions |
57 | | * PIX *pixExtractRectangularRegions() |
58 | | * PIXA *pixClipRectangles() |
59 | | * PIX *pixClipRectangle() |
60 | | * PIX *pixClipRectangleWithBorder() |
61 | | * PIX *pixClipMasked() |
62 | | * l_int32 pixCropToMatch() |
63 | | * PIX *pixCropToSize() |
64 | | * PIX *pixResizeToMatch() |
65 | | * |
66 | | * Select a connected component by size |
67 | | * PIX *pixSelectComponentBySize() |
68 | | * PIX *pixFilterComponentBySize() |
69 | | * |
70 | | * Make special masks |
71 | | * PIX *pixMakeSymmetricMask() |
72 | | * PIX *pixMakeFrameMask() |
73 | | * |
74 | | * Generate a covering of rectangles over connected components |
75 | | * PIX * pixMakeCoveringOfRectangles() |
76 | | * |
77 | | * Fraction of Fg pixels under a mask |
78 | | * l_int32 pixFractionFgInMask() |
79 | | * |
80 | | * Clip to foreground |
81 | | * PIX *pixClipToForeground() |
82 | | * l_int32 pixTestClipToForeground() |
83 | | * l_int32 pixClipBoxToForeground() |
84 | | * l_int32 pixScanForForeground() |
85 | | * l_int32 pixClipBoxToEdges() |
86 | | * l_int32 pixScanForEdge() |
87 | | * |
88 | | * Extract pixel averages and reversals along lines |
89 | | * NUMA *pixExtractOnLine() |
90 | | * l_float32 pixAverageOnLine() |
91 | | * NUMA *pixAverageIntensityProfile() |
92 | | * NUMA *pixReversalProfile() |
93 | | * |
94 | | * Extract windowed variance along a line |
95 | | * NUMA *pixWindowedVarianceOnLine() |
96 | | * |
97 | | * Extract min/max of pixel values near lines |
98 | | * l_int32 pixMinMaxNearLine() |
99 | | * |
100 | | * Rank row and column transforms |
101 | | * PIX *pixRankRowTransform() |
102 | | * PIX *pixRankColumnTransform() |
103 | | * </pre> |
104 | | */ |
105 | | |
106 | | #ifdef HAVE_CONFIG_H |
107 | | #include <config_auto.h> |
108 | | #endif /* HAVE_CONFIG_H */ |
109 | | |
110 | | #include <string.h> |
111 | | #include <math.h> |
112 | | #include "allheaders.h" |
113 | | |
114 | | static const l_uint32 rmask32[] = {0x0, |
115 | | 0x00000001, 0x00000003, 0x00000007, 0x0000000f, |
116 | | 0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff, |
117 | | 0x000001ff, 0x000003ff, 0x000007ff, 0x00000fff, |
118 | | 0x00001fff, 0x00003fff, 0x00007fff, 0x0000ffff, |
119 | | 0x0001ffff, 0x0003ffff, 0x0007ffff, 0x000fffff, |
120 | | 0x001fffff, 0x003fffff, 0x007fffff, 0x00ffffff, |
121 | | 0x01ffffff, 0x03ffffff, 0x07ffffff, 0x0fffffff, |
122 | | 0x1fffffff, 0x3fffffff, 0x7fffffff, 0xffffffff}; |
123 | | |
124 | | #ifndef NO_CONSOLE_IO |
125 | | #define DEBUG_EDGES 0 |
126 | | #endif /* ~NO_CONSOLE_IO */ |
127 | | |
128 | | |
129 | | /*-------------------------------------------------------------* |
130 | | * Measurement of properties * |
131 | | *-------------------------------------------------------------*/ |
132 | | /*! |
133 | | * \brief pixaFindDimensions() |
134 | | * |
135 | | * \param[in] pixa |
136 | | * \param[out] pnaw [optional] numa of pix widths |
137 | | * \param[out] pnah [optional] numa of pix heights |
138 | | * \return 0 if OK, 1 on error |
139 | | */ |
140 | | l_ok |
141 | | pixaFindDimensions(PIXA *pixa, |
142 | | NUMA **pnaw, |
143 | | NUMA **pnah) |
144 | 1.60k | { |
145 | 1.60k | l_int32 i, n, w, h; |
146 | 1.60k | PIX *pixt; |
147 | | |
148 | 1.60k | if (pnaw) *pnaw = NULL; |
149 | 1.60k | if (pnah) *pnah = NULL; |
150 | 1.60k | if (!pnaw && !pnah) |
151 | 0 | return ERROR_INT("no output requested", __func__, 1); |
152 | 1.60k | if (!pixa) |
153 | 0 | return ERROR_INT("pixa not defined", __func__, 1); |
154 | | |
155 | 1.60k | n = pixaGetCount(pixa); |
156 | 1.60k | if (pnaw) *pnaw = numaCreate(n); |
157 | 1.60k | if (pnah) *pnah = numaCreate(n); |
158 | 168k | for (i = 0; i < n; i++) { |
159 | 166k | pixt = pixaGetPix(pixa, i, L_CLONE); |
160 | 166k | pixGetDimensions(pixt, &w, &h, NULL); |
161 | 166k | if (pnaw) |
162 | 166k | numaAddNumber(*pnaw, w); |
163 | 166k | if (pnah) |
164 | 166k | numaAddNumber(*pnah, h); |
165 | 166k | pixDestroy(&pixt); |
166 | 166k | } |
167 | 1.60k | return 0; |
168 | 1.60k | } |
169 | | |
170 | | |
171 | | /*! |
172 | | * \brief pixFindAreaPerimRatio() |
173 | | * |
174 | | * \param[in] pixs 1 bpp |
175 | | * \param[in] tab [optional] pixel sum table, can be NULL |
176 | | * \param[out] pfract area/perimeter ratio |
177 | | * \return 0 if OK, 1 on error |
178 | | * |
179 | | * <pre> |
180 | | * Notes: |
181 | | * (1) The area is the number of fg pixels that are not on the |
182 | | * boundary (i.e., are not 8-connected to a bg pixel), and the |
183 | | * perimeter is the number of fg boundary pixels. Returns |
184 | | * 0.0 if there are no fg pixels. |
185 | | * (2) This function is retained because clients are using it. |
186 | | * </pre> |
187 | | */ |
188 | | l_ok |
189 | | pixFindAreaPerimRatio(PIX *pixs, |
190 | | l_int32 *tab, |
191 | | l_float32 *pfract) |
192 | 0 | { |
193 | 0 | l_int32 *tab8; |
194 | 0 | l_int32 nfg, nbound; |
195 | 0 | PIX *pixt; |
196 | |
|
197 | 0 | if (!pfract) |
198 | 0 | return ERROR_INT("&fract not defined", __func__, 1); |
199 | 0 | *pfract = 0.0; |
200 | 0 | if (!pixs || pixGetDepth(pixs) != 1) |
201 | 0 | return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1); |
202 | | |
203 | 0 | if (!tab) |
204 | 0 | tab8 = makePixelSumTab8(); |
205 | 0 | else |
206 | 0 | tab8 = tab; |
207 | |
|
208 | 0 | pixt = pixErodeBrick(NULL, pixs, 3, 3); |
209 | 0 | pixCountPixels(pixt, &nfg, tab8); |
210 | 0 | if (nfg == 0) { |
211 | 0 | pixDestroy(&pixt); |
212 | 0 | if (!tab) LEPT_FREE(tab8); |
213 | 0 | return 0; |
214 | 0 | } |
215 | 0 | pixXor(pixt, pixt, pixs); |
216 | 0 | pixCountPixels(pixt, &nbound, tab8); |
217 | 0 | *pfract = (l_float32)nfg / (l_float32)nbound; |
218 | 0 | pixDestroy(&pixt); |
219 | |
|
220 | 0 | if (!tab) LEPT_FREE(tab8); |
221 | 0 | return 0; |
222 | 0 | } |
223 | | |
224 | | |
225 | | /*! |
226 | | * \brief pixaFindPerimToAreaRatio() |
227 | | * |
228 | | * \param[in] pixa of 1 bpp pix |
229 | | * \return na of perimeter/arear ratio for each pix, or NULL on error |
230 | | * |
231 | | * <pre> |
232 | | * Notes: |
233 | | * (1) This is typically used for a pixa consisting of |
234 | | * 1 bpp connected components. |
235 | | * </pre> |
236 | | */ |
237 | | NUMA * |
238 | | pixaFindPerimToAreaRatio(PIXA *pixa) |
239 | 0 | { |
240 | 0 | l_int32 i, n; |
241 | 0 | l_int32 *tab; |
242 | 0 | l_float32 fract; |
243 | 0 | NUMA *na; |
244 | 0 | PIX *pixt; |
245 | |
|
246 | 0 | if (!pixa) |
247 | 0 | return (NUMA *)ERROR_PTR("pixa not defined", __func__, NULL); |
248 | | |
249 | 0 | n = pixaGetCount(pixa); |
250 | 0 | na = numaCreate(n); |
251 | 0 | tab = makePixelSumTab8(); |
252 | 0 | for (i = 0; i < n; i++) { |
253 | 0 | pixt = pixaGetPix(pixa, i, L_CLONE); |
254 | 0 | pixFindPerimToAreaRatio(pixt, tab, &fract); |
255 | 0 | numaAddNumber(na, fract); |
256 | 0 | pixDestroy(&pixt); |
257 | 0 | } |
258 | 0 | LEPT_FREE(tab); |
259 | 0 | return na; |
260 | 0 | } |
261 | | |
262 | | |
263 | | /*! |
264 | | * \brief pixFindPerimToAreaRatio() |
265 | | * |
266 | | * \param[in] pixs 1 bpp |
267 | | * \param[in] tab [optional] pixel sum table, can be NULL |
268 | | * \param[out] pfract perimeter/area ratio |
269 | | * \return 0 if OK, 1 on error |
270 | | * |
271 | | * <pre> |
272 | | * Notes: |
273 | | * (1) The perimeter is the number of fg boundary pixels, and the |
274 | | * area is the number of fg pixels. This returns 0.0 if |
275 | | * there are no fg pixels. |
276 | | * (2) Unlike pixFindAreaPerimRatio(), this uses the full set of |
277 | | * fg pixels for the area, and the ratio is taken in the opposite |
278 | | * order. |
279 | | * (3) This is typically used for a single connected component. |
280 | | * This always has a value <= 1.0, and if the average distance |
281 | | * of a fg pixel from the nearest bg pixel is d, this has |
282 | | * a value ~1/d. |
283 | | * </pre> |
284 | | */ |
285 | | l_ok |
286 | | pixFindPerimToAreaRatio(PIX *pixs, |
287 | | l_int32 *tab, |
288 | | l_float32 *pfract) |
289 | 0 | { |
290 | 0 | l_int32 *tab8; |
291 | 0 | l_int32 nfg, nbound; |
292 | 0 | PIX *pixt; |
293 | |
|
294 | 0 | if (!pfract) |
295 | 0 | return ERROR_INT("&fract not defined", __func__, 1); |
296 | 0 | *pfract = 0.0; |
297 | 0 | if (!pixs || pixGetDepth(pixs) != 1) |
298 | 0 | return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1); |
299 | | |
300 | 0 | if (!tab) |
301 | 0 | tab8 = makePixelSumTab8(); |
302 | 0 | else |
303 | 0 | tab8 = tab; |
304 | |
|
305 | 0 | pixCountPixels(pixs, &nfg, tab8); |
306 | 0 | if (nfg == 0) { |
307 | 0 | if (!tab) LEPT_FREE(tab8); |
308 | 0 | return 0; |
309 | 0 | } |
310 | 0 | pixt = pixErodeBrick(NULL, pixs, 3, 3); |
311 | 0 | pixXor(pixt, pixt, pixs); |
312 | 0 | pixCountPixels(pixt, &nbound, tab8); |
313 | 0 | *pfract = (l_float32)nbound / (l_float32)nfg; |
314 | 0 | pixDestroy(&pixt); |
315 | |
|
316 | 0 | if (!tab) LEPT_FREE(tab8); |
317 | 0 | return 0; |
318 | 0 | } |
319 | | |
320 | | |
321 | | /*! |
322 | | * \brief pixaFindPerimSizeRatio() |
323 | | * |
324 | | * \param[in] pixa of 1 bpp pix |
325 | | * \return na of fg perimeter/(2*(w+h)) ratio for each pix, |
326 | | * or NULL on error |
327 | | * |
328 | | * <pre> |
329 | | * Notes: |
330 | | * (1) This is typically used for a pixa consisting of |
331 | | * 1 bpp connected components. |
332 | | * (2) This has a minimum value for a circle of pi/4; a value for |
333 | | * a rectangle component of approx. 1.0; and a value much larger |
334 | | * than 1.0 for a component with a highly irregular boundary. |
335 | | * </pre> |
336 | | */ |
337 | | NUMA * |
338 | | pixaFindPerimSizeRatio(PIXA *pixa) |
339 | 0 | { |
340 | 0 | l_int32 i, n; |
341 | 0 | l_int32 *tab; |
342 | 0 | l_float32 ratio; |
343 | 0 | NUMA *na; |
344 | 0 | PIX *pixt; |
345 | |
|
346 | 0 | if (!pixa) |
347 | 0 | return (NUMA *)ERROR_PTR("pixa not defined", __func__, NULL); |
348 | | |
349 | 0 | n = pixaGetCount(pixa); |
350 | 0 | na = numaCreate(n); |
351 | 0 | tab = makePixelSumTab8(); |
352 | 0 | for (i = 0; i < n; i++) { |
353 | 0 | pixt = pixaGetPix(pixa, i, L_CLONE); |
354 | 0 | pixFindPerimSizeRatio(pixt, tab, &ratio); |
355 | 0 | numaAddNumber(na, ratio); |
356 | 0 | pixDestroy(&pixt); |
357 | 0 | } |
358 | 0 | LEPT_FREE(tab); |
359 | 0 | return na; |
360 | 0 | } |
361 | | |
362 | | |
363 | | /*! |
364 | | * \brief pixFindPerimSizeRatio() |
365 | | * |
366 | | * \param[in] pixs 1 bpp |
367 | | * \param[in] tab [optional] pixel sum table, can be NULL |
368 | | * \param[out] pratio perimeter/size ratio |
369 | | * \return 0 if OK, 1 on error |
370 | | * |
371 | | * <pre> |
372 | | * Notes: |
373 | | * (1) We take the 'size' as twice the sum of the width and |
374 | | * height of pixs, and the perimeter is the number of fg |
375 | | * boundary pixels. We use the fg pixels of the boundary |
376 | | * because the pix may be clipped to the boundary, so an |
377 | | * erosion is required to count all boundary pixels. |
378 | | * (2) This has a large value for dendritic, fractal-like components |
379 | | * with highly irregular boundaries. |
380 | | * (3) This is typically used for a single connected component. |
381 | | * It has a value of about 1.0 for rectangular components with |
382 | | * relatively smooth boundaries. |
383 | | * </pre> |
384 | | */ |
385 | | l_ok |
386 | | pixFindPerimSizeRatio(PIX *pixs, |
387 | | l_int32 *tab, |
388 | | l_float32 *pratio) |
389 | 0 | { |
390 | 0 | l_int32 *tab8; |
391 | 0 | l_int32 w, h, nbound; |
392 | 0 | PIX *pixt; |
393 | |
|
394 | 0 | if (!pratio) |
395 | 0 | return ERROR_INT("&ratio not defined", __func__, 1); |
396 | 0 | *pratio = 0.0; |
397 | 0 | if (!pixs || pixGetDepth(pixs) != 1) |
398 | 0 | return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1); |
399 | | |
400 | 0 | if (!tab) |
401 | 0 | tab8 = makePixelSumTab8(); |
402 | 0 | else |
403 | 0 | tab8 = tab; |
404 | |
|
405 | 0 | pixt = pixErodeBrick(NULL, pixs, 3, 3); |
406 | 0 | pixXor(pixt, pixt, pixs); |
407 | 0 | pixCountPixels(pixt, &nbound, tab8); |
408 | 0 | pixGetDimensions(pixs, &w, &h, NULL); |
409 | 0 | *pratio = (0.5f * nbound) / (l_float32)(w + h); |
410 | 0 | pixDestroy(&pixt); |
411 | |
|
412 | 0 | if (!tab) LEPT_FREE(tab8); |
413 | 0 | return 0; |
414 | 0 | } |
415 | | |
416 | | |
417 | | /*! |
418 | | * \brief pixaFindAreaFraction() |
419 | | * |
420 | | * \param[in] pixa of 1 bpp pix |
421 | | * \return na of area fractions for each pix, or NULL on error |
422 | | * |
423 | | * <pre> |
424 | | * Notes: |
425 | | * (1) This is typically used for a pixa consisting of |
426 | | * 1 bpp connected components. |
427 | | * </pre> |
428 | | */ |
429 | | NUMA * |
430 | | pixaFindAreaFraction(PIXA *pixa) |
431 | 1.60k | { |
432 | 1.60k | l_int32 i, n; |
433 | 1.60k | l_int32 *tab; |
434 | 1.60k | l_float32 fract; |
435 | 1.60k | NUMA *na; |
436 | 1.60k | PIX *pixt; |
437 | | |
438 | 1.60k | if (!pixa) |
439 | 0 | return (NUMA *)ERROR_PTR("pixa not defined", __func__, NULL); |
440 | | |
441 | 1.60k | n = pixaGetCount(pixa); |
442 | 1.60k | na = numaCreate(n); |
443 | 1.60k | tab = makePixelSumTab8(); |
444 | 168k | for (i = 0; i < n; i++) { |
445 | 166k | pixt = pixaGetPix(pixa, i, L_CLONE); |
446 | 166k | pixFindAreaFraction(pixt, tab, &fract); |
447 | 166k | numaAddNumber(na, fract); |
448 | 166k | pixDestroy(&pixt); |
449 | 166k | } |
450 | 1.60k | LEPT_FREE(tab); |
451 | 1.60k | return na; |
452 | 1.60k | } |
453 | | |
454 | | |
455 | | /*! |
456 | | * \brief pixFindAreaFraction() |
457 | | * |
458 | | * \param[in] pixs 1 bpp |
459 | | * \param[in] tab [optional] pixel sum table, can be NULL |
460 | | * \param[out] pfract fg area/size ratio |
461 | | * \return 0 if OK, 1 on error |
462 | | * |
463 | | * <pre> |
464 | | * Notes: |
465 | | * (1) This finds the ratio of the number of fg pixels to the |
466 | | * size of the pix (w * h). It is typically used for a |
467 | | * single connected component. |
468 | | * </pre> |
469 | | */ |
470 | | l_ok |
471 | | pixFindAreaFraction(PIX *pixs, |
472 | | l_int32 *tab, |
473 | | l_float32 *pfract) |
474 | 166k | { |
475 | 166k | l_int32 w, h, sum; |
476 | 166k | l_int32 *tab8; |
477 | | |
478 | 166k | if (!pfract) |
479 | 0 | return ERROR_INT("&fract not defined", __func__, 1); |
480 | 166k | *pfract = 0.0; |
481 | 166k | if (!pixs || pixGetDepth(pixs) != 1) |
482 | 0 | return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1); |
483 | | |
484 | 166k | if (!tab) |
485 | 0 | tab8 = makePixelSumTab8(); |
486 | 166k | else |
487 | 166k | tab8 = tab; |
488 | 166k | pixGetDimensions(pixs, &w, &h, NULL); |
489 | 166k | pixCountPixels(pixs, &sum, tab8); |
490 | 166k | *pfract = (l_float32)sum / (l_float32)(w * h); |
491 | | |
492 | 166k | if (!tab) LEPT_FREE(tab8); |
493 | 166k | return 0; |
494 | 166k | } |
495 | | |
496 | | |
497 | | /*! |
498 | | * \brief pixaFindAreaFractionMasked() |
499 | | * |
500 | | * \param[in] pixa of 1 bpp pix |
501 | | * \param[in] pixm mask image |
502 | | * \param[in] debug 1 for output, 0 to suppress |
503 | | * \return na of ratio masked/total fractions for each pix, |
504 | | * or NULL on error |
505 | | * |
506 | | * <pre> |
507 | | * Notes: |
508 | | * (1) This is typically used for a pixa consisting of |
509 | | * 1 bpp connected components, which has an associated |
510 | | * boxa giving the location of the components relative |
511 | | * to the mask origin. |
512 | | * (2) The debug flag displays in green and red the masked and |
513 | | * unmasked parts of the image from which pixa was derived. |
514 | | * </pre> |
515 | | */ |
516 | | NUMA * |
517 | | pixaFindAreaFractionMasked(PIXA *pixa, |
518 | | PIX *pixm, |
519 | | l_int32 debug) |
520 | 0 | { |
521 | 0 | l_int32 i, n, full; |
522 | 0 | l_int32 *tab; |
523 | 0 | l_float32 fract; |
524 | 0 | BOX *box; |
525 | 0 | NUMA *na; |
526 | 0 | PIX *pix; |
527 | |
|
528 | 0 | if (!pixa) |
529 | 0 | return (NUMA *)ERROR_PTR("pixa not defined", __func__, NULL); |
530 | 0 | if (!pixm || pixGetDepth(pixm) != 1) |
531 | 0 | return (NUMA *)ERROR_PTR("pixm undefined or not 1 bpp", __func__, NULL); |
532 | | |
533 | 0 | n = pixaGetCount(pixa); |
534 | 0 | na = numaCreate(n); |
535 | 0 | tab = makePixelSumTab8(); |
536 | 0 | pixaIsFull(pixa, NULL, &full); /* check boxa */ |
537 | 0 | box = NULL; |
538 | 0 | for (i = 0; i < n; i++) { |
539 | 0 | pix = pixaGetPix(pixa, i, L_CLONE); |
540 | 0 | if (full) |
541 | 0 | box = pixaGetBox(pixa, i, L_CLONE); |
542 | 0 | pixFindAreaFractionMasked(pix, box, pixm, tab, &fract); |
543 | 0 | numaAddNumber(na, fract); |
544 | 0 | boxDestroy(&box); |
545 | 0 | pixDestroy(&pix); |
546 | 0 | } |
547 | 0 | LEPT_FREE(tab); |
548 | |
|
549 | 0 | if (debug) { |
550 | 0 | l_int32 w, h; |
551 | 0 | PIX *pix1, *pix2; |
552 | 0 | pixGetDimensions(pixm, &w, &h, NULL); |
553 | 0 | pix1 = pixaDisplay(pixa, w, h); /* recover original image */ |
554 | 0 | pix2 = pixCreate(w, h, 8); /* make an 8 bpp white image ... */ |
555 | 0 | pixSetColormap(pix2, pixcmapCreate(8)); /* that's cmapped ... */ |
556 | 0 | pixSetBlackOrWhite(pix2, L_SET_WHITE); /* and init to white */ |
557 | 0 | pixSetMaskedCmap(pix2, pix1, 0, 0, 255, 0, 0); /* color all fg red */ |
558 | 0 | pixRasterop(pix1, 0, 0, w, h, PIX_MASK, pixm, 0, 0); |
559 | 0 | pixSetMaskedCmap(pix2, pix1, 0, 0, 0, 255, 0); /* turn masked green */ |
560 | 0 | pixDisplay(pix2, 100, 100); |
561 | 0 | pixDestroy(&pix1); |
562 | 0 | pixDestroy(&pix2); |
563 | 0 | } |
564 | |
|
565 | 0 | return na; |
566 | 0 | } |
567 | | |
568 | | |
569 | | /*! |
570 | | * \brief pixFindAreaFractionMasked() |
571 | | * |
572 | | * \param[in] pixs 1 bpp, typically a single component |
573 | | * \param[in] box [optional] for pixs relative to pixm |
574 | | * \param[in] pixm 1 bpp mask, typically over the entire image from |
575 | | * which the component pixs was extracted |
576 | | * \param[in] tab [optional] pixel sum table, can be NULL |
577 | | * \param[out] pfract fg area/size ratio |
578 | | * \return 0 if OK, 1 on error |
579 | | * |
580 | | * <pre> |
581 | | * Notes: |
582 | | * (1) This finds the ratio of the number of masked fg pixels |
583 | | * in pixs to the total number of fg pixels in pixs. |
584 | | * It is typically used for a single connected component. |
585 | | * If there are no fg pixels, this returns a ratio of 0.0. |
586 | | * (2) The box gives the location of the pix relative to that |
587 | | * of the UL corner of the mask. Therefore, the rasterop |
588 | | * is performed with the pix translated to its location |
589 | | * (x, y) in the mask before ANDing. |
590 | | * If box == NULL, the UL corners of pixs and pixm are aligned. |
591 | | * </pre> |
592 | | */ |
593 | | l_ok |
594 | | pixFindAreaFractionMasked(PIX *pixs, |
595 | | BOX *box, |
596 | | PIX *pixm, |
597 | | l_int32 *tab, |
598 | | l_float32 *pfract) |
599 | 0 | { |
600 | 0 | l_int32 x, y, w, h, sum, masksum; |
601 | 0 | l_int32 *tab8; |
602 | 0 | PIX *pix1; |
603 | |
|
604 | 0 | if (!pfract) |
605 | 0 | return ERROR_INT("&fract not defined", __func__, 1); |
606 | 0 | *pfract = 0.0; |
607 | 0 | if (!pixs || pixGetDepth(pixs) != 1) |
608 | 0 | return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1); |
609 | 0 | if (!pixm || pixGetDepth(pixm) != 1) |
610 | 0 | return ERROR_INT("pixm not defined or not 1 bpp", __func__, 1); |
611 | | |
612 | 0 | if (!tab) |
613 | 0 | tab8 = makePixelSumTab8(); |
614 | 0 | else |
615 | 0 | tab8 = tab; |
616 | 0 | x = y = 0; |
617 | 0 | if (box) |
618 | 0 | boxGetGeometry(box, &x, &y, NULL, NULL); |
619 | 0 | pixGetDimensions(pixs, &w, &h, NULL); |
620 | |
|
621 | 0 | pix1 = pixCopy(NULL, pixs); |
622 | 0 | pixRasterop(pix1, 0, 0, w, h, PIX_MASK, pixm, x, y); |
623 | 0 | pixCountPixels(pixs, &sum, tab8); |
624 | 0 | if (sum == 0) { |
625 | 0 | pixDestroy(&pix1); |
626 | 0 | if (!tab) LEPT_FREE(tab8); |
627 | 0 | return 0; |
628 | 0 | } |
629 | 0 | pixCountPixels(pix1, &masksum, tab8); |
630 | 0 | *pfract = (l_float32)masksum / (l_float32)sum; |
631 | |
|
632 | 0 | if (!tab) LEPT_FREE(tab8); |
633 | 0 | pixDestroy(&pix1); |
634 | 0 | return 0; |
635 | 0 | } |
636 | | |
637 | | |
638 | | /*! |
639 | | * \brief pixaFindWidthHeightRatio() |
640 | | * |
641 | | * \param[in] pixa of 1 bpp pix |
642 | | * \return na of width/height ratios for each pix, or NULL on error |
643 | | * |
644 | | * <pre> |
645 | | * Notes: |
646 | | * (1) This is typically used for a pixa consisting of |
647 | | * 1 bpp connected components. |
648 | | * </pre> |
649 | | */ |
650 | | NUMA * |
651 | | pixaFindWidthHeightRatio(PIXA *pixa) |
652 | 1.60k | { |
653 | 1.60k | l_int32 i, n, w, h; |
654 | 1.60k | NUMA *na; |
655 | 1.60k | PIX *pixt; |
656 | | |
657 | 1.60k | if (!pixa) |
658 | 0 | return (NUMA *)ERROR_PTR("pixa not defined", __func__, NULL); |
659 | | |
660 | 1.60k | n = pixaGetCount(pixa); |
661 | 1.60k | na = numaCreate(n); |
662 | 168k | for (i = 0; i < n; i++) { |
663 | 166k | pixt = pixaGetPix(pixa, i, L_CLONE); |
664 | 166k | pixGetDimensions(pixt, &w, &h, NULL); |
665 | 166k | numaAddNumber(na, (l_float32)w / (l_float32)h); |
666 | 166k | pixDestroy(&pixt); |
667 | 166k | } |
668 | 1.60k | return na; |
669 | 1.60k | } |
670 | | |
671 | | |
672 | | /*! |
673 | | * \brief pixaFindWidthHeightProduct() |
674 | | * |
675 | | * \param[in] pixa of 1 bpp pix |
676 | | * \return na of width*height products for each pix, or NULL on error |
677 | | * |
678 | | * <pre> |
679 | | * Notes: |
680 | | * (1) This is typically used for a pixa consisting of |
681 | | * 1 bpp connected components. |
682 | | * </pre> |
683 | | */ |
684 | | NUMA * |
685 | | pixaFindWidthHeightProduct(PIXA *pixa) |
686 | 0 | { |
687 | 0 | l_int32 i, n, w, h; |
688 | 0 | NUMA *na; |
689 | 0 | PIX *pixt; |
690 | |
|
691 | 0 | if (!pixa) |
692 | 0 | return (NUMA *)ERROR_PTR("pixa not defined", __func__, NULL); |
693 | | |
694 | 0 | n = pixaGetCount(pixa); |
695 | 0 | na = numaCreate(n); |
696 | 0 | for (i = 0; i < n; i++) { |
697 | 0 | pixt = pixaGetPix(pixa, i, L_CLONE); |
698 | 0 | pixGetDimensions(pixt, &w, &h, NULL); |
699 | 0 | numaAddNumber(na, w * h); |
700 | 0 | pixDestroy(&pixt); |
701 | 0 | } |
702 | 0 | return na; |
703 | 0 | } |
704 | | |
705 | | |
706 | | /*! |
707 | | * \brief pixFindOverlapFraction() |
708 | | * |
709 | | * \param[in] pixs1, pixs2 1 bpp |
710 | | * \param[in] x2, y2 location in pixs1 of UL corner of pixs2 |
711 | | * \param[in] tab [optional] pixel sum table, can be null |
712 | | * \param[out] pratio ratio fg intersection to fg union |
713 | | * \param[out] pnoverlap [optional] number of overlapping pixels |
714 | | * \return 0 if OK, 1 on error |
715 | | * |
716 | | * <pre> |
717 | | * Notes: |
718 | | * (1) The UL corner of pixs2 is placed at (x2, y2) in pixs1. |
719 | | * (2) This measure is similar to the correlation. |
720 | | * </pre> |
721 | | */ |
722 | | l_ok |
723 | | pixFindOverlapFraction(PIX *pixs1, |
724 | | PIX *pixs2, |
725 | | l_int32 x2, |
726 | | l_int32 y2, |
727 | | l_int32 *tab, |
728 | | l_float32 *pratio, |
729 | | l_int32 *pnoverlap) |
730 | 0 | { |
731 | 0 | l_int32 *tab8; |
732 | 0 | l_int32 w, h, nintersect, nunion; |
733 | 0 | PIX *pixt; |
734 | |
|
735 | 0 | if (pnoverlap) *pnoverlap = 0; |
736 | 0 | if (!pratio) |
737 | 0 | return ERROR_INT("&ratio not defined", __func__, 1); |
738 | 0 | *pratio = 0.0; |
739 | 0 | if (!pixs1 || pixGetDepth(pixs1) != 1) |
740 | 0 | return ERROR_INT("pixs1 not defined or not 1 bpp", __func__, 1); |
741 | 0 | if (!pixs2 || pixGetDepth(pixs2) != 1) |
742 | 0 | return ERROR_INT("pixs2 not defined or not 1 bpp", __func__, 1); |
743 | | |
744 | 0 | if (!tab) |
745 | 0 | tab8 = makePixelSumTab8(); |
746 | 0 | else |
747 | 0 | tab8 = tab; |
748 | |
|
749 | 0 | pixGetDimensions(pixs2, &w, &h, NULL); |
750 | 0 | pixt = pixCopy(NULL, pixs1); |
751 | 0 | pixRasterop(pixt, x2, y2, w, h, PIX_MASK, pixs2, 0, 0); /* AND */ |
752 | 0 | pixCountPixels(pixt, &nintersect, tab8); |
753 | 0 | if (pnoverlap) |
754 | 0 | *pnoverlap = nintersect; |
755 | 0 | pixCopy(pixt, pixs1); |
756 | 0 | pixRasterop(pixt, x2, y2, w, h, PIX_PAINT, pixs2, 0, 0); /* OR */ |
757 | 0 | pixCountPixels(pixt, &nunion, tab8); |
758 | 0 | if (!tab) LEPT_FREE(tab8); |
759 | 0 | pixDestroy(&pixt); |
760 | |
|
761 | 0 | if (nunion > 0) |
762 | 0 | *pratio = (l_float32)nintersect / (l_float32)nunion; |
763 | 0 | return 0; |
764 | 0 | } |
765 | | |
766 | | |
767 | | /*! |
768 | | * \brief pixFindRectangleComps() |
769 | | * |
770 | | * \param[in] pixs 1 bpp |
771 | | * \param[in] dist max distance allowed between bounding box |
772 | | * and nearest foreground pixel within it |
773 | | * \param[in] minw, minh minimum size in each direction as a requirement |
774 | | * for a conforming rectangle |
775 | | * \return boxa of components that conform, or NULL on error |
776 | | * |
777 | | * <pre> |
778 | | * Notes: |
779 | | * (1) This applies the function pixConformsToRectangle() to |
780 | | * each 8-c.c. in pixs, and returns a boxa containing the |
781 | | * regions of all components that are conforming. |
782 | | * (2) Conforming components must satisfy both the size constraint |
783 | | * given by %minsize and the slop in conforming to a rectangle |
784 | | * determined by %dist. |
785 | | * </pre> |
786 | | */ |
787 | | BOXA * |
788 | | pixFindRectangleComps(PIX *pixs, |
789 | | l_int32 dist, |
790 | | l_int32 minw, |
791 | | l_int32 minh) |
792 | 0 | { |
793 | 0 | l_int32 w, h, i, n, conforms; |
794 | 0 | BOX *box; |
795 | 0 | BOXA *boxa, *boxad; |
796 | 0 | PIX *pix; |
797 | 0 | PIXA *pixa; |
798 | |
|
799 | 0 | if (!pixs || pixGetDepth(pixs) != 1) |
800 | 0 | return (BOXA *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL); |
801 | 0 | if (dist < 0) |
802 | 0 | return (BOXA *)ERROR_PTR("dist must be >= 0", __func__, NULL); |
803 | 0 | if (minw <= 2 * dist && minh <= 2 * dist) |
804 | 0 | return (BOXA *)ERROR_PTR("invalid parameters", __func__, NULL); |
805 | | |
806 | 0 | boxa = pixConnComp(pixs, &pixa, 8); |
807 | 0 | boxad = boxaCreate(0); |
808 | 0 | n = pixaGetCount(pixa); |
809 | 0 | for (i = 0; i < n; i++) { |
810 | 0 | pix = pixaGetPix(pixa, i, L_CLONE); |
811 | 0 | pixGetDimensions(pix, &w, &h, NULL); |
812 | 0 | if (w < minw || h < minh) { |
813 | 0 | pixDestroy(&pix); |
814 | 0 | continue; |
815 | 0 | } |
816 | 0 | pixConformsToRectangle(pix, NULL, dist, &conforms); |
817 | 0 | if (conforms) { |
818 | 0 | box = boxaGetBox(boxa, i, L_COPY); |
819 | 0 | boxaAddBox(boxad, box, L_INSERT); |
820 | 0 | } |
821 | 0 | pixDestroy(&pix); |
822 | 0 | } |
823 | 0 | boxaDestroy(&boxa); |
824 | 0 | pixaDestroy(&pixa); |
825 | 0 | return boxad; |
826 | 0 | } |
827 | | |
828 | | |
829 | | /*! |
830 | | * \brief pixConformsToRectangle() |
831 | | * |
832 | | * \param[in] pixs 1 bpp |
833 | | * \param[in] box [optional] if null, use the entire pixs |
834 | | * \param[in] dist max distance allowed between bounding box and |
835 | | * nearest foreground pixel within it |
836 | | * \param[out] pconforms 0 (false) if not conforming; |
837 | | * 1 (true) if conforming |
838 | | * \return 0 if OK, 1 on error |
839 | | * |
840 | | * <pre> |
841 | | * Notes: |
842 | | * (1) There are several ways to test if a connected component has |
843 | | * an essentially rectangular boundary, such as: |
844 | | * a. Fraction of fill into the bounding box |
845 | | * b. Max-min distance of fg pixel from periphery of bounding box |
846 | | * c. Max depth of bg intrusions into component within bounding box |
847 | | * The weakness of (a) is that it is highly sensitive to holes |
848 | | * within the c.c. The weakness of (b) is that it can have |
849 | | * arbitrarily large intrusions into the c.c. Method (c) tests |
850 | | * the integrity of the outer boundary of the c.c., with respect |
851 | | * to the enclosing bounding box, so we use it. |
852 | | * (2) This tests if the connected component within the box conforms |
853 | | * to the box at all points on the periphery within %dist. |
854 | | * Inside, at a distance from the box boundary that is greater |
855 | | * than %dist, we don't care about the pixels in the c.c. |
856 | | * (3) We can think of the conforming condition as follows: |
857 | | * No pixel inside a distance %dist from the boundary |
858 | | * can connect to the boundary through a path through the bg. |
859 | | * To implement this, we need to do a flood fill. We can go |
860 | | * either from inside toward the boundary, or the other direction. |
861 | | * It's easiest to fill from the boundary, and then verify that |
862 | | * there are no filled pixels farther than %dist from the boundary. |
863 | | * </pre> |
864 | | */ |
865 | | l_ok |
866 | | pixConformsToRectangle(PIX *pixs, |
867 | | BOX *box, |
868 | | l_int32 dist, |
869 | | l_int32 *pconforms) |
870 | 0 | { |
871 | 0 | l_int32 w, h, empty; |
872 | 0 | PIX *pix1, *pix2; |
873 | |
|
874 | 0 | if (!pconforms) |
875 | 0 | return ERROR_INT("&conforms not defined", __func__, 1); |
876 | 0 | *pconforms = 0; |
877 | 0 | if (!pixs || pixGetDepth(pixs) != 1) |
878 | 0 | return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1); |
879 | 0 | if (dist < 0) |
880 | 0 | return ERROR_INT("dist must be >= 0", __func__, 1); |
881 | 0 | pixGetDimensions(pixs, &w, &h, NULL); |
882 | 0 | if (w <= 2 * dist || h <= 2 * dist) { |
883 | 0 | L_WARNING("automatic conformation: distance too large\n", __func__); |
884 | 0 | *pconforms = 1; |
885 | 0 | return 0; |
886 | 0 | } |
887 | | |
888 | | /* Extract the region, if necessary */ |
889 | 0 | if (box) |
890 | 0 | pix1 = pixClipRectangle(pixs, box, NULL); |
891 | 0 | else |
892 | 0 | pix1 = pixCopy(NULL, pixs); |
893 | | |
894 | | /* Invert and fill from the boundary into the interior. |
895 | | * Because we're considering the connected component in an |
896 | | * 8-connected sense, we do the background filling as 4 c.c. */ |
897 | 0 | pixInvert(pix1, pix1); |
898 | 0 | pix2 = pixExtractBorderConnComps(pix1, 4); |
899 | | |
900 | | /* Mask out all pixels within a distance %dist from the box |
901 | | * boundary. Any remaining pixels are from filling that goes |
902 | | * more than %dist from the boundary. If no pixels remain, |
903 | | * the component conforms to the bounding rectangle within |
904 | | * a distance %dist. */ |
905 | 0 | pixSetOrClearBorder(pix2, dist, dist, dist, dist, PIX_CLR); |
906 | 0 | pixZero(pix2, &empty); |
907 | 0 | pixDestroy(&pix1); |
908 | 0 | pixDestroy(&pix2); |
909 | 0 | *pconforms = (empty) ? 1 : 0; |
910 | 0 | return 0; |
911 | 0 | } |
912 | | |
913 | | |
914 | | /*-----------------------------------------------------------------------* |
915 | | * Extract rectangular regions * |
916 | | *-----------------------------------------------------------------------*/ |
917 | | /*! |
918 | | * \brief pixExtractRectangularRegions() |
919 | | * |
920 | | * \param[in] pixs |
921 | | * \param[in] boxa regions to extract |
922 | | * \return pix with extracted regions, or NULL on error |
923 | | * |
924 | | * <pre> |
925 | | * Notes: |
926 | | * (1) The returned pix has the rectangular regions clipped from |
927 | | * the input pixs. |
928 | | * (2) We could equally well do this operation using a mask of 1's over |
929 | | * the regions determined by the boxa: |
930 | | * pix1 = pixCreateTemplate(pixs); |
931 | | * pixMaskBoxa(pix1, pix1, boxa, L_SET_PIXELS); |
932 | | * pixAnd(pix1, pix1, pixs); |
933 | | * </pre> |
934 | | */ |
935 | | PIX * |
936 | | pixExtractRectangularRegions(PIX *pixs, |
937 | | BOXA *boxa) |
938 | 0 | { |
939 | 0 | l_int32 w, h; |
940 | 0 | PIX *pix1; |
941 | 0 | PIXA *pixa1; |
942 | |
|
943 | 0 | if (!pixs) |
944 | 0 | return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); |
945 | 0 | if (!boxa) |
946 | 0 | return (PIX *)ERROR_PTR("boxa not defined", __func__, NULL); |
947 | | |
948 | 0 | if ((pixa1 = pixClipRectangles(pixs, boxa)) == NULL) |
949 | 0 | return (PIX *)ERROR_PTR("pixa1 not made", __func__, NULL); |
950 | 0 | pixGetDimensions(pixs, &w, &h, NULL); |
951 | 0 | pix1 = pixaDisplay(pixa1, w, h); |
952 | 0 | pixaDestroy(&pixa1); |
953 | 0 | return pix1; |
954 | 0 | } |
955 | | |
956 | | |
957 | | /*! |
958 | | * \brief pixClipRectangles() |
959 | | * |
960 | | * \param[in] pixs |
961 | | * \param[in] boxa requested clipping regions |
962 | | * \return pixa consisting of requested regions, or NULL on error |
963 | | * |
964 | | * <pre> |
965 | | * Notes: |
966 | | * (1) The boxa in the returned pixa has the regions clipped from |
967 | | * the input pixs. |
968 | | * </pre> |
969 | | */ |
970 | | PIXA * |
971 | | pixClipRectangles(PIX *pixs, |
972 | | BOXA *boxa) |
973 | 5.38k | { |
974 | 5.38k | l_int32 i, n; |
975 | 5.38k | BOX *box, *boxc; |
976 | 5.38k | PIX *pix; |
977 | 5.38k | PIXA *pixa; |
978 | | |
979 | 5.38k | if (!pixs) |
980 | 0 | return (PIXA *)ERROR_PTR("pixs not defined", __func__, NULL); |
981 | 5.38k | if (!boxa) |
982 | 632 | return (PIXA *)ERROR_PTR("boxa not defined", __func__, NULL); |
983 | | |
984 | 4.75k | n = boxaGetCount(boxa); |
985 | 4.75k | pixa = pixaCreate(n); |
986 | 331k | for (i = 0; i < n; i++) { |
987 | 327k | box = boxaGetBox(boxa, i, L_CLONE); |
988 | 327k | pix = pixClipRectangle(pixs, box, &boxc); |
989 | 327k | pixaAddPix(pixa, pix, L_INSERT); |
990 | 327k | pixaAddBox(pixa, boxc, L_INSERT); |
991 | 327k | boxDestroy(&box); |
992 | 327k | } |
993 | | |
994 | 4.75k | return pixa; |
995 | 5.38k | } |
996 | | |
997 | | |
998 | | /*! |
999 | | * \brief pixClipRectangle() |
1000 | | * |
1001 | | * \param[in] pixs |
1002 | | * \param[in] box requested clipping region; const |
1003 | | * \param[out] pboxc [optional] actual box of clipped region |
1004 | | * \return clipped pix, or NULL on error or if rectangle |
1005 | | * doesn't intersect pixs |
1006 | | * |
1007 | | * <pre> |
1008 | | * Notes: |
1009 | | * |
1010 | | * This should be simple, but there are choices to be made. |
1011 | | * The box is defined relative to the pix coordinates. However, |
1012 | | * if the box is not contained within the pix, we have two choices: |
1013 | | * |
1014 | | * (1) clip the box to the pix |
1015 | | * (2) make a new pix equal to the full box dimensions, |
1016 | | * but let rasterop do the clipping and positioning |
1017 | | * of the src with respect to the dest |
1018 | | * |
1019 | | * Choice (2) immediately brings up the problem of what pixel values |
1020 | | * to use that were not taken from the src. For example, on a grayscale |
1021 | | * image, do you want the pixels not taken from the src to be black |
1022 | | * or white or something else? To implement choice 2, one needs to |
1023 | | * specify the color of these extra pixels. |
1024 | | * |
1025 | | * So we adopt (1), and clip the box first, if necessary, |
1026 | | * before making the dest pix and doing the rasterop. But there |
1027 | | * is another issue to consider. If you want to paste the |
1028 | | * clipped pix back into pixs, it must be properly aligned, and |
1029 | | * it is necessary to use the clipped box for alignment. |
1030 | | * Accordingly, this function has a third (optional) argument, which is |
1031 | | * the input box clipped to the src pix. |
1032 | | * </pre> |
1033 | | */ |
1034 | | PIX * |
1035 | | pixClipRectangle(PIX *pixs, |
1036 | | BOX *box, |
1037 | | BOX **pboxc) |
1038 | 26.2M | { |
1039 | 26.2M | l_int32 w, h, d, bx, by, bw, bh; |
1040 | 26.2M | BOX *boxc; |
1041 | 26.2M | PIX *pixd; |
1042 | | |
1043 | 26.2M | if (pboxc) *pboxc = NULL; |
1044 | 26.2M | if (!pixs) |
1045 | 188 | return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); |
1046 | 26.2M | if (!box) |
1047 | 5.71k | return (PIX *)ERROR_PTR("box not defined", __func__, NULL); |
1048 | | |
1049 | | /* Clip the input box to the pix */ |
1050 | 26.2M | pixGetDimensions(pixs, &w, &h, &d); |
1051 | 26.2M | if ((boxc = boxClipToRectangle(box, w, h)) == NULL) { |
1052 | 706 | L_WARNING("box doesn't overlap pix\n", __func__); |
1053 | 706 | return NULL; |
1054 | 706 | } |
1055 | 26.2M | boxGetGeometry(boxc, &bx, &by, &bw, &bh); |
1056 | | |
1057 | | /* Extract the block */ |
1058 | 26.2M | if ((pixd = pixCreate(bw, bh, d)) == NULL) { |
1059 | 0 | boxDestroy(&boxc); |
1060 | 0 | return (PIX *)ERROR_PTR("pixd not made", __func__, NULL); |
1061 | 0 | } |
1062 | 26.2M | pixCopyResolution(pixd, pixs); |
1063 | 26.2M | pixCopyColormap(pixd, pixs); |
1064 | 26.2M | pixCopyText(pixd, pixs); |
1065 | 26.2M | pixRasterop(pixd, 0, 0, bw, bh, PIX_SRC, pixs, bx, by); |
1066 | | |
1067 | 26.2M | if (pboxc) |
1068 | 327k | *pboxc = boxc; |
1069 | 25.9M | else |
1070 | 25.9M | boxDestroy(&boxc); |
1071 | | |
1072 | 26.2M | return pixd; |
1073 | 26.2M | } |
1074 | | |
1075 | | |
1076 | | /*! |
1077 | | * \brief pixClipRectangleWithBorder() |
1078 | | * |
1079 | | * \param[in] pixs |
1080 | | * \param[in] box requested clipping region; const |
1081 | | * \param[in] maxbord maximum amount of border to include |
1082 | | * \param[out] pboxn box in coordinates of returned pix |
1083 | | * \return under-clipped pix, or NULL on error or if rectangle |
1084 | | * doesn't intersect pixs |
1085 | | * |
1086 | | * <pre> |
1087 | | * Notes: |
1088 | | * (1) This underclips by an amount determined by the minimum of |
1089 | | * %maxbord and the amount of border that can be included |
1090 | | * equally on all 4 sides. |
1091 | | * (2) If part of the rectangle lies outside the pix, no border |
1092 | | * is included on any side. |
1093 | | * </pre> |
1094 | | */ |
1095 | | PIX * |
1096 | | pixClipRectangleWithBorder(PIX *pixs, |
1097 | | BOX *box, |
1098 | | l_int32 maxbord, |
1099 | | BOX **pboxn) |
1100 | 0 | { |
1101 | 0 | l_int32 w, h, bx, by, bw, bh, bord; |
1102 | 0 | BOX *box1; |
1103 | 0 | PIX *pix1; |
1104 | |
|
1105 | 0 | if (!pboxn) |
1106 | 0 | return (PIX *)ERROR_PTR("&boxn not defined", __func__, NULL); |
1107 | 0 | *pboxn = NULL; |
1108 | 0 | if (!pixs) |
1109 | 0 | return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); |
1110 | 0 | if (!box) |
1111 | 0 | return (PIX *)ERROR_PTR("box not defined", __func__, NULL); |
1112 | | |
1113 | | /* Determine the border width */ |
1114 | 0 | pixGetDimensions(pixs, &w, &h, NULL); |
1115 | 0 | boxGetGeometry(box, &bx, &by, &bw, &bh); |
1116 | 0 | bord = L_MIN(bx, by); |
1117 | 0 | bord = L_MIN(bord, w - bx - bw); |
1118 | 0 | bord = L_MIN(bord, h - by - bh); |
1119 | 0 | bord = L_MIN(bord, maxbord); |
1120 | |
|
1121 | 0 | if (bord <= 0) { /* standard clipping */ |
1122 | 0 | pix1 = pixClipRectangle(pixs, box, NULL); |
1123 | 0 | pixGetDimensions(pix1, &w, &h, NULL); |
1124 | 0 | *pboxn = boxCreate(0, 0, w, h); |
1125 | 0 | return pix1; |
1126 | 0 | } |
1127 | | |
1128 | | /* There is a positive border */ |
1129 | 0 | box1 = boxAdjustSides(NULL, box, -bord, bord, -bord, bord); |
1130 | 0 | pix1 = pixClipRectangle(pixs, box1, NULL); |
1131 | 0 | boxDestroy(&box1); |
1132 | 0 | *pboxn = boxCreate(bord, bord, bw, bh); |
1133 | 0 | return pix1; |
1134 | 0 | } |
1135 | | |
1136 | | |
1137 | | /*! |
1138 | | * \brief pixClipMasked() |
1139 | | * |
1140 | | * \param[in] pixs 1, 2, 4, 8, 16, 32 bpp; colormap ok |
1141 | | * \param[in] pixm clipping mask, 1 bpp |
1142 | | * \param[in] x, y origin of clipping mask relative to pixs |
1143 | | * \param[in] outval val to use for pixels that are outside the mask |
1144 | | * \return pixd, clipped pix or NULL on error or if pixm doesn't |
1145 | | * intersect pixs |
1146 | | * |
1147 | | * <pre> |
1148 | | * Notes: |
1149 | | * (1) If pixs has a colormap, it is preserved in pixd. |
1150 | | * (2) The depth of pixd is the same as that of pixs. |
1151 | | * (3) If the depth of pixs is 1, use %outval = 0 for white background |
1152 | | * and 1 for black; otherwise, use the max value for white |
1153 | | * and 0 for black. If pixs has a colormap, the max value for |
1154 | | * %outval is 0xffffffff; otherwise, it is 2^d - 1. |
1155 | | * (4) When using 1 bpp pixs, this is a simple clip and |
1156 | | * blend operation. For example, if both pix1 and pix2 are |
1157 | | * black text on white background, and you want to OR the |
1158 | | * fg on the two images, let pixm be the inverse of pix2. |
1159 | | * Then the operation takes all of pix1 that's in the bg of |
1160 | | * pix2, and for the remainder (which are the pixels |
1161 | | * corresponding to the fg of the pix2), paint them black |
1162 | | * (1) in pix1. The function call looks like |
1163 | | * pixClipMasked(pix2, pixInvert(pix1, pix1), x, y, 1); |
1164 | | * </pre> |
1165 | | */ |
1166 | | PIX * |
1167 | | pixClipMasked(PIX *pixs, |
1168 | | PIX *pixm, |
1169 | | l_int32 x, |
1170 | | l_int32 y, |
1171 | | l_uint32 outval) |
1172 | 0 | { |
1173 | 0 | l_int32 wm, hm, index, rval, gval, bval; |
1174 | 0 | l_uint32 pixel; |
1175 | 0 | BOX *box; |
1176 | 0 | PIX *pixmi, *pixd; |
1177 | 0 | PIXCMAP *cmap; |
1178 | |
|
1179 | 0 | if (!pixs) |
1180 | 0 | return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); |
1181 | 0 | if (!pixm || pixGetDepth(pixm) != 1) |
1182 | 0 | return (PIX *)ERROR_PTR("pixm undefined or not 1 bpp", __func__, NULL); |
1183 | | |
1184 | | /* Clip out the region specified by pixm and (x,y) */ |
1185 | 0 | pixGetDimensions(pixm, &wm, &hm, NULL); |
1186 | 0 | box = boxCreate(x, y, wm, hm); |
1187 | 0 | pixd = pixClipRectangle(pixs, box, NULL); |
1188 | | |
1189 | | /* Paint 'outval' (or something close to it if cmapped) through |
1190 | | * the pixels not masked by pixm */ |
1191 | 0 | cmap = pixGetColormap(pixd); |
1192 | 0 | pixmi = pixInvert(NULL, pixm); |
1193 | 0 | if (cmap) { |
1194 | 0 | extractRGBValues(outval, &rval, &gval, &bval); |
1195 | 0 | pixcmapGetNearestIndex(cmap, rval, gval, bval, &index); |
1196 | 0 | pixcmapGetColor(cmap, index, &rval, &gval, &bval); |
1197 | 0 | composeRGBPixel(rval, gval, bval, &pixel); |
1198 | 0 | pixPaintThroughMask(pixd, pixmi, 0, 0, pixel); |
1199 | 0 | } else { |
1200 | 0 | pixPaintThroughMask(pixd, pixmi, 0, 0, outval); |
1201 | 0 | } |
1202 | |
|
1203 | 0 | boxDestroy(&box); |
1204 | 0 | pixDestroy(&pixmi); |
1205 | 0 | return pixd; |
1206 | 0 | } |
1207 | | |
1208 | | |
1209 | | /*! |
1210 | | * \brief pixCropToMatch() |
1211 | | * |
1212 | | * \param[in] pixs1 any depth, colormap OK |
1213 | | * \param[in] pixs2 any depth, colormap OK |
1214 | | * \param[out] ppixd1 may be a clone |
1215 | | * \param[out] ppixd2 may be a clone |
1216 | | * \return 0 if OK, 1 on error |
1217 | | * |
1218 | | * <pre> |
1219 | | * Notes: |
1220 | | * (1) This resizes pixs1 and/or pixs2 by cropping at the right |
1221 | | * and bottom, so that they're the same size. |
1222 | | * (2) If a pix doesn't need to be cropped, a clone is returned. |
1223 | | * (3) Note: the images are implicitly aligned to the UL corner. |
1224 | | * </pre> |
1225 | | */ |
1226 | | l_ok |
1227 | | pixCropToMatch(PIX *pixs1, |
1228 | | PIX *pixs2, |
1229 | | PIX **ppixd1, |
1230 | | PIX **ppixd2) |
1231 | 0 | { |
1232 | 0 | l_int32 w1, h1, w2, h2, w, h; |
1233 | |
|
1234 | 0 | if (!ppixd1 || !ppixd2) |
1235 | 0 | return ERROR_INT("&pixd1 and &pixd2 not both defined", __func__, 1); |
1236 | 0 | *ppixd1 = *ppixd2 = NULL; |
1237 | 0 | if (!pixs1 || !pixs2) |
1238 | 0 | return ERROR_INT("pixs1 and pixs2 not defined", __func__, 1); |
1239 | | |
1240 | 0 | pixGetDimensions(pixs1, &w1, &h1, NULL); |
1241 | 0 | pixGetDimensions(pixs2, &w2, &h2, NULL); |
1242 | 0 | w = L_MIN(w1, w2); |
1243 | 0 | h = L_MIN(h1, h2); |
1244 | |
|
1245 | 0 | *ppixd1 = pixCropToSize(pixs1, w, h); |
1246 | 0 | *ppixd2 = pixCropToSize(pixs2, w, h); |
1247 | 0 | if (*ppixd1 == NULL || *ppixd2 == NULL) |
1248 | 0 | return ERROR_INT("cropped image failure", __func__, 1); |
1249 | 0 | return 0; |
1250 | 0 | } |
1251 | | |
1252 | | |
1253 | | /*! |
1254 | | * \brief pixCropToSize() |
1255 | | * |
1256 | | * \param[in] pixs any depth, colormap OK |
1257 | | * \param[in] w, h max dimensions of cropped image |
1258 | | * \return pixd cropped if necessary or NULL on error. |
1259 | | * |
1260 | | * <pre> |
1261 | | * Notes: |
1262 | | * (1) If either w or h is smaller than the corresponding dimension |
1263 | | * of pixs, this returns a cropped image; otherwise it returns |
1264 | | * a clone of pixs. |
1265 | | * </pre> |
1266 | | */ |
1267 | | PIX * |
1268 | | pixCropToSize(PIX *pixs, |
1269 | | l_int32 w, |
1270 | | l_int32 h) |
1271 | 0 | { |
1272 | 0 | l_int32 ws, hs, wd, hd, d; |
1273 | 0 | PIX *pixd; |
1274 | |
|
1275 | 0 | if (!pixs) |
1276 | 0 | return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); |
1277 | | |
1278 | 0 | pixGetDimensions(pixs, &ws, &hs, &d); |
1279 | 0 | if (ws <= w && hs <= h) /* no cropping necessary */ |
1280 | 0 | return pixClone(pixs); |
1281 | | |
1282 | 0 | wd = L_MIN(ws, w); |
1283 | 0 | hd = L_MIN(hs, h); |
1284 | 0 | if ((pixd = pixCreate(wd, hd, d)) == NULL) |
1285 | 0 | return (PIX *)ERROR_PTR("pixd not made", __func__, NULL); |
1286 | 0 | pixCopyResolution(pixd, pixs); |
1287 | 0 | pixCopyColormap(pixd, pixs); |
1288 | 0 | pixCopyText(pixd, pixs); |
1289 | 0 | pixCopyInputFormat(pixd, pixs); |
1290 | 0 | pixRasterop(pixd, 0, 0, wd, hd, PIX_SRC, pixs, 0, 0); |
1291 | 0 | return pixd; |
1292 | 0 | } |
1293 | | |
1294 | | |
1295 | | /*! |
1296 | | * \brief pixResizeToMatch() |
1297 | | * |
1298 | | * \param[in] pixs 1, 2, 4, 8, 16, 32 bpp; colormap ok |
1299 | | * \param[in] pixt can be null; we use only the size |
1300 | | * \param[in] w, h ignored if pixt is defined |
1301 | | * \return pixd resized to match or NULL on error |
1302 | | * |
1303 | | * <pre> |
1304 | | * Notes: |
1305 | | * (1) This resizes pixs to make pixd, without scaling, by either |
1306 | | * cropping or extending separately in both width and height. |
1307 | | * Extension is done by replicating the last row or column. |
1308 | | * This is useful in a situation where, due to scaling |
1309 | | * operations, two images that are expected to be the |
1310 | | * same size can differ slightly in each dimension. |
1311 | | * (2) You can use either an existing pixt or specify |
1312 | | * both %w and %h. If pixt is defined, the values |
1313 | | * in %w and %h are ignored. |
1314 | | * (3) If pixt is larger than pixs (or if w and/or d is larger |
1315 | | * than the dimension of pixs, replicate the outer row and |
1316 | | * column of pixels in pixs into pixd. |
1317 | | * </pre> |
1318 | | */ |
1319 | | PIX * |
1320 | | pixResizeToMatch(PIX *pixs, |
1321 | | PIX *pixt, |
1322 | | l_int32 w, |
1323 | | l_int32 h) |
1324 | 0 | { |
1325 | 0 | l_int32 i, j, ws, hs, d; |
1326 | 0 | PIX *pixd; |
1327 | |
|
1328 | 0 | if (!pixs) |
1329 | 0 | return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); |
1330 | 0 | if (!pixt && (w <= 0 || h <= 0)) |
1331 | 0 | return (PIX *)ERROR_PTR("both w and h not > 0", __func__, NULL); |
1332 | | |
1333 | 0 | if (pixt) /* redefine w, h */ |
1334 | 0 | pixGetDimensions(pixt, &w, &h, NULL); |
1335 | 0 | pixGetDimensions(pixs, &ws, &hs, &d); |
1336 | 0 | if (ws == w && hs == h) |
1337 | 0 | return pixCopy(NULL, pixs); |
1338 | | |
1339 | 0 | if ((pixd = pixCreate(w, h, d)) == NULL) |
1340 | 0 | return (PIX *)ERROR_PTR("pixd not made", __func__, NULL); |
1341 | 0 | pixCopyResolution(pixd, pixs); |
1342 | 0 | pixCopyColormap(pixd, pixs); |
1343 | 0 | pixCopyText(pixd, pixs); |
1344 | 0 | pixCopyInputFormat(pixd, pixs); |
1345 | 0 | pixRasterop(pixd, 0, 0, ws, hs, PIX_SRC, pixs, 0, 0); |
1346 | 0 | if (ws >= w && hs >= h) |
1347 | 0 | return pixd; |
1348 | | |
1349 | | /* Replicate the last column and then the last row */ |
1350 | 0 | if (ws < w) { |
1351 | 0 | for (j = ws; j < w; j++) |
1352 | 0 | pixRasterop(pixd, j, 0, 1, h, PIX_SRC, pixd, ws - 1, 0); |
1353 | 0 | } |
1354 | 0 | if (hs < h) { |
1355 | 0 | for (i = hs; i < h; i++) |
1356 | 0 | pixRasterop(pixd, 0, i, w, 1, PIX_SRC, pixd, 0, hs - 1); |
1357 | 0 | } |
1358 | |
|
1359 | 0 | return pixd; |
1360 | 0 | } |
1361 | | |
1362 | | |
1363 | | /*---------------------------------------------------------------------* |
1364 | | * Select a connected component by size * |
1365 | | *---------------------------------------------------------------------*/ |
1366 | | /*! |
1367 | | * \brief pixSelectComponentBySize() |
1368 | | * |
1369 | | * \param[in] pixs 1 bpp |
1370 | | * \param[in] rankorder in decreasing size: 0 for largest. |
1371 | | * \param[in] type L_SELECT_BY_WIDTH, L_SELECT_BY_HEIGHT, |
1372 | | * L_SELECT_BY_MAX_DIMENSION, |
1373 | | * L_SELECT_BY_AREA, L_SELECT_BY_PERIMETER |
1374 | | * \param[in] connectivity 4 or 8 |
1375 | | * \param[out] pbox [optional] location of returned component |
1376 | | * \return pix of rank order connected component, or NULL on error. |
1377 | | * |
1378 | | * <pre> |
1379 | | * Notes: |
1380 | | * (1) This selects the Nth largest connected component, based on |
1381 | | * the selection type and connectivity. |
1382 | | * (2) Note that %rankorder is an integer. Use %rankorder = 0 for |
1383 | | * the largest component and %rankorder = -1 for the smallest. |
1384 | | * If %rankorder >= number of components, select the smallest. |
1385 | | */ |
1386 | | PIX * |
1387 | | pixSelectComponentBySize(PIX *pixs, |
1388 | | l_int32 rankorder, |
1389 | | l_int32 type, |
1390 | | l_int32 connectivity, |
1391 | | BOX **pbox) |
1392 | 0 | { |
1393 | 0 | l_int32 n, empty, sorttype, index; |
1394 | 0 | BOXA *boxa1; |
1395 | 0 | NUMA *naindex; |
1396 | 0 | PIX *pixd; |
1397 | 0 | PIXA *pixa1, *pixa2; |
1398 | |
|
1399 | 0 | if (pbox) *pbox = NULL; |
1400 | 0 | if (!pixs || pixGetDepth(pixs) != 1) |
1401 | 0 | return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL); |
1402 | 0 | if (type == L_SELECT_BY_WIDTH) |
1403 | 0 | sorttype = L_SORT_BY_WIDTH; |
1404 | 0 | else if (type == L_SELECT_BY_HEIGHT) |
1405 | 0 | sorttype = L_SORT_BY_HEIGHT; |
1406 | 0 | else if (type == L_SELECT_BY_MAX_DIMENSION) |
1407 | 0 | sorttype = L_SORT_BY_MAX_DIMENSION; |
1408 | 0 | else if (type == L_SELECT_BY_AREA) |
1409 | 0 | sorttype = L_SORT_BY_AREA; |
1410 | 0 | else if (type == L_SELECT_BY_PERIMETER) |
1411 | 0 | sorttype = L_SORT_BY_PERIMETER; |
1412 | 0 | else |
1413 | 0 | return (PIX *)ERROR_PTR("invalid selection type", __func__, NULL); |
1414 | 0 | if (connectivity != 4 && connectivity != 8) |
1415 | 0 | return (PIX *)ERROR_PTR("connectivity not 4 or 8", __func__, NULL); |
1416 | 0 | pixZero(pixs, &empty); |
1417 | 0 | if (empty) |
1418 | 0 | return (PIX *)ERROR_PTR("no foreground pixels", __func__, NULL); |
1419 | | |
1420 | 0 | boxa1 = pixConnComp(pixs, &pixa1, connectivity); |
1421 | 0 | n = boxaGetCount(boxa1); |
1422 | 0 | if (rankorder < 0 || rankorder >= n) |
1423 | 0 | rankorder = n - 1; /* smallest */ |
1424 | 0 | pixa2 = pixaSort(pixa1, sorttype, L_SORT_DECREASING, &naindex, L_CLONE); |
1425 | 0 | pixd = pixaGetPix(pixa2, rankorder, L_COPY); |
1426 | 0 | if (pbox) { |
1427 | 0 | numaGetIValue(naindex, rankorder, &index); |
1428 | 0 | *pbox = boxaGetBox(boxa1, index, L_COPY); |
1429 | 0 | } |
1430 | |
|
1431 | 0 | numaDestroy(&naindex); |
1432 | 0 | boxaDestroy(&boxa1); |
1433 | 0 | pixaDestroy(&pixa1); |
1434 | 0 | pixaDestroy(&pixa2); |
1435 | 0 | return pixd; |
1436 | 0 | } |
1437 | | |
1438 | | |
1439 | | /*! |
1440 | | * \brief pixFilterComponentBySize() |
1441 | | * |
1442 | | * \param[in] pixs 1 bpp |
1443 | | * \param[in] rankorder in decreasing size: 0 for largest. |
1444 | | * \param[in] type L_SELECT_BY_WIDTH, L_SELECT_BY_HEIGHT, |
1445 | | * L_SELECT_BY_MAX_DIMENSION, |
1446 | | * L_SELECT_BY_AREA, L_SELECT_BY_PERIMETER |
1447 | | * \param[in] connectivity 4 or 8 |
1448 | | * \param[out] pbox [optional] location of returned component |
1449 | | * \return pix with all other components removed, or NULL on error. |
1450 | | * |
1451 | | * <pre> |
1452 | | * Notes: |
1453 | | * (1) See notes in pixSelectComponentBySize(). |
1454 | | * (2) This returns a copy of %pixs, with all components removed |
1455 | | * except for the selected one. |
1456 | | */ |
1457 | | PIX * |
1458 | | pixFilterComponentBySize(PIX *pixs, |
1459 | | l_int32 rankorder, |
1460 | | l_int32 type, |
1461 | | l_int32 connectivity, |
1462 | | BOX **pbox) |
1463 | 0 | { |
1464 | 0 | l_int32 x, y, w, h; |
1465 | 0 | BOX *box; |
1466 | 0 | PIX *pix1, *pix2; |
1467 | |
|
1468 | 0 | if (!pixs || pixGetDepth(pixs) != 1) |
1469 | 0 | return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL); |
1470 | | |
1471 | 0 | pix1 = pixSelectComponentBySize(pixs, rankorder, type, connectivity, &box); |
1472 | 0 | if (!pix1) { |
1473 | 0 | boxDestroy(&box); |
1474 | 0 | return (PIX *)ERROR_PTR("pix1 not made", __func__, NULL); |
1475 | 0 | } |
1476 | | |
1477 | | /* Put the selected component in a new pix at the same |
1478 | | * location as it had in %pixs */ |
1479 | 0 | boxGetGeometry(box, &x, &y, &w, &h); |
1480 | 0 | pix2 = pixCreateTemplate(pixs); |
1481 | 0 | pixRasterop(pix2, x, y, w, h, PIX_SRC, pix1, 0, 0); |
1482 | 0 | if (pbox) |
1483 | 0 | *pbox = box; |
1484 | 0 | else |
1485 | 0 | boxDestroy(&box); |
1486 | 0 | pixDestroy(&pix1); |
1487 | 0 | return pix2; |
1488 | 0 | } |
1489 | | |
1490 | | |
1491 | | /*---------------------------------------------------------------------* |
1492 | | * Make special masks * |
1493 | | *---------------------------------------------------------------------*/ |
1494 | | /*! |
1495 | | * \brief pixMakeSymmetricMask() |
1496 | | * |
1497 | | * \param[in] w, h dimensions of output 1 bpp pix |
1498 | | * \param[in] hf horizontal fraction of half-width |
1499 | | * \param[in] vf vertical fraction of half-height |
1500 | | * \param[in] type L_USE_INNER, L_USE_OUTER |
1501 | | * \return pixd 1 bpp, or NULL on error. |
1502 | | * |
1503 | | * <pre> |
1504 | | * Notes: |
1505 | | * (1) This is a convenience function for generating masks with |
1506 | | * horizontal and vertical reflection symmetry, over either |
1507 | | * the inner or outer parts of an image. |
1508 | | * (2) Using L_USE_INNER to generate a mask over the inner part |
1509 | | * of the image, the mask is a solid rectangle, and the fractions |
1510 | | * describe the distance between the boundary of the image and |
1511 | | * the rectangle boundary. For example, with hf == vf == 0.0, |
1512 | | * the mask covers the full image. |
1513 | | * (3) Using L_USE_OUTER to generate a mask over an outer frame |
1514 | | * of the image, the mask touches the boundary of the image, |
1515 | | * and the fractions describe the location of the inner |
1516 | | * boundary of the frame. For example, with hf == vf == 1.0, |
1517 | | * the inner boundary is at the center of the image, so the |
1518 | | * mask covers the full image. |
1519 | | * (4) More examples: |
1520 | | * * mask covering the inner 70%: hf = vf = 0.3, type = L_USE_INNER |
1521 | | * * frame covering the outer 30%: hf = vf = 0.3, type = L_USE_OUTER |
1522 | | * </pre> |
1523 | | */ |
1524 | | PIX * |
1525 | | pixMakeSymmetricMask(l_int32 w, |
1526 | | l_int32 h, |
1527 | | l_float32 hf, |
1528 | | l_float32 vf, |
1529 | | l_int32 type) |
1530 | 765 | { |
1531 | 765 | if (w <= 0 || h <= 0) |
1532 | 0 | return (PIX *)ERROR_PTR("mask size 0", __func__, NULL); |
1533 | 765 | if (hf < 0.0 || hf > 1.0) |
1534 | 0 | return (PIX *)ERROR_PTR("invalid horiz fractions", __func__, NULL); |
1535 | 765 | if (vf < 0.0 || vf > 1.0) |
1536 | 0 | return (PIX *)ERROR_PTR("invalid vert fractions", __func__, NULL); |
1537 | | |
1538 | 765 | if (type == L_USE_INNER) |
1539 | 765 | return pixMakeFrameMask(w, h, hf, 1.0, vf, 1.0); |
1540 | 0 | else if (type == L_USE_OUTER) |
1541 | 0 | return pixMakeFrameMask(w, h, 0.0, hf, 0.0, vf); |
1542 | 0 | else |
1543 | 0 | return (PIX *)ERROR_PTR("invalid type", __func__, NULL); |
1544 | 765 | } |
1545 | | |
1546 | | |
1547 | | /*! |
1548 | | * \brief pixMakeFrameMask() |
1549 | | * |
1550 | | * \param[in] w, h dimensions of output 1 bpp pix |
1551 | | * \param[in] hf1 horizontal fraction of half-width at outer frame bdry |
1552 | | * \param[in] hf2 horizontal fraction of half-width at inner frame bdry |
1553 | | * \param[in] vf1 vertical fraction of half-width at outer frame bdry |
1554 | | * \param[in] vf2 vertical fraction of half-width at inner frame bdry |
1555 | | * \return pixd 1 bpp, or NULL on error. |
1556 | | * |
1557 | | * <pre> |
1558 | | * Notes: |
1559 | | * (1) This makes an arbitrary 1-component mask with a centered fg |
1560 | | * frame, which can have both an inner and an outer boundary. |
1561 | | * All input fractional distances are measured from the image |
1562 | | * border to the frame boundary, in units of the image half-width |
1563 | | * for hf1 and hf2 and the image half-height for vf1 and vf2. |
1564 | | * The distances to the outer frame boundary are given by hf1 |
1565 | | * and vf1; to the inner frame boundary, by hf2 and vf2. |
1566 | | * Input fractions are thus in [0.0 ... 1.0], with hf1 <= hf2 |
1567 | | * and vf1 <= vf2. Horizontal and vertical frame widths are |
1568 | | * thus independently specified. |
1569 | | * (2) Special cases: |
1570 | | * * full fg mask: hf1 = vf1 = 0.0, hf2 = vf2 = 1.0. |
1571 | | * * empty fg (zero width) mask: set hf1 = hf2 and vf1 = vf2. |
1572 | | * * fg rectangle with no hole: set hf2 = vf2 = 1.0. |
1573 | | * * frame touching outer boundary: set hf1 = vf1 = 0.0. |
1574 | | * (3) The vertical thickness of the horizontal mask parts |
1575 | | * is 0.5 * (vf2 - vf1) * h. The horizontal thickness of the |
1576 | | * vertical mask parts is 0.5 * (hf2 - hf1) * w. |
1577 | | * </pre> |
1578 | | */ |
1579 | | PIX * |
1580 | | pixMakeFrameMask(l_int32 w, |
1581 | | l_int32 h, |
1582 | | l_float32 hf1, |
1583 | | l_float32 hf2, |
1584 | | l_float32 vf1, |
1585 | | l_float32 vf2) |
1586 | 765 | { |
1587 | 765 | l_int32 h1, h2, v1, v2; |
1588 | 765 | PIX *pixd; |
1589 | | |
1590 | 765 | if (w <= 0 || h <= 0) |
1591 | 0 | return (PIX *)ERROR_PTR("mask size 0", __func__, NULL); |
1592 | 765 | if (hf1 < 0.0 || hf1 > 1.0 || hf2 < 0.0 || hf2 > 1.0) |
1593 | 0 | return (PIX *)ERROR_PTR("invalid horiz fractions", __func__, NULL); |
1594 | 765 | if (vf1 < 0.0 || vf1 > 1.0 || vf2 < 0.0 || vf2 > 1.0) |
1595 | 0 | return (PIX *)ERROR_PTR("invalid vert fractions", __func__, NULL); |
1596 | 765 | if (hf1 > hf2 || vf1 > vf2) |
1597 | 0 | return (PIX *)ERROR_PTR("invalid relative sizes", __func__, NULL); |
1598 | | |
1599 | 765 | pixd = pixCreate(w, h, 1); |
1600 | | |
1601 | | /* Special cases */ |
1602 | 765 | if (hf1 == 0.0 && vf1 == 0.0 && hf2 == 1.0 && vf2 == 1.0) { /* full */ |
1603 | 0 | pixSetAll(pixd); |
1604 | 0 | return pixd; |
1605 | 0 | } |
1606 | 765 | if (hf1 == hf2 && vf1 == vf2) { /* empty */ |
1607 | 0 | return pixd; |
1608 | 0 | } |
1609 | | |
1610 | | /* General case */ |
1611 | 765 | h1 = 0.5f * hf1 * w; |
1612 | 765 | h2 = 0.5f * hf2 * w; |
1613 | 765 | v1 = 0.5f * vf1 * h; |
1614 | 765 | v2 = 0.5f * vf2 * h; |
1615 | 765 | pixRasterop(pixd, h1, v1, w - 2 * h1, h - 2 * v1, PIX_SET, NULL, 0, 0); |
1616 | 765 | if (hf2 < 1.0 && vf2 < 1.0) |
1617 | 0 | pixRasterop(pixd, h2, v2, w - 2 * h2, h - 2 * v2, PIX_CLR, NULL, 0, 0); |
1618 | 765 | return pixd; |
1619 | 765 | } |
1620 | | |
1621 | | |
1622 | | /*---------------------------------------------------------------------* |
1623 | | * Generate a covering of rectangles over connected components * |
1624 | | *---------------------------------------------------------------------*/ |
1625 | | /*! |
1626 | | * \brief pixMakeCoveringOfRectangles() |
1627 | | * |
1628 | | * \param[in] pixs 1 bpp |
1629 | | * \param[in] maxiters max iterations: use 0 to iterate to completion |
1630 | | * \return pixd, or NULL on error |
1631 | | * |
1632 | | * <pre> |
1633 | | * Notes: |
1634 | | * (1) This iteratively finds the bounding boxes of the connected |
1635 | | * components and generates a mask from them. Two iterations |
1636 | | * should suffice for most situations. |
1637 | | * (2) Returns an empty pix if %pixs is empty. |
1638 | | * (3) If there are many small components in proximity, it may |
1639 | | * be useful to merge them with a morphological closing before |
1640 | | * calling this one. |
1641 | | * </pre> |
1642 | | */ |
1643 | | PIX * |
1644 | | pixMakeCoveringOfRectangles(PIX *pixs, |
1645 | | l_int32 maxiters) |
1646 | 0 | { |
1647 | 0 | l_int32 empty, same, niters; |
1648 | 0 | BOXA *boxa; |
1649 | 0 | PIX *pix1, *pix2; |
1650 | |
|
1651 | 0 | if (!pixs || pixGetDepth(pixs) != 1) |
1652 | 0 | return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL); |
1653 | 0 | if (maxiters < 0) |
1654 | 0 | return (PIX *)ERROR_PTR("maxiters must be >= 0", __func__, NULL); |
1655 | 0 | if (maxiters == 0) maxiters = 50; /* ridiculously large number */ |
1656 | |
|
1657 | 0 | pixZero(pixs, &empty); |
1658 | 0 | pix1 = pixCreateTemplate(pixs); |
1659 | 0 | if (empty) return pix1; |
1660 | | |
1661 | | /* Do first iteration */ |
1662 | 0 | boxa = pixConnCompBB(pixs, 8); |
1663 | 0 | pixMaskBoxa(pix1, pix1, boxa, L_SET_PIXELS); |
1664 | 0 | boxaDestroy(&boxa); |
1665 | 0 | if (maxiters == 1) return pix1; |
1666 | | |
1667 | 0 | niters = 1; |
1668 | 0 | while (niters < maxiters) { /* continue to add pixels to pix1 */ |
1669 | 0 | niters++; |
1670 | 0 | boxa = pixConnCompBB(pix1, 8); |
1671 | 0 | pix2 = pixCopy(NULL, pix1); |
1672 | 0 | pixMaskBoxa(pix1, pix1, boxa, L_SET_PIXELS); |
1673 | 0 | boxaDestroy(&boxa); |
1674 | 0 | pixEqual(pix1, pix2, &same); |
1675 | 0 | pixDestroy(&pix2); |
1676 | 0 | if (same) { |
1677 | 0 | L_INFO("%d iterations\n", __func__, niters - 1); |
1678 | 0 | return pix1; |
1679 | 0 | } |
1680 | 0 | } |
1681 | 0 | L_INFO("maxiters = %d reached\n", __func__, niters); |
1682 | 0 | return pix1; |
1683 | 0 | } |
1684 | | |
1685 | | |
1686 | | /*---------------------------------------------------------------------* |
1687 | | * Fraction of Fg pixels under a mask * |
1688 | | *---------------------------------------------------------------------*/ |
1689 | | /*! |
1690 | | * \brief pixFractionFgInMask() |
1691 | | * |
1692 | | * \param[in] pix1 1 bpp |
1693 | | * \param[in] pix2 1 bpp |
1694 | | * \param[out] pfract fraction of fg pixels in 1 that are |
1695 | | * aligned with the fg of 2 |
1696 | | * \return 0 if OK, 1 on error. |
1697 | | * |
1698 | | * <pre> |
1699 | | * Notes: |
1700 | | * (1) This gives the fraction of fg pixels in pix1 that are in |
1701 | | * the intersection (i.e., under the fg) of pix2: |
1702 | | * |1 & 2|/|1|, where |...| means the number of fg pixels. |
1703 | | * Note that this is different from the situation where |
1704 | | * pix1 and pix2 are reversed. |
1705 | | * (2) Both pix1 and pix2 are registered to the UL corners. A warning |
1706 | | * is issued if pix1 and pix2 have different sizes. |
1707 | | * (3) This can also be used to find the fraction of fg pixels in pix1 |
1708 | | * that are NOT under the fg of pix2: 1.0 - |1 & 2|/|1| |
1709 | | * (4) If pix1 or pix2 are empty, this returns %fract = 0.0. |
1710 | | * (5) For example, pix2 could be a frame around the outside of the |
1711 | | * image, made from pixMakeFrameMask(). |
1712 | | * </pre> |
1713 | | */ |
1714 | | l_ok |
1715 | | pixFractionFgInMask(PIX *pix1, |
1716 | | PIX *pix2, |
1717 | | l_float32 *pfract) |
1718 | 0 | { |
1719 | 0 | l_int32 w1, h1, w2, h2, empty, count1, count3; |
1720 | 0 | PIX *pix3; |
1721 | |
|
1722 | 0 | if (!pfract) |
1723 | 0 | return ERROR_INT("&fract not defined", __func__, 1); |
1724 | 0 | *pfract = 0.0; |
1725 | 0 | if (!pix1 || pixGetDepth(pix1) != 1) |
1726 | 0 | return ERROR_INT("pix1 not defined or not 1 bpp", __func__, 1); |
1727 | 0 | if (!pix2 || pixGetDepth(pix2) != 1) |
1728 | 0 | return ERROR_INT("pix2 not defined or not 1 bpp", __func__, 1); |
1729 | | |
1730 | 0 | pixGetDimensions(pix1, &w1, &h1, NULL); |
1731 | 0 | pixGetDimensions(pix2, &w2, &h2, NULL); |
1732 | 0 | if (w1 != w2 || h1 != h2) { |
1733 | 0 | L_INFO("sizes unequal: (w1,w2) = (%d,%d), (h1,h2) = (%d,%d)\n", |
1734 | 0 | __func__, w1, w2, h1, h2); |
1735 | 0 | } |
1736 | 0 | pixZero(pix1, &empty); |
1737 | 0 | if (empty) return 0; |
1738 | 0 | pixZero(pix2, &empty); |
1739 | 0 | if (empty) return 0; |
1740 | | |
1741 | 0 | pix3 = pixCopy(NULL, pix1); |
1742 | 0 | pixAnd(pix3, pix3, pix2); |
1743 | 0 | pixCountPixels(pix1, &count1, NULL); /* |1| */ |
1744 | 0 | pixCountPixels(pix3, &count3, NULL); /* |1 & 2| */ |
1745 | 0 | *pfract = (l_float32)count3 / (l_float32)count1; |
1746 | 0 | pixDestroy(&pix3); |
1747 | 0 | return 0; |
1748 | 0 | } |
1749 | | |
1750 | | |
1751 | | /*---------------------------------------------------------------------* |
1752 | | * Clip to Foreground * |
1753 | | *---------------------------------------------------------------------*/ |
1754 | | /*! |
1755 | | * \brief pixClipToForeground() |
1756 | | * |
1757 | | * \param[in] pixs 1 bpp |
1758 | | * \param[out] ppixd [optional] clipped pix returned |
1759 | | * \param[out] pbox [optional] bounding box |
1760 | | * \return 0 if OK; 1 on error or if there are no fg pixels |
1761 | | * |
1762 | | * <pre> |
1763 | | * Notes: |
1764 | | * (1) At least one of {&pixd, &box} must be specified. |
1765 | | * (2) If there are no fg pixels, the returned ptrs are null. |
1766 | | * </pre> |
1767 | | */ |
1768 | | l_ok |
1769 | | pixClipToForeground(PIX *pixs, |
1770 | | PIX **ppixd, |
1771 | | BOX **pbox) |
1772 | 231k | { |
1773 | 231k | l_int32 w, h, wpl, nfullwords, extra, i, j; |
1774 | 231k | l_int32 minx, miny, maxx, maxy; |
1775 | 231k | l_uint32 result, mask; |
1776 | 231k | l_uint32 *data, *line; |
1777 | 231k | BOX *box; |
1778 | | |
1779 | 231k | if (ppixd) *ppixd = NULL; |
1780 | 231k | if (pbox) *pbox = NULL; |
1781 | 231k | if (!ppixd && !pbox) |
1782 | 0 | return ERROR_INT("no output requested", __func__, 1); |
1783 | 231k | if (!pixs || (pixGetDepth(pixs) != 1)) |
1784 | 0 | return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1); |
1785 | | |
1786 | 231k | pixGetDimensions(pixs, &w, &h, NULL); |
1787 | 231k | nfullwords = w / 32; |
1788 | 231k | extra = w & 31; |
1789 | 231k | mask = ~rmask32[32 - extra]; |
1790 | 231k | wpl = pixGetWpl(pixs); |
1791 | 231k | data = pixGetData(pixs); |
1792 | | |
1793 | 231k | result = 0; |
1794 | 2.02M | for (i = 0, miny = 0; i < h; i++, miny++) { |
1795 | 2.00M | line = data + i * wpl; |
1796 | 13.6M | for (j = 0; j < nfullwords; j++) |
1797 | 11.6M | result |= line[j]; |
1798 | 2.00M | if (extra) |
1799 | 1.97M | result |= (line[j] & mask); |
1800 | 2.00M | if (result) |
1801 | 216k | break; |
1802 | 2.00M | } |
1803 | 231k | if (miny == h) /* no ON pixels */ |
1804 | 15.0k | return 1; |
1805 | | |
1806 | 216k | result = 0; |
1807 | 1.30M | for (i = h - 1, maxy = h - 1; i >= 0; i--, maxy--) { |
1808 | 1.30M | line = data + i * wpl; |
1809 | 8.18M | for (j = 0; j < nfullwords; j++) |
1810 | 6.88M | result |= line[j]; |
1811 | 1.30M | if (extra) |
1812 | 1.28M | result |= (line[j] & mask); |
1813 | 1.30M | if (result) |
1814 | 216k | break; |
1815 | 1.30M | } |
1816 | | |
1817 | 216k | minx = 0; |
1818 | 19.1M | for (j = 0, minx = 0; j < w; j++, minx++) { |
1819 | 1.32G | for (i = 0; i < h; i++) { |
1820 | 1.30G | line = data + i * wpl; |
1821 | 1.30G | if (GET_DATA_BIT(line, j)) |
1822 | 216k | goto minx_found; |
1823 | 1.30G | } |
1824 | 19.1M | } |
1825 | | |
1826 | 216k | minx_found: |
1827 | 3.44M | for (j = w - 1, maxx = w - 1; j >= 0; j--, maxx--) { |
1828 | 224M | for (i = 0; i < h; i++) { |
1829 | 220M | line = data + i * wpl; |
1830 | 220M | if (GET_DATA_BIT(line, j)) |
1831 | 216k | goto maxx_found; |
1832 | 220M | } |
1833 | 3.44M | } |
1834 | | |
1835 | 216k | maxx_found: |
1836 | 216k | box = boxCreate(minx, miny, maxx - minx + 1, maxy - miny + 1); |
1837 | | |
1838 | 216k | if (ppixd) |
1839 | 214k | *ppixd = pixClipRectangle(pixs, box, NULL); |
1840 | 216k | if (pbox) |
1841 | 33.1k | *pbox = box; |
1842 | 183k | else |
1843 | 183k | boxDestroy(&box); |
1844 | | |
1845 | 216k | return 0; |
1846 | 216k | } |
1847 | | |
1848 | | |
1849 | | /*! |
1850 | | * \brief pixTestClipToForeground() |
1851 | | * |
1852 | | * \param[in] pixs 1 bpp |
1853 | | * \param[out] pcanclip 1 if fg does not extend to all four edges |
1854 | | * \return 0 if OK; 1 on error |
1855 | | * |
1856 | | * <pre> |
1857 | | * Notes: |
1858 | | * (1) This is a lightweight test to determine if a 1 bpp image |
1859 | | * can be further cropped without loss of fg pixels. |
1860 | | * If it cannot, canclip is set to 0. |
1861 | | * (2) It does not test for the existence of any fg pixels. |
1862 | | * If there are no fg pixels, it will return %canclip = 1. |
1863 | | * Check the output of the subsequent call to pixClipToForeground(). |
1864 | | * </pre> |
1865 | | */ |
1866 | | l_ok |
1867 | | pixTestClipToForeground(PIX *pixs, |
1868 | | l_int32 *pcanclip) |
1869 | 137k | { |
1870 | 137k | l_int32 i, j, w, h, wpl, found; |
1871 | 137k | l_uint32 *data, *line; |
1872 | | |
1873 | 137k | if (!pcanclip) |
1874 | 0 | return ERROR_INT("&canclip not defined", __func__, 1); |
1875 | 137k | *pcanclip = 0; |
1876 | 137k | if (!pixs || (pixGetDepth(pixs) != 1)) |
1877 | 0 | return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1); |
1878 | | |
1879 | | /* Check top and bottom raster lines */ |
1880 | 137k | pixGetDimensions(pixs, &w, &h, NULL); |
1881 | 137k | data = pixGetData(pixs); |
1882 | 137k | wpl = pixGetWpl(pixs); |
1883 | 137k | found = FALSE; |
1884 | 6.88M | for (j = 0; found == FALSE && j < w; j++) |
1885 | 6.74M | found = GET_DATA_BIT(data, j); |
1886 | 137k | if (!found) { |
1887 | 56.8k | *pcanclip = 1; |
1888 | 56.8k | return 0; |
1889 | 56.8k | } |
1890 | | |
1891 | 80.3k | line = data + (h - 1) * wpl; |
1892 | 80.3k | found = FALSE; |
1893 | 648k | for (j = 0; found == FALSE && j < w; j++) |
1894 | 568k | found = GET_DATA_BIT(data, j); |
1895 | 80.3k | if (!found) { |
1896 | 0 | *pcanclip = 1; |
1897 | 0 | return 0; |
1898 | 0 | } |
1899 | | |
1900 | | /* Check left and right edges */ |
1901 | 80.3k | found = FALSE; |
1902 | 830k | for (i = 0, line = data; found == FALSE && i < h; line += wpl, i++) |
1903 | 749k | found = GET_DATA_BIT(line, 0); |
1904 | 80.3k | if (!found) { |
1905 | 3.79k | *pcanclip = 1; |
1906 | 3.79k | return 0; |
1907 | 3.79k | } |
1908 | | |
1909 | 76.5k | found = FALSE; |
1910 | 721k | for (i = 0, line = data; found == FALSE && i < h; line += wpl, i++) |
1911 | 645k | found = GET_DATA_BIT(line, w - 1); |
1912 | 76.5k | if (!found) |
1913 | 103 | *pcanclip = 1; |
1914 | | |
1915 | 76.5k | return 0; /* fg pixels found on all edges */ |
1916 | 80.3k | } |
1917 | | |
1918 | | |
1919 | | /*! |
1920 | | * \brief pixClipBoxToForeground() |
1921 | | * |
1922 | | * \param[in] pixs 1 bpp |
1923 | | * \param[in] boxs [optional] use full image if null |
1924 | | * \param[out] ppixd [optional] clipped pix returned |
1925 | | * \param[out] pboxd [optional] bounding box |
1926 | | * \return 0 if OK; 1 on error or if there are no fg pixels |
1927 | | * |
1928 | | * <pre> |
1929 | | * Notes: |
1930 | | * (1) At least one of {&pixd, &boxd} must be specified. |
1931 | | * (2) If there are no fg pixels, the returned ptrs are null. |
1932 | | * (3) Do not use &pixs for the 3rd arg or &boxs for the 4th arg; |
1933 | | * this will leak memory. |
1934 | | * </pre> |
1935 | | */ |
1936 | | l_ok |
1937 | | pixClipBoxToForeground(PIX *pixs, |
1938 | | BOX *boxs, |
1939 | | PIX **ppixd, |
1940 | | BOX **pboxd) |
1941 | 0 | { |
1942 | 0 | l_int32 w, h, bx, by, bw, bh, cbw, cbh, left, right, top, bottom; |
1943 | 0 | BOX *boxt, *boxd; |
1944 | |
|
1945 | 0 | if (ppixd) *ppixd = NULL; |
1946 | 0 | if (pboxd) *pboxd = NULL; |
1947 | 0 | if (!ppixd && !pboxd) |
1948 | 0 | return ERROR_INT("no output requested", __func__, 1); |
1949 | 0 | if (!pixs || (pixGetDepth(pixs) != 1)) |
1950 | 0 | return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1); |
1951 | | |
1952 | 0 | if (!boxs) |
1953 | 0 | return pixClipToForeground(pixs, ppixd, pboxd); |
1954 | | |
1955 | 0 | pixGetDimensions(pixs, &w, &h, NULL); |
1956 | 0 | boxGetGeometry(boxs, &bx, &by, &bw, &bh); |
1957 | 0 | cbw = L_MIN(bw, w - bx); |
1958 | 0 | cbh = L_MIN(bh, h - by); |
1959 | 0 | if (cbw < 0 || cbh < 0) |
1960 | 0 | return ERROR_INT("box not within image", __func__, 1); |
1961 | 0 | boxt = boxCreate(bx, by, cbw, cbh); |
1962 | |
|
1963 | 0 | if (pixScanForForeground(pixs, boxt, L_FROM_LEFT, &left)) { |
1964 | 0 | boxDestroy(&boxt); |
1965 | 0 | return 1; |
1966 | 0 | } |
1967 | 0 | pixScanForForeground(pixs, boxt, L_FROM_RIGHT, &right); |
1968 | 0 | pixScanForForeground(pixs, boxt, L_FROM_TOP, &top); |
1969 | 0 | pixScanForForeground(pixs, boxt, L_FROM_BOT, &bottom); |
1970 | |
|
1971 | 0 | boxd = boxCreate(left, top, right - left + 1, bottom - top + 1); |
1972 | 0 | if (ppixd) |
1973 | 0 | *ppixd = pixClipRectangle(pixs, boxd, NULL); |
1974 | 0 | if (pboxd) |
1975 | 0 | *pboxd = boxd; |
1976 | 0 | else |
1977 | 0 | boxDestroy(&boxd); |
1978 | |
|
1979 | 0 | boxDestroy(&boxt); |
1980 | 0 | return 0; |
1981 | 0 | } |
1982 | | |
1983 | | |
1984 | | /*! |
1985 | | * \brief pixScanForForeground() |
1986 | | * |
1987 | | * \param[in] pixs 1 bpp |
1988 | | * \param[in] box [optional] within which the search is conducted |
1989 | | * \param[in] scanflag direction of scan; e.g., L_FROM_LEFT |
1990 | | * \param[out] ploc location in scan direction of first black pixel |
1991 | | * \return 0 if OK; 1 on error or if no fg pixels are found |
1992 | | * |
1993 | | * <pre> |
1994 | | * Notes: |
1995 | | * (1) If there are no fg pixels, the position is set to 0. |
1996 | | * Caller must check the return value! |
1997 | | * (2) Use %box == NULL to scan from edge of pixs |
1998 | | * </pre> |
1999 | | */ |
2000 | | l_ok |
2001 | | pixScanForForeground(PIX *pixs, |
2002 | | BOX *box, |
2003 | | l_int32 scanflag, |
2004 | | l_int32 *ploc) |
2005 | 0 | { |
2006 | 0 | l_int32 bx, by, bw, bh, x, xstart, xend, y, ystart, yend, wpl; |
2007 | 0 | l_uint32 *data, *line; |
2008 | 0 | BOX *boxt; |
2009 | |
|
2010 | 0 | if (!ploc) |
2011 | 0 | return ERROR_INT("&loc not defined", __func__, 1); |
2012 | 0 | *ploc = 0; |
2013 | 0 | if (!pixs || (pixGetDepth(pixs) != 1)) |
2014 | 0 | return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1); |
2015 | | |
2016 | | /* Clip box to pixs if it exists */ |
2017 | 0 | pixGetDimensions(pixs, &bw, &bh, NULL); |
2018 | 0 | if (box) { |
2019 | 0 | if ((boxt = boxClipToRectangle(box, bw, bh)) == NULL) |
2020 | 0 | return ERROR_INT("invalid box", __func__, 1); |
2021 | 0 | boxGetGeometry(boxt, &bx, &by, &bw, &bh); |
2022 | 0 | boxDestroy(&boxt); |
2023 | 0 | } else { |
2024 | 0 | bx = by = 0; |
2025 | 0 | } |
2026 | 0 | xstart = bx; |
2027 | 0 | ystart = by; |
2028 | 0 | xend = bx + bw - 1; |
2029 | 0 | yend = by + bh - 1; |
2030 | |
|
2031 | 0 | data = pixGetData(pixs); |
2032 | 0 | wpl = pixGetWpl(pixs); |
2033 | 0 | if (scanflag == L_FROM_LEFT) { |
2034 | 0 | for (x = xstart; x <= xend; x++) { |
2035 | 0 | for (y = ystart; y <= yend; y++) { |
2036 | 0 | line = data + y * wpl; |
2037 | 0 | if (GET_DATA_BIT(line, x)) { |
2038 | 0 | *ploc = x; |
2039 | 0 | return 0; |
2040 | 0 | } |
2041 | 0 | } |
2042 | 0 | } |
2043 | 0 | } else if (scanflag == L_FROM_RIGHT) { |
2044 | 0 | for (x = xend; x >= xstart; x--) { |
2045 | 0 | for (y = ystart; y <= yend; y++) { |
2046 | 0 | line = data + y * wpl; |
2047 | 0 | if (GET_DATA_BIT(line, x)) { |
2048 | 0 | *ploc = x; |
2049 | 0 | return 0; |
2050 | 0 | } |
2051 | 0 | } |
2052 | 0 | } |
2053 | 0 | } else if (scanflag == L_FROM_TOP) { |
2054 | 0 | for (y = ystart; y <= yend; y++) { |
2055 | 0 | line = data + y * wpl; |
2056 | 0 | for (x = xstart; x <= xend; x++) { |
2057 | 0 | if (GET_DATA_BIT(line, x)) { |
2058 | 0 | *ploc = y; |
2059 | 0 | return 0; |
2060 | 0 | } |
2061 | 0 | } |
2062 | 0 | } |
2063 | 0 | } else if (scanflag == L_FROM_BOT) { |
2064 | 0 | for (y = yend; y >= ystart; y--) { |
2065 | 0 | line = data + y * wpl; |
2066 | 0 | for (x = xstart; x <= xend; x++) { |
2067 | 0 | if (GET_DATA_BIT(line, x)) { |
2068 | 0 | *ploc = y; |
2069 | 0 | return 0; |
2070 | 0 | } |
2071 | 0 | } |
2072 | 0 | } |
2073 | 0 | } else { |
2074 | 0 | return ERROR_INT("invalid scanflag", __func__, 1); |
2075 | 0 | } |
2076 | | |
2077 | 0 | return 1; /* no fg found */ |
2078 | 0 | } |
2079 | | |
2080 | | |
2081 | | /*! |
2082 | | * \brief pixClipBoxToEdges() |
2083 | | * |
2084 | | * \param[in] pixs 1 bpp |
2085 | | * \param[in] boxs [optional] ; use full image if null |
2086 | | * \param[in] lowthresh threshold to choose clipping location |
2087 | | * \param[in] highthresh threshold required to find an edge |
2088 | | * \param[in] maxwidth max allowed width between low and high thresh locs |
2089 | | * \param[in] factor sampling factor along pixel counting direction |
2090 | | * \param[out] ppixd [optional] clipped pix returned |
2091 | | * \param[out] pboxd [optional] bounding box |
2092 | | * \return 0 if OK; 1 on error or if a fg edge is not found from |
2093 | | * all four sides. |
2094 | | * |
2095 | | * <pre> |
2096 | | * Notes: |
2097 | | * (1) At least one of {&pixd, &boxd} must be specified. |
2098 | | * (2) If there are no fg pixels, the returned ptrs are null. |
2099 | | * (3) This function attempts to locate rectangular "image" regions |
2100 | | * of high-density fg pixels, that have well-defined edges |
2101 | | * on the four sides. |
2102 | | * (4) Edges are searched for on each side, iterating in order |
2103 | | * from left, right, top and bottom. As each new edge is |
2104 | | * found, the search box is resized to use that location. |
2105 | | * Once an edge is found, it is held. If no more edges |
2106 | | * are found in one iteration, the search fails. |
2107 | | * (5) See pixScanForEdge() for usage of the thresholds and %maxwidth. |
2108 | | * (6) The thresholds must be at least 1, and the low threshold |
2109 | | * cannot be larger than the high threshold. |
2110 | | * (7) If the low and high thresholds are both 1, this is equivalent |
2111 | | * to pixClipBoxToForeground(). |
2112 | | * </pre> |
2113 | | */ |
2114 | | l_ok |
2115 | | pixClipBoxToEdges(PIX *pixs, |
2116 | | BOX *boxs, |
2117 | | l_int32 lowthresh, |
2118 | | l_int32 highthresh, |
2119 | | l_int32 maxwidth, |
2120 | | l_int32 factor, |
2121 | | PIX **ppixd, |
2122 | | BOX **pboxd) |
2123 | 0 | { |
2124 | 0 | l_int32 w, h, bx, by, bw, bh, cbw, cbh, left, right, top, bottom; |
2125 | 0 | l_int32 lfound, rfound, tfound, bfound, change; |
2126 | 0 | BOX *boxt, *boxd; |
2127 | |
|
2128 | 0 | if (ppixd) *ppixd = NULL; |
2129 | 0 | if (pboxd) *pboxd = NULL; |
2130 | 0 | if (!ppixd && !pboxd) |
2131 | 0 | return ERROR_INT("no output requested", __func__, 1); |
2132 | 0 | if (!pixs || (pixGetDepth(pixs) != 1)) |
2133 | 0 | return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1); |
2134 | 0 | if (lowthresh < 1 || highthresh < 1 || |
2135 | 0 | lowthresh > highthresh || maxwidth < 1) |
2136 | 0 | return ERROR_INT("invalid thresholds", __func__, 1); |
2137 | 0 | factor = L_MIN(1, factor); |
2138 | |
|
2139 | 0 | if (lowthresh == 1 && highthresh == 1) |
2140 | 0 | return pixClipBoxToForeground(pixs, boxs, ppixd, pboxd); |
2141 | | |
2142 | 0 | pixGetDimensions(pixs, &w, &h, NULL); |
2143 | 0 | if (boxs) { |
2144 | 0 | boxGetGeometry(boxs, &bx, &by, &bw, &bh); |
2145 | 0 | cbw = L_MIN(bw, w - bx); |
2146 | 0 | cbh = L_MIN(bh, h - by); |
2147 | 0 | if (cbw < 0 || cbh < 0) |
2148 | 0 | return ERROR_INT("box not within image", __func__, 1); |
2149 | 0 | boxt = boxCreate(bx, by, cbw, cbh); |
2150 | 0 | } else { |
2151 | 0 | boxt = boxCreate(0, 0, w, h); |
2152 | 0 | } |
2153 | | |
2154 | 0 | lfound = rfound = tfound = bfound = 0; |
2155 | 0 | while (!lfound || !rfound || !tfound || !bfound) { |
2156 | 0 | change = 0; |
2157 | 0 | if (!lfound) { |
2158 | 0 | if (!pixScanForEdge(pixs, boxt, lowthresh, highthresh, maxwidth, |
2159 | 0 | factor, L_FROM_LEFT, &left)) { |
2160 | 0 | lfound = 1; |
2161 | 0 | change = 1; |
2162 | 0 | boxRelocateOneSide(boxt, boxt, left, L_FROM_LEFT); |
2163 | 0 | } |
2164 | 0 | } |
2165 | 0 | if (!rfound) { |
2166 | 0 | if (!pixScanForEdge(pixs, boxt, lowthresh, highthresh, maxwidth, |
2167 | 0 | factor, L_FROM_RIGHT, &right)) { |
2168 | 0 | rfound = 1; |
2169 | 0 | change = 1; |
2170 | 0 | boxRelocateOneSide(boxt, boxt, right, L_FROM_RIGHT); |
2171 | 0 | } |
2172 | 0 | } |
2173 | 0 | if (!tfound) { |
2174 | 0 | if (!pixScanForEdge(pixs, boxt, lowthresh, highthresh, maxwidth, |
2175 | 0 | factor, L_FROM_TOP, &top)) { |
2176 | 0 | tfound = 1; |
2177 | 0 | change = 1; |
2178 | 0 | boxRelocateOneSide(boxt, boxt, top, L_FROM_TOP); |
2179 | 0 | } |
2180 | 0 | } |
2181 | 0 | if (!bfound) { |
2182 | 0 | if (!pixScanForEdge(pixs, boxt, lowthresh, highthresh, maxwidth, |
2183 | 0 | factor, L_FROM_BOT, &bottom)) { |
2184 | 0 | bfound = 1; |
2185 | 0 | change = 1; |
2186 | 0 | boxRelocateOneSide(boxt, boxt, bottom, L_FROM_BOT); |
2187 | 0 | } |
2188 | 0 | } |
2189 | |
|
2190 | | #if DEBUG_EDGES |
2191 | | lept_stderr("iter: %d %d %d %d\n", lfound, rfound, tfound, bfound); |
2192 | | #endif /* DEBUG_EDGES */ |
2193 | |
|
2194 | 0 | if (change == 0) break; |
2195 | 0 | } |
2196 | 0 | boxDestroy(&boxt); |
2197 | |
|
2198 | 0 | if (change == 0) |
2199 | 0 | return ERROR_INT("not all edges found", __func__, 1); |
2200 | | |
2201 | 0 | boxd = boxCreate(left, top, right - left + 1, bottom - top + 1); |
2202 | 0 | if (ppixd) |
2203 | 0 | *ppixd = pixClipRectangle(pixs, boxd, NULL); |
2204 | 0 | if (pboxd) |
2205 | 0 | *pboxd = boxd; |
2206 | 0 | else |
2207 | 0 | boxDestroy(&boxd); |
2208 | |
|
2209 | 0 | return 0; |
2210 | 0 | } |
2211 | | |
2212 | | |
2213 | | /*! |
2214 | | * \brief pixScanForEdge() |
2215 | | * |
2216 | | * \param[in] pixs 1 bpp |
2217 | | * \param[in] box [optional] within which the search is conducted |
2218 | | * \param[in] lowthresh threshold to choose clipping location |
2219 | | * \param[in] highthresh threshold required to find an edge |
2220 | | * \param[in] maxwidth max allowed width between low and high thresh locs |
2221 | | * \param[in] factor sampling factor along pixel counting direction |
2222 | | * \param[in] scanflag direction of scan; e.g., L_FROM_LEFT |
2223 | | * \param[out] ploc location in scan direction of first black pixel |
2224 | | * \return 0 if OK; 1 on error or if the edge is not found |
2225 | | * |
2226 | | * <pre> |
2227 | | * Notes: |
2228 | | * (1) If there are no fg pixels, the position is set to 0. |
2229 | | * Caller must check the return value! |
2230 | | * (2) Use %box == NULL to scan from edge of pixs |
2231 | | * (3) As the scan progresses, the location where the sum of |
2232 | | * pixels equals or excees %lowthresh is noted (loc). The |
2233 | | * scan is stopped when the sum of pixels equals or exceeds |
2234 | | * %highthresh. If the scan distance between loc and that |
2235 | | * point does not exceed %maxwidth, an edge is found and |
2236 | | * its position is taken to be loc. %maxwidth implicitly |
2237 | | * sets a minimum on the required gradient of the edge. |
2238 | | * (4) The thresholds must be at least 1, and the low threshold |
2239 | | * cannot be larger than the high threshold. |
2240 | | * </pre> |
2241 | | */ |
2242 | | l_ok |
2243 | | pixScanForEdge(PIX *pixs, |
2244 | | BOX *box, |
2245 | | l_int32 lowthresh, |
2246 | | l_int32 highthresh, |
2247 | | l_int32 maxwidth, |
2248 | | l_int32 factor, |
2249 | | l_int32 scanflag, |
2250 | | l_int32 *ploc) |
2251 | 0 | { |
2252 | 0 | l_int32 bx, by, bw, bh, foundmin, loc, sum, wpl; |
2253 | 0 | l_int32 x, xstart, xend, y, ystart, yend; |
2254 | 0 | l_uint32 *data, *line; |
2255 | 0 | BOX *boxt; |
2256 | |
|
2257 | 0 | if (!ploc) |
2258 | 0 | return ERROR_INT("&ploc not defined", __func__, 1); |
2259 | 0 | *ploc = 0; |
2260 | 0 | if (!pixs || (pixGetDepth(pixs) != 1)) |
2261 | 0 | return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1); |
2262 | 0 | if (lowthresh < 1 || highthresh < 1 || |
2263 | 0 | lowthresh > highthresh || maxwidth < 1) |
2264 | 0 | return ERROR_INT("invalid thresholds", __func__, 1); |
2265 | 0 | factor = L_MIN(1, factor); |
2266 | | |
2267 | | /* Clip box to pixs if it exists */ |
2268 | 0 | pixGetDimensions(pixs, &bw, &bh, NULL); |
2269 | 0 | if (box) { |
2270 | 0 | if ((boxt = boxClipToRectangle(box, bw, bh)) == NULL) |
2271 | 0 | return ERROR_INT("invalid box", __func__, 1); |
2272 | 0 | boxGetGeometry(boxt, &bx, &by, &bw, &bh); |
2273 | 0 | boxDestroy(&boxt); |
2274 | 0 | } else { |
2275 | 0 | bx = by = 0; |
2276 | 0 | } |
2277 | 0 | xstart = bx; |
2278 | 0 | ystart = by; |
2279 | 0 | xend = bx + bw - 1; |
2280 | 0 | yend = by + bh - 1; |
2281 | |
|
2282 | 0 | data = pixGetData(pixs); |
2283 | 0 | wpl = pixGetWpl(pixs); |
2284 | 0 | foundmin = 0; |
2285 | 0 | if (scanflag == L_FROM_LEFT) { |
2286 | 0 | for (x = xstart; x <= xend; x++) { |
2287 | 0 | sum = 0; |
2288 | 0 | for (y = ystart; y <= yend; y += factor) { |
2289 | 0 | line = data + y * wpl; |
2290 | 0 | if (GET_DATA_BIT(line, x)) |
2291 | 0 | sum++; |
2292 | 0 | } |
2293 | 0 | if (!foundmin && sum < lowthresh) |
2294 | 0 | continue; |
2295 | 0 | if (!foundmin) { /* save the loc of the beginning of the edge */ |
2296 | 0 | foundmin = 1; |
2297 | 0 | loc = x; |
2298 | 0 | } |
2299 | 0 | if (sum >= highthresh) { |
2300 | | #if DEBUG_EDGES |
2301 | | lept_stderr("Left: x = %d, loc = %d\n", x, loc); |
2302 | | #endif /* DEBUG_EDGES */ |
2303 | 0 | if (x - loc < maxwidth) { |
2304 | 0 | *ploc = loc; |
2305 | 0 | return 0; |
2306 | 0 | } else { |
2307 | 0 | return 1; |
2308 | 0 | } |
2309 | 0 | } |
2310 | 0 | } |
2311 | 0 | } else if (scanflag == L_FROM_RIGHT) { |
2312 | 0 | for (x = xend; x >= xstart; x--) { |
2313 | 0 | sum = 0; |
2314 | 0 | for (y = ystart; y <= yend; y += factor) { |
2315 | 0 | line = data + y * wpl; |
2316 | 0 | if (GET_DATA_BIT(line, x)) |
2317 | 0 | sum++; |
2318 | 0 | } |
2319 | 0 | if (!foundmin && sum < lowthresh) |
2320 | 0 | continue; |
2321 | 0 | if (!foundmin) { |
2322 | 0 | foundmin = 1; |
2323 | 0 | loc = x; |
2324 | 0 | } |
2325 | 0 | if (sum >= highthresh) { |
2326 | | #if DEBUG_EDGES |
2327 | | lept_stderr("Right: x = %d, loc = %d\n", x, loc); |
2328 | | #endif /* DEBUG_EDGES */ |
2329 | 0 | if (loc - x < maxwidth) { |
2330 | 0 | *ploc = loc; |
2331 | 0 | return 0; |
2332 | 0 | } else { |
2333 | 0 | return 1; |
2334 | 0 | } |
2335 | 0 | } |
2336 | 0 | } |
2337 | 0 | } else if (scanflag == L_FROM_TOP) { |
2338 | 0 | for (y = ystart; y <= yend; y++) { |
2339 | 0 | sum = 0; |
2340 | 0 | line = data + y * wpl; |
2341 | 0 | for (x = xstart; x <= xend; x += factor) { |
2342 | 0 | if (GET_DATA_BIT(line, x)) |
2343 | 0 | sum++; |
2344 | 0 | } |
2345 | 0 | if (!foundmin && sum < lowthresh) |
2346 | 0 | continue; |
2347 | 0 | if (!foundmin) { |
2348 | 0 | foundmin = 1; |
2349 | 0 | loc = y; |
2350 | 0 | } |
2351 | 0 | if (sum >= highthresh) { |
2352 | | #if DEBUG_EDGES |
2353 | | lept_stderr("Top: y = %d, loc = %d\n", y, loc); |
2354 | | #endif /* DEBUG_EDGES */ |
2355 | 0 | if (y - loc < maxwidth) { |
2356 | 0 | *ploc = loc; |
2357 | 0 | return 0; |
2358 | 0 | } else { |
2359 | 0 | return 1; |
2360 | 0 | } |
2361 | 0 | } |
2362 | 0 | } |
2363 | 0 | } else if (scanflag == L_FROM_BOT) { |
2364 | 0 | for (y = yend; y >= ystart; y--) { |
2365 | 0 | sum = 0; |
2366 | 0 | line = data + y * wpl; |
2367 | 0 | for (x = xstart; x <= xend; x += factor) { |
2368 | 0 | if (GET_DATA_BIT(line, x)) |
2369 | 0 | sum++; |
2370 | 0 | } |
2371 | 0 | if (!foundmin && sum < lowthresh) |
2372 | 0 | continue; |
2373 | 0 | if (!foundmin) { |
2374 | 0 | foundmin = 1; |
2375 | 0 | loc = y; |
2376 | 0 | } |
2377 | 0 | if (sum >= highthresh) { |
2378 | | #if DEBUG_EDGES |
2379 | | lept_stderr("Bottom: y = %d, loc = %d\n", y, loc); |
2380 | | #endif /* DEBUG_EDGES */ |
2381 | 0 | if (loc - y < maxwidth) { |
2382 | 0 | *ploc = loc; |
2383 | 0 | return 0; |
2384 | 0 | } else { |
2385 | 0 | return 1; |
2386 | 0 | } |
2387 | 0 | } |
2388 | 0 | } |
2389 | 0 | } else { |
2390 | 0 | return ERROR_INT("invalid scanflag", __func__, 1); |
2391 | 0 | } |
2392 | | |
2393 | 0 | return 1; /* edge not found */ |
2394 | 0 | } |
2395 | | |
2396 | | |
2397 | | /*---------------------------------------------------------------------* |
2398 | | * Extract pixel averages and reversals along lines * |
2399 | | *---------------------------------------------------------------------*/ |
2400 | | /*! |
2401 | | * \brief pixExtractOnLine() |
2402 | | * |
2403 | | * \param[in] pixs 1 bpp or 8 bpp; no colormap |
2404 | | * \param[in] x1, y1 one end point for line |
2405 | | * \param[in] x2, y2 another end pt for line |
2406 | | * \param[in] factor sampling; >= 1 |
2407 | | * \return na of pixel values along line, or NULL on error. |
2408 | | * |
2409 | | * <pre> |
2410 | | * Notes: |
2411 | | * (1) Input end points are clipped to the pix. |
2412 | | * (2) If the line is either horizontal, or closer to horizontal |
2413 | | * than to vertical, the points will be extracted from left |
2414 | | * to right in the pix. Likewise, if the line is vertical, |
2415 | | * or closer to vertical than to horizontal, the points will |
2416 | | * be extracted from top to bottom. |
2417 | | * (3) Can be used with numaCountReverals(), for example, to |
2418 | | * characterize the intensity smoothness along a line. |
2419 | | * </pre> |
2420 | | */ |
2421 | | NUMA * |
2422 | | pixExtractOnLine(PIX *pixs, |
2423 | | l_int32 x1, |
2424 | | l_int32 y1, |
2425 | | l_int32 x2, |
2426 | | l_int32 y2, |
2427 | | l_int32 factor) |
2428 | 0 | { |
2429 | 0 | l_int32 i, w, h, d, xmin, ymin, xmax, ymax, npts, direction; |
2430 | 0 | l_uint32 val; |
2431 | 0 | l_float32 x, y; |
2432 | 0 | l_float64 slope; |
2433 | 0 | NUMA *na; |
2434 | 0 | PTA *pta; |
2435 | |
|
2436 | 0 | if (!pixs) |
2437 | 0 | return (NUMA *)ERROR_PTR("pixs not defined", __func__, NULL); |
2438 | 0 | pixGetDimensions(pixs, &w, &h, &d); |
2439 | 0 | if (d != 1 && d != 8) |
2440 | 0 | return (NUMA *)ERROR_PTR("d not 1 or 8 bpp", __func__, NULL); |
2441 | 0 | if (pixGetColormap(pixs)) |
2442 | 0 | return (NUMA *)ERROR_PTR("pixs has a colormap", __func__, NULL); |
2443 | 0 | if (factor < 1) { |
2444 | 0 | L_WARNING("factor must be >= 1; setting to 1\n", __func__); |
2445 | 0 | factor = 1; |
2446 | 0 | } |
2447 | | |
2448 | | /* Clip line to the image */ |
2449 | 0 | x1 = L_MAX(0, L_MIN(x1, w - 1)); |
2450 | 0 | x2 = L_MAX(0, L_MIN(x2, w - 1)); |
2451 | 0 | y1 = L_MAX(0, L_MIN(y1, h - 1)); |
2452 | 0 | y2 = L_MAX(0, L_MIN(y2, h - 1)); |
2453 | |
|
2454 | 0 | if (x1 == x2 && y1 == y2) { |
2455 | 0 | pixGetPixel(pixs, x1, y1, &val); |
2456 | 0 | na = numaCreate(1); |
2457 | 0 | numaAddNumber(na, val); |
2458 | 0 | return na; |
2459 | 0 | } |
2460 | | |
2461 | 0 | if (y1 == y2) |
2462 | 0 | direction = L_HORIZONTAL_LINE; |
2463 | 0 | else if (x1 == x2) |
2464 | 0 | direction = L_VERTICAL_LINE; |
2465 | 0 | else |
2466 | 0 | direction = L_OBLIQUE_LINE; |
2467 | |
|
2468 | 0 | na = numaCreate(0); |
2469 | 0 | if (direction == L_HORIZONTAL_LINE) { /* plot against x */ |
2470 | 0 | xmin = L_MIN(x1, x2); |
2471 | 0 | xmax = L_MAX(x1, x2); |
2472 | 0 | numaSetParameters(na, xmin, factor); |
2473 | 0 | for (i = xmin; i <= xmax; i += factor) { |
2474 | 0 | pixGetPixel(pixs, i, y1, &val); |
2475 | 0 | numaAddNumber(na, val); |
2476 | 0 | } |
2477 | 0 | } else if (direction == L_VERTICAL_LINE) { /* plot against y */ |
2478 | 0 | ymin = L_MIN(y1, y2); |
2479 | 0 | ymax = L_MAX(y1, y2); |
2480 | 0 | numaSetParameters(na, ymin, factor); |
2481 | 0 | for (i = ymin; i <= ymax; i += factor) { |
2482 | 0 | pixGetPixel(pixs, x1, i, &val); |
2483 | 0 | numaAddNumber(na, val); |
2484 | 0 | } |
2485 | 0 | } else { /* direction == L_OBLIQUE_LINE */ |
2486 | 0 | slope = (l_float64)((y2 - y1) / (x2 - x1)); |
2487 | 0 | if (L_ABS(slope) < 1.0) { /* quasi-horizontal */ |
2488 | 0 | xmin = L_MIN(x1, x2); |
2489 | 0 | xmax = L_MAX(x1, x2); |
2490 | 0 | ymin = (xmin == x1) ? y1 : y2; /* pt that goes with xmin */ |
2491 | 0 | ymax = (ymin == y1) ? y2 : y1; /* pt that goes with xmax */ |
2492 | 0 | pta = generatePtaLine(xmin, ymin, xmax, ymax); |
2493 | 0 | numaSetParameters(na, xmin, (l_float32)factor); |
2494 | 0 | } else { /* quasi-vertical */ |
2495 | 0 | ymin = L_MIN(y1, y2); |
2496 | 0 | ymax = L_MAX(y1, y2); |
2497 | 0 | xmin = (ymin == y1) ? x1 : x2; /* pt that goes with ymin */ |
2498 | 0 | xmax = (xmin == x1) ? x2 : x1; /* pt that goes with ymax */ |
2499 | 0 | pta = generatePtaLine(xmin, ymin, xmax, ymax); |
2500 | 0 | numaSetParameters(na, ymin, (l_float32)factor); |
2501 | 0 | } |
2502 | 0 | npts = ptaGetCount(pta); |
2503 | 0 | for (i = 0; i < npts; i += factor) { |
2504 | 0 | ptaGetPt(pta, i, &x, &y); |
2505 | 0 | pixGetPixel(pixs, (l_int32)x, (l_int32)y, &val); |
2506 | 0 | numaAddNumber(na, val); |
2507 | 0 | } |
2508 | |
|
2509 | | #if 0 /* debugging */ |
2510 | | pixPlotAlongPta(pixs, pta, GPLOT_PNG, NULL); |
2511 | | #endif |
2512 | |
|
2513 | 0 | ptaDestroy(&pta); |
2514 | 0 | } |
2515 | |
|
2516 | 0 | return na; |
2517 | 0 | } |
2518 | | |
2519 | | |
2520 | | /*! |
2521 | | * \brief pixAverageOnLine() |
2522 | | * |
2523 | | * \param[in] pixs 1 bpp or 8 bpp; no colormap |
2524 | | * \param[in] x1, y1 starting pt for line |
2525 | | * \param[in] x2, y2 end pt for line |
2526 | | * \param[in] factor sampling; >= 1 |
2527 | | * \return average of pixel values along line, or NULL on error. |
2528 | | * |
2529 | | * <pre> |
2530 | | * Notes: |
2531 | | * (1) The line must be either horizontal or vertical, so either |
2532 | | * y1 == y2 (horizontal) or x1 == x2 (vertical). |
2533 | | * (2) If horizontal, x1 must be <= x2. |
2534 | | * If vertical, y1 must be <= y2. |
2535 | | * characterize the intensity smoothness along a line. |
2536 | | * (3) Input end points are clipped to the pix. |
2537 | | * </pre> |
2538 | | */ |
2539 | | l_float32 |
2540 | | pixAverageOnLine(PIX *pixs, |
2541 | | l_int32 x1, |
2542 | | l_int32 y1, |
2543 | | l_int32 x2, |
2544 | | l_int32 y2, |
2545 | | l_int32 factor) |
2546 | 0 | { |
2547 | 0 | l_int32 i, j, w, h, d, direction, count, wpl; |
2548 | 0 | l_uint32 *data, *line; |
2549 | 0 | l_float32 sum; |
2550 | |
|
2551 | 0 | if (!pixs) |
2552 | 0 | return ERROR_INT("pixs not defined", __func__, 1); |
2553 | 0 | pixGetDimensions(pixs, &w, &h, &d); |
2554 | 0 | if (d != 1 && d != 8) |
2555 | 0 | return ERROR_INT("d not 1 or 8 bpp", __func__, 1); |
2556 | 0 | if (pixGetColormap(pixs)) |
2557 | 0 | return ERROR_INT("pixs has a colormap", __func__, 1); |
2558 | 0 | if (x1 > x2 || y1 > y2) |
2559 | 0 | return ERROR_INT("x1 > x2 or y1 > y2", __func__, 1); |
2560 | | |
2561 | 0 | if (y1 == y2) { |
2562 | 0 | x1 = L_MAX(0, x1); |
2563 | 0 | x2 = L_MIN(w - 1, x2); |
2564 | 0 | y1 = L_MAX(0, L_MIN(y1, h - 1)); |
2565 | 0 | direction = L_HORIZONTAL_LINE; |
2566 | 0 | } else if (x1 == x2) { |
2567 | 0 | y1 = L_MAX(0, y1); |
2568 | 0 | y2 = L_MIN(h - 1, y2); |
2569 | 0 | x1 = L_MAX(0, L_MIN(x1, w - 1)); |
2570 | 0 | direction = L_VERTICAL_LINE; |
2571 | 0 | } else { |
2572 | 0 | return ERROR_INT("line neither horiz nor vert", __func__, 1); |
2573 | 0 | } |
2574 | | |
2575 | 0 | if (factor < 1) { |
2576 | 0 | L_WARNING("factor must be >= 1; setting to 1\n", __func__); |
2577 | 0 | factor = 1; |
2578 | 0 | } |
2579 | |
|
2580 | 0 | data = pixGetData(pixs); |
2581 | 0 | wpl = pixGetWpl(pixs); |
2582 | 0 | sum = 0; |
2583 | 0 | count = 0; |
2584 | 0 | if (direction == L_HORIZONTAL_LINE) { |
2585 | 0 | line = data + y1 * wpl; |
2586 | 0 | for (j = x1, count = 0; j <= x2; count++, j += factor) { |
2587 | 0 | if (d == 1) |
2588 | 0 | sum += GET_DATA_BIT(line, j); |
2589 | 0 | else /* d == 8 */ |
2590 | 0 | sum += GET_DATA_BYTE(line, j); |
2591 | 0 | } |
2592 | 0 | } else if (direction == L_VERTICAL_LINE) { |
2593 | 0 | for (i = y1, count = 0; i <= y2; count++, i += factor) { |
2594 | 0 | line = data + i * wpl; |
2595 | 0 | if (d == 1) |
2596 | 0 | sum += GET_DATA_BIT(line, x1); |
2597 | 0 | else /* d == 8 */ |
2598 | 0 | sum += GET_DATA_BYTE(line, x1); |
2599 | 0 | } |
2600 | 0 | } |
2601 | |
|
2602 | 0 | return sum / (l_float32)count; |
2603 | 0 | } |
2604 | | |
2605 | | |
2606 | | /*! |
2607 | | * \brief pixAverageIntensityProfile() |
2608 | | * |
2609 | | * \param[in] pixs any depth; colormap OK |
2610 | | * \param[in] fract fraction of image width or height to be used |
2611 | | * \param[in] dir averaging direction: L_HORIZONTAL_LINE or |
2612 | | * L_VERTICAL_LINE |
2613 | | * \param[in] first, last span of rows or columns to measure |
2614 | | * \param[in] factor1 sampling along fast scan direction; >= 1 |
2615 | | * \param[in] factor2 sampling along slow scan direction; >= 1 |
2616 | | * \return na of reversal profile, or NULL on error. |
2617 | | * |
2618 | | * <pre> |
2619 | | * Notes: |
2620 | | * (1) If d != 1 bpp, colormaps are removed and the result |
2621 | | * is converted to 8 bpp. |
2622 | | * (2) If %dir == L_HORIZONTAL_LINE, the intensity is averaged |
2623 | | * along each horizontal raster line (sampled by %factor1), |
2624 | | * and the profile is the array of these averages in the |
2625 | | * vertical direction between %first and %last raster lines, |
2626 | | * and sampled by %factor2. |
2627 | | * (3) If %dir == L_VERTICAL_LINE, the intensity is averaged |
2628 | | * along each vertical line (sampled by %factor1), |
2629 | | * and the profile is the array of these averages in the |
2630 | | * horizontal direction between %first and %last columns, |
2631 | | * and sampled by %factor2. |
2632 | | * (4) The averages are measured over the central %fract of the image. |
2633 | | * Use %fract == 1.0 to average across the entire width or height. |
2634 | | * </pre> |
2635 | | */ |
2636 | | NUMA * |
2637 | | pixAverageIntensityProfile(PIX *pixs, |
2638 | | l_float32 fract, |
2639 | | l_int32 dir, |
2640 | | l_int32 first, |
2641 | | l_int32 last, |
2642 | | l_int32 factor1, |
2643 | | l_int32 factor2) |
2644 | 0 | { |
2645 | 0 | l_int32 i, j, w, h, d, start, end; |
2646 | 0 | l_float32 ave; |
2647 | 0 | NUMA *nad; |
2648 | 0 | PIX *pixr, *pixg; |
2649 | |
|
2650 | 0 | if (!pixs) |
2651 | 0 | return (NUMA *)ERROR_PTR("pixs not defined", __func__, NULL); |
2652 | 0 | if (fract < 0.0 || fract > 1.0) |
2653 | 0 | return (NUMA *)ERROR_PTR("fract < 0.0 or > 1.0", __func__, NULL); |
2654 | 0 | if (dir != L_HORIZONTAL_LINE && dir != L_VERTICAL_LINE) |
2655 | 0 | return (NUMA *)ERROR_PTR("invalid direction", __func__, NULL); |
2656 | 0 | if (first < 0) first = 0; |
2657 | 0 | if (last < first) |
2658 | 0 | return (NUMA *)ERROR_PTR("last must be >= first", __func__, NULL); |
2659 | 0 | if (factor1 < 1) { |
2660 | 0 | L_WARNING("factor1 must be >= 1; setting to 1\n", __func__); |
2661 | 0 | factor1 = 1; |
2662 | 0 | } |
2663 | 0 | if (factor2 < 1) { |
2664 | 0 | L_WARNING("factor2 must be >= 1; setting to 1\n", __func__); |
2665 | 0 | factor2 = 1; |
2666 | 0 | } |
2667 | | |
2668 | | /* Use 1 or 8 bpp, without colormap */ |
2669 | 0 | if (pixGetColormap(pixs)) |
2670 | 0 | pixr = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE); |
2671 | 0 | else |
2672 | 0 | pixr = pixClone(pixs); |
2673 | 0 | pixGetDimensions(pixr, &w, &h, &d); |
2674 | 0 | if (d == 1) |
2675 | 0 | pixg = pixClone(pixr); |
2676 | 0 | else |
2677 | 0 | pixg = pixConvertTo8(pixr, 0); |
2678 | |
|
2679 | 0 | nad = numaCreate(0); /* output: samples in slow scan direction */ |
2680 | 0 | numaSetParameters(nad, 0, factor2); |
2681 | 0 | if (dir == L_HORIZONTAL_LINE) { |
2682 | 0 | start = (l_int32)(0.5 * (1.0 - fract) * (l_float32)w); |
2683 | 0 | end = w - start; |
2684 | 0 | if (last > h - 1) { |
2685 | 0 | L_WARNING("last > h - 1; clipping\n", __func__); |
2686 | 0 | last = h - 1; |
2687 | 0 | } |
2688 | 0 | for (i = first; i <= last; i += factor2) { |
2689 | 0 | ave = pixAverageOnLine(pixg, start, i, end, i, factor1); |
2690 | 0 | numaAddNumber(nad, ave); |
2691 | 0 | } |
2692 | 0 | } else if (dir == L_VERTICAL_LINE) { |
2693 | 0 | start = (l_int32)(0.5 * (1.0 - fract) * (l_float32)h); |
2694 | 0 | end = h - start; |
2695 | 0 | if (last > w - 1) { |
2696 | 0 | L_WARNING("last > w - 1; clipping\n", __func__); |
2697 | 0 | last = w - 1; |
2698 | 0 | } |
2699 | 0 | for (j = first; j <= last; j += factor2) { |
2700 | 0 | ave = pixAverageOnLine(pixg, j, start, j, end, factor1); |
2701 | 0 | numaAddNumber(nad, ave); |
2702 | 0 | } |
2703 | 0 | } |
2704 | |
|
2705 | 0 | pixDestroy(&pixr); |
2706 | 0 | pixDestroy(&pixg); |
2707 | 0 | return nad; |
2708 | 0 | } |
2709 | | |
2710 | | |
2711 | | /*! |
2712 | | * \brief pixReversalProfile() |
2713 | | * |
2714 | | * \param[in] pixs any depth; colormap OK |
2715 | | * \param[in] fract fraction of image width or height to be used |
2716 | | * \param[in] dir profile direction: L_HORIZONTAL_LINE or |
2717 | | * L_VERTICAL_LINE |
2718 | | * \param[in] first, last span of rows or columns to measure |
2719 | | * \param[in] minreversal minimum change in intensity to trigger a reversal |
2720 | | * \param[in] factor1 sampling along raster line (fast scan); >= 1 |
2721 | | * \param[in] factor2 sampling of raster lines (slow scan); >= 1 |
2722 | | * \return na of reversal profile, or NULL on error. |
2723 | | * |
2724 | | * <pre> |
2725 | | * Notes: |
2726 | | * (1) If d != 1 bpp, colormaps are removed and the result |
2727 | | * is converted to 8 bpp. |
2728 | | * (2) If %dir == L_HORIZONTAL_LINE, the the reversals are counted |
2729 | | * along each horizontal raster line (sampled by %factor1), |
2730 | | * and the profile is the array of these sums in the |
2731 | | * vertical direction between %first and %last raster lines, |
2732 | | * and sampled by %factor2. |
2733 | | * (3) If %dir == L_VERTICAL_LINE, the the reversals are counted |
2734 | | * along each vertical column (sampled by %factor1), |
2735 | | * and the profile is the array of these sums in the |
2736 | | * horizontal direction between %first and %last columns, |
2737 | | * and sampled by %factor2. |
2738 | | * (4) For each row or column, the reversals are summed over the |
2739 | | * central %fract of the image. Use %fract == 1.0 to sum |
2740 | | * across the entire width (of row) or height (of column). |
2741 | | * (5) %minreversal is the relative change in intensity that is |
2742 | | * required to resolve peaks and valleys. A typical number for |
2743 | | * locating text in 8 bpp might be 50. For 1 bpp, minreversal |
2744 | | * must be 1. |
2745 | | * (6) The reversal profile is simply the number of reversals |
2746 | | * in a row or column, vs the row or column index. |
2747 | | * </pre> |
2748 | | */ |
2749 | | NUMA * |
2750 | | pixReversalProfile(PIX *pixs, |
2751 | | l_float32 fract, |
2752 | | l_int32 dir, |
2753 | | l_int32 first, |
2754 | | l_int32 last, |
2755 | | l_int32 minreversal, |
2756 | | l_int32 factor1, |
2757 | | l_int32 factor2) |
2758 | 0 | { |
2759 | 0 | l_int32 i, j, w, h, d, start, end, nr; |
2760 | 0 | NUMA *naline, *nad; |
2761 | 0 | PIX *pixr, *pixg; |
2762 | |
|
2763 | 0 | if (!pixs) |
2764 | 0 | return (NUMA *)ERROR_PTR("pixs not defined", __func__, NULL); |
2765 | 0 | if (fract < 0.0 || fract > 1.0) |
2766 | 0 | return (NUMA *)ERROR_PTR("fract < 0.0 or > 1.0", __func__, NULL); |
2767 | 0 | if (dir != L_HORIZONTAL_LINE && dir != L_VERTICAL_LINE) |
2768 | 0 | return (NUMA *)ERROR_PTR("invalid direction", __func__, NULL); |
2769 | 0 | if (first < 0) first = 0; |
2770 | 0 | if (last < first) |
2771 | 0 | return (NUMA *)ERROR_PTR("last must be >= first", __func__, NULL); |
2772 | 0 | if (factor1 < 1) { |
2773 | 0 | L_WARNING("factor1 must be >= 1; setting to 1\n", __func__); |
2774 | 0 | factor1 = 1; |
2775 | 0 | } |
2776 | 0 | if (factor2 < 1) { |
2777 | 0 | L_WARNING("factor2 must be >= 1; setting to 1\n", __func__); |
2778 | 0 | factor2 = 1; |
2779 | 0 | } |
2780 | | |
2781 | | /* Use 1 or 8 bpp, without colormap */ |
2782 | 0 | if (pixGetColormap(pixs)) |
2783 | 0 | pixr = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE); |
2784 | 0 | else |
2785 | 0 | pixr = pixClone(pixs); |
2786 | 0 | pixGetDimensions(pixr, &w, &h, &d); |
2787 | 0 | if (d == 1) { |
2788 | 0 | pixg = pixClone(pixr); |
2789 | 0 | minreversal = 1; /* enforce this */ |
2790 | 0 | } else { |
2791 | 0 | pixg = pixConvertTo8(pixr, 0); |
2792 | 0 | } |
2793 | |
|
2794 | 0 | nad = numaCreate(0); /* output: samples in slow scan direction */ |
2795 | 0 | numaSetParameters(nad, 0, factor2); |
2796 | 0 | if (dir == L_HORIZONTAL_LINE) { |
2797 | 0 | start = (l_int32)(0.5 * (1.0 - fract) * (l_float32)w); |
2798 | 0 | end = w - start; |
2799 | 0 | if (last > h - 1) { |
2800 | 0 | L_WARNING("last > h - 1; clipping\n", __func__); |
2801 | 0 | last = h - 1; |
2802 | 0 | } |
2803 | 0 | for (i = first; i <= last; i += factor2) { |
2804 | 0 | naline = pixExtractOnLine(pixg, start, i, end, i, factor1); |
2805 | 0 | numaCountReversals(naline, minreversal, &nr, NULL); |
2806 | 0 | numaAddNumber(nad, nr); |
2807 | 0 | numaDestroy(&naline); |
2808 | 0 | } |
2809 | 0 | } else if (dir == L_VERTICAL_LINE) { |
2810 | 0 | start = (l_int32)(0.5 * (1.0 - fract) * (l_float32)h); |
2811 | 0 | end = h - start; |
2812 | 0 | if (last > w - 1) { |
2813 | 0 | L_WARNING("last > w - 1; clipping\n", __func__); |
2814 | 0 | last = w - 1; |
2815 | 0 | } |
2816 | 0 | for (j = first; j <= last; j += factor2) { |
2817 | 0 | naline = pixExtractOnLine(pixg, j, start, j, end, factor1); |
2818 | 0 | numaCountReversals(naline, minreversal, &nr, NULL); |
2819 | 0 | numaAddNumber(nad, nr); |
2820 | 0 | numaDestroy(&naline); |
2821 | 0 | } |
2822 | 0 | } |
2823 | |
|
2824 | 0 | pixDestroy(&pixr); |
2825 | 0 | pixDestroy(&pixg); |
2826 | 0 | return nad; |
2827 | 0 | } |
2828 | | |
2829 | | |
2830 | | /*---------------------------------------------------------------------* |
2831 | | * Extract windowed variance along a line * |
2832 | | *---------------------------------------------------------------------*/ |
2833 | | /*! |
2834 | | * \brief pixWindowedVarianceOnLine() |
2835 | | * |
2836 | | * \param[in] pixs 8 bpp; no colormap |
2837 | | * \param[in] dir L_HORIZONTAL_LINE or L_VERTICAL_LINE |
2838 | | * \param[in] loc location of the constant coordinate for the line |
2839 | | * \param[in] c1, c2 end point coordinates for the line |
2840 | | * \param[in] size window size; must be > 1 |
2841 | | * \param[out] pnad windowed square root of variance |
2842 | | * \return 0 if OK; 1 on error |
2843 | | * |
2844 | | * <pre> |
2845 | | * Notes: |
2846 | | * (1) The returned variance array traverses the line starting |
2847 | | * from the smallest coordinate, min(c1,c2). |
2848 | | * (2) Line end points are clipped to pixs. |
2849 | | * (3) The reference point for the variance calculation is the center of |
2850 | | * the window. Therefore, the numa start parameter from |
2851 | | * pixExtractOnLine() is incremented by %size/2, |
2852 | | * to align the variance values with the pixel coordinate. |
2853 | | * (4) The square root of the variance is the RMS deviation from the mean. |
2854 | | * </pre> |
2855 | | */ |
2856 | | l_ok |
2857 | | pixWindowedVarianceOnLine(PIX *pixs, |
2858 | | l_int32 dir, |
2859 | | l_int32 loc, |
2860 | | l_int32 c1, |
2861 | | l_int32 c2, |
2862 | | l_int32 size, |
2863 | | NUMA **pnad) |
2864 | 0 | { |
2865 | 0 | l_int32 i, j, w, h, cmin, cmax, maxloc, n, x, y; |
2866 | 0 | l_uint32 val; |
2867 | 0 | l_float32 norm, rootvar; |
2868 | 0 | l_float32 *array; |
2869 | 0 | l_float64 sum1, sum2, ave, var; |
2870 | 0 | NUMA *na1, *nad; |
2871 | 0 | PTA *pta; |
2872 | |
|
2873 | 0 | if (!pnad) |
2874 | 0 | return ERROR_INT("&nad not defined", __func__, 1); |
2875 | 0 | *pnad = NULL; |
2876 | 0 | if (!pixs || pixGetDepth(pixs) != 8) |
2877 | 0 | return ERROR_INT("pixs not defined or not 8bpp", __func__, 1); |
2878 | 0 | if (size < 2) |
2879 | 0 | return ERROR_INT("window size must be > 1", __func__, 1); |
2880 | 0 | if (dir != L_HORIZONTAL_LINE && dir != L_VERTICAL_LINE) |
2881 | 0 | return ERROR_INT("invalid direction", __func__, 1); |
2882 | 0 | pixGetDimensions(pixs, &w, &h, NULL); |
2883 | 0 | maxloc = (dir == L_HORIZONTAL_LINE) ? h - 1 : w - 1; |
2884 | 0 | if (loc < 0 || loc > maxloc) |
2885 | 0 | return ERROR_INT("invalid line position", __func__, 1); |
2886 | | |
2887 | | /* Clip line to the image */ |
2888 | 0 | cmin = L_MIN(c1, c2); |
2889 | 0 | cmax = L_MAX(c1, c2); |
2890 | 0 | maxloc = (dir == L_HORIZONTAL_LINE) ? w - 1 : h - 1; |
2891 | 0 | cmin = L_MAX(0, L_MIN(cmin, maxloc)); |
2892 | 0 | cmax = L_MAX(0, L_MIN(cmax, maxloc)); |
2893 | 0 | n = cmax - cmin + 1; |
2894 | | |
2895 | | /* Generate pta along the line */ |
2896 | 0 | pta = ptaCreate(n); |
2897 | 0 | if (dir == L_HORIZONTAL_LINE) { |
2898 | 0 | for (i = cmin; i <= cmax; i++) |
2899 | 0 | ptaAddPt(pta, i, loc); |
2900 | 0 | } else { /* vertical line */ |
2901 | 0 | for (i = cmin; i <= cmax; i++) |
2902 | 0 | ptaAddPt(pta, loc, i); |
2903 | 0 | } |
2904 | | |
2905 | | /* Get numa of pixel values on the line */ |
2906 | 0 | na1 = numaCreate(n); |
2907 | 0 | numaSetParameters(na1, cmin, 1); |
2908 | 0 | for (i = 0; i < n; i++) { |
2909 | 0 | ptaGetIPt(pta, i, &x, &y); |
2910 | 0 | pixGetPixel(pixs, x, y, &val); |
2911 | 0 | numaAddNumber(na1, val); |
2912 | 0 | } |
2913 | 0 | array = numaGetFArray(na1, L_NOCOPY); |
2914 | 0 | ptaDestroy(&pta); |
2915 | | |
2916 | | /* Compute root variance on overlapping windows */ |
2917 | 0 | nad = numaCreate(n); |
2918 | 0 | *pnad = nad; |
2919 | 0 | numaSetParameters(nad, cmin + size / 2, 1); |
2920 | 0 | norm = 1.0f / (l_float32)size; |
2921 | 0 | for (i = 0; i < n - size; i++) { /* along the line */ |
2922 | 0 | sum1 = sum2 = 0; |
2923 | 0 | for (j = 0; j < size; j++) { /* over the window */ |
2924 | 0 | val = array[i + j]; |
2925 | 0 | sum1 += val; |
2926 | 0 | sum2 += (l_float64)(val) * val; |
2927 | 0 | } |
2928 | 0 | ave = norm * sum1; |
2929 | 0 | var = norm * sum2 - ave * ave; |
2930 | 0 | if (var < 0) /* avoid small negative values from rounding effects */ |
2931 | 0 | var = 0.0; |
2932 | 0 | rootvar = (l_float32)sqrt(var); |
2933 | 0 | numaAddNumber(nad, rootvar); |
2934 | 0 | } |
2935 | |
|
2936 | 0 | numaDestroy(&na1); |
2937 | 0 | return 0; |
2938 | 0 | } |
2939 | | |
2940 | | |
2941 | | /*---------------------------------------------------------------------* |
2942 | | * Extract min/max of pixel values near lines * |
2943 | | *---------------------------------------------------------------------*/ |
2944 | | /*! |
2945 | | * \brief pixMinMaxNearLine() |
2946 | | * |
2947 | | * \param[in] pixs 8 bpp; no colormap |
2948 | | * \param[in] x1, y1 starting pt for line |
2949 | | * \param[in] x2, y2 end pt for line |
2950 | | * \param[in] dist distance to search from line in each direction |
2951 | | * \param[in] direction L_SCAN_NEGATIVE, L_SCAN_POSITIVE, L_SCAN_BOTH |
2952 | | * \param[out] pnamin [optional] minimum values |
2953 | | * \param[out] pnamax [optional] maximum values |
2954 | | * \param[out] pminave [optional] average of minimum values |
2955 | | * \param[out] pmaxave [optional] average of maximum values |
2956 | | * \return 0 if OK; 1 on error or if there are no sampled points |
2957 | | * within the image. |
2958 | | * |
2959 | | * <pre> |
2960 | | * Notes: |
2961 | | * (1) If the line is more horizontal than vertical, the values |
2962 | | * are computed for [x1, x2], and the pixels are taken |
2963 | | * below and/or above the local y-value. Otherwise, the |
2964 | | * values are computed for [y1, y2] and the pixels are taken |
2965 | | * to the left and/or right of the local x value. |
2966 | | * (2) %direction specifies which side (or both sides) of the |
2967 | | * line are scanned for min and max values. |
2968 | | * (3) There are two ways to tell if the returned values of min |
2969 | | * and max averages are valid: the returned values cannot be |
2970 | | * negative and the function must return 0. |
2971 | | * (4) All accessed pixels are clipped to the pix. |
2972 | | * </pre> |
2973 | | */ |
2974 | | l_ok |
2975 | | pixMinMaxNearLine(PIX *pixs, |
2976 | | l_int32 x1, |
2977 | | l_int32 y1, |
2978 | | l_int32 x2, |
2979 | | l_int32 y2, |
2980 | | l_int32 dist, |
2981 | | l_int32 direction, |
2982 | | NUMA **pnamin, |
2983 | | NUMA **pnamax, |
2984 | | l_float32 *pminave, |
2985 | | l_float32 *pmaxave) |
2986 | 0 | { |
2987 | 0 | l_int32 i, j, w, h, d, x, y, n, dir, found, minval, maxval, negloc, posloc; |
2988 | 0 | l_uint32 val; |
2989 | 0 | l_float32 sum; |
2990 | 0 | NUMA *namin, *namax; |
2991 | 0 | PTA *pta; |
2992 | |
|
2993 | 0 | if (pnamin) *pnamin = NULL; |
2994 | 0 | if (pnamax) *pnamax = NULL; |
2995 | 0 | if (pminave) *pminave = UNDEF; |
2996 | 0 | if (pmaxave) *pmaxave = UNDEF; |
2997 | 0 | if (!pnamin && !pnamax && !pminave && !pmaxave) |
2998 | 0 | return ERROR_INT("no output requested", __func__, 1); |
2999 | 0 | if (!pixs) |
3000 | 0 | return ERROR_INT("pixs not defined", __func__, 1); |
3001 | 0 | pixGetDimensions(pixs, &w, &h, &d); |
3002 | 0 | if (d != 8 || pixGetColormap(pixs)) |
3003 | 0 | return ERROR_INT("pixs not 8 bpp or has colormap", __func__, 1); |
3004 | 0 | dist = L_ABS(dist); |
3005 | 0 | if (direction != L_SCAN_NEGATIVE && direction != L_SCAN_POSITIVE && |
3006 | 0 | direction != L_SCAN_BOTH) |
3007 | 0 | return ERROR_INT("invalid direction", __func__, 1); |
3008 | | |
3009 | 0 | pta = generatePtaLine(x1, y1, x2, y2); |
3010 | 0 | n = ptaGetCount(pta); |
3011 | 0 | dir = (L_ABS(x1 - x2) == n - 1) ? L_HORIZ : L_VERT; |
3012 | 0 | namin = numaCreate(n); |
3013 | 0 | namax = numaCreate(n); |
3014 | 0 | negloc = -dist; |
3015 | 0 | posloc = dist; |
3016 | 0 | if (direction == L_SCAN_NEGATIVE) |
3017 | 0 | posloc = 0; |
3018 | 0 | else if (direction == L_SCAN_POSITIVE) |
3019 | 0 | negloc = 0; |
3020 | 0 | for (i = 0; i < n; i++) { |
3021 | 0 | ptaGetIPt(pta, i, &x, &y); |
3022 | 0 | minval = 255; |
3023 | 0 | maxval = 0; |
3024 | 0 | found = FALSE; |
3025 | 0 | if (dir == L_HORIZ) { |
3026 | 0 | if (x < 0 || x >= w) continue; |
3027 | 0 | for (j = negloc; j <= posloc; j++) { |
3028 | 0 | if (y + j < 0 || y + j >= h) continue; |
3029 | 0 | pixGetPixel(pixs, x, y + j, &val); |
3030 | 0 | found = TRUE; |
3031 | 0 | if (val < minval) minval = val; |
3032 | 0 | if (val > maxval) maxval = val; |
3033 | 0 | } |
3034 | 0 | } else { /* dir == L_VERT */ |
3035 | 0 | if (y < 0 || y >= h) continue; |
3036 | 0 | for (j = negloc; j <= posloc; j++) { |
3037 | 0 | if (x + j < 0 || x + j >= w) continue; |
3038 | 0 | pixGetPixel(pixs, x + j, y, &val); |
3039 | 0 | found = TRUE; |
3040 | 0 | if (val < minval) minval = val; |
3041 | 0 | if (val > maxval) maxval = val; |
3042 | 0 | } |
3043 | 0 | } |
3044 | 0 | if (found) { |
3045 | 0 | numaAddNumber(namin, minval); |
3046 | 0 | numaAddNumber(namax, maxval); |
3047 | 0 | } |
3048 | 0 | } |
3049 | |
|
3050 | 0 | n = numaGetCount(namin); |
3051 | 0 | if (n == 0) { |
3052 | 0 | numaDestroy(&namin); |
3053 | 0 | numaDestroy(&namax); |
3054 | 0 | ptaDestroy(&pta); |
3055 | 0 | return ERROR_INT("no output from this line", __func__, 1); |
3056 | 0 | } |
3057 | | |
3058 | 0 | if (pminave) { |
3059 | 0 | numaGetSum(namin, &sum); |
3060 | 0 | *pminave = sum / n; |
3061 | 0 | } |
3062 | 0 | if (pmaxave) { |
3063 | 0 | numaGetSum(namax, &sum); |
3064 | 0 | *pmaxave = sum / n; |
3065 | 0 | } |
3066 | 0 | if (pnamin) |
3067 | 0 | *pnamin = namin; |
3068 | 0 | else |
3069 | 0 | numaDestroy(&namin); |
3070 | 0 | if (pnamax) |
3071 | 0 | *pnamax = namax; |
3072 | 0 | else |
3073 | 0 | numaDestroy(&namax); |
3074 | 0 | ptaDestroy(&pta); |
3075 | 0 | return 0; |
3076 | 0 | } |
3077 | | |
3078 | | |
3079 | | /*---------------------------------------------------------------------* |
3080 | | * Rank row and column transforms * |
3081 | | *---------------------------------------------------------------------*/ |
3082 | | /*! |
3083 | | * \brief pixRankRowTransform() |
3084 | | * |
3085 | | * \param[in] pixs 8 bpp; no colormap |
3086 | | * \return pixd with pixels sorted in each row, from |
3087 | | * min to max value |
3088 | | * |
3089 | | * <pre> |
3090 | | * Notes: |
3091 | | * (1) The time is O(n) in the number of pixels and runs about |
3092 | | * 100 Mpixels/sec on a 3 GHz machine. |
3093 | | * </pre> |
3094 | | */ |
3095 | | PIX * |
3096 | | pixRankRowTransform(PIX *pixs) |
3097 | 0 | { |
3098 | 0 | l_int32 i, j, k, m, w, h, wpl, val; |
3099 | 0 | l_int32 histo[256]; |
3100 | 0 | l_uint32 *datas, *datad, *lines, *lined; |
3101 | 0 | PIX *pixd; |
3102 | |
|
3103 | 0 | if (!pixs) |
3104 | 0 | return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); |
3105 | 0 | if (pixGetDepth(pixs) != 8) |
3106 | 0 | return (PIX *)ERROR_PTR("pixs not 8 bpp", __func__, NULL); |
3107 | 0 | if (pixGetColormap(pixs)) |
3108 | 0 | return (PIX *)ERROR_PTR("pixs has a colormap", __func__, NULL); |
3109 | | |
3110 | 0 | pixGetDimensions(pixs, &w, &h, NULL); |
3111 | 0 | pixd = pixCreateTemplate(pixs); |
3112 | 0 | datas = pixGetData(pixs); |
3113 | 0 | datad = pixGetData(pixd); |
3114 | 0 | wpl = pixGetWpl(pixs); |
3115 | 0 | for (i = 0; i < h; i++) { |
3116 | 0 | memset(histo, 0, 1024); |
3117 | 0 | lines = datas + i * wpl; |
3118 | 0 | lined = datad + i * wpl; |
3119 | 0 | for (j = 0; j < w; j++) { |
3120 | 0 | val = GET_DATA_BYTE(lines, j); |
3121 | 0 | histo[val]++; |
3122 | 0 | } |
3123 | 0 | for (m = 0, j = 0; m < 256; m++) { |
3124 | 0 | for (k = 0; k < histo[m]; k++, j++) |
3125 | 0 | SET_DATA_BYTE(lined, j, m); |
3126 | 0 | } |
3127 | 0 | } |
3128 | |
|
3129 | 0 | return pixd; |
3130 | 0 | } |
3131 | | |
3132 | | |
3133 | | /*! |
3134 | | * \brief pixRankColumnTransform() |
3135 | | * |
3136 | | * \param[in] pixs 8 bpp; no colormap |
3137 | | * \return pixd with pixels sorted in each column, from |
3138 | | * min to max value |
3139 | | * |
3140 | | * <pre> |
3141 | | * Notes: |
3142 | | * (1) The time is O(n) in the number of pixels and runs about |
3143 | | * 50 Mpixels/sec on a 3 GHz machine. |
3144 | | * </pre> |
3145 | | */ |
3146 | | PIX * |
3147 | | pixRankColumnTransform(PIX *pixs) |
3148 | 0 | { |
3149 | 0 | l_int32 i, j, k, m, w, h, val; |
3150 | 0 | l_int32 histo[256]; |
3151 | 0 | void **lines8, **lined8; |
3152 | 0 | PIX *pixd; |
3153 | |
|
3154 | 0 | if (!pixs) |
3155 | 0 | return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); |
3156 | 0 | if (pixGetDepth(pixs) != 8) |
3157 | 0 | return (PIX *)ERROR_PTR("pixs not 8 bpp", __func__, NULL); |
3158 | 0 | if (pixGetColormap(pixs)) |
3159 | 0 | return (PIX *)ERROR_PTR("pixs has a colormap", __func__, NULL); |
3160 | | |
3161 | 0 | pixGetDimensions(pixs, &w, &h, NULL); |
3162 | 0 | pixd = pixCreateTemplate(pixs); |
3163 | 0 | lines8 = pixGetLinePtrs(pixs, NULL); |
3164 | 0 | lined8 = pixGetLinePtrs(pixd, NULL); |
3165 | 0 | for (j = 0; j < w; j++) { |
3166 | 0 | memset(histo, 0, 1024); |
3167 | 0 | for (i = 0; i < h; i++) { |
3168 | 0 | val = GET_DATA_BYTE(lines8[i], j); |
3169 | 0 | histo[val]++; |
3170 | 0 | } |
3171 | 0 | for (m = 0, i = 0; m < 256; m++) { |
3172 | 0 | for (k = 0; k < histo[m]; k++, i++) |
3173 | 0 | SET_DATA_BYTE(lined8[i], j, m); |
3174 | 0 | } |
3175 | 0 | } |
3176 | |
|
3177 | 0 | LEPT_FREE(lines8); |
3178 | 0 | LEPT_FREE(lined8); |
3179 | 0 | return pixd; |
3180 | 0 | } |