| /*====================================================================* |
| - Copyright (C) 2001 Leptonica. All rights reserved. |
| - This software is distributed in the hope that it will be |
| - useful, but with NO WARRANTY OF ANY KIND. |
| - No author or distributor accepts responsibility to anyone for the |
| - consequences of using this software, or for whether it serves any |
| - particular purpose or works at all, unless he or she says so in |
| - writing. Everyone is granted permission to copy, modify and |
| - redistribute this source code, for commercial or non-commercial |
| - purposes, with the following restrictions: (1) the origin of this |
| - source code must not be misrepresented; (2) modified versions must |
| - be plainly marked as such; and (3) this notice may not be removed |
| - or altered from any source or modified source distribution. |
| *====================================================================*/ |
| |
| /* |
| * pix4.c |
| * |
| * This file has these operations: |
| * |
| * (1) Pixel histograms |
| * (2) Foreground/background estimation |
| * (3) Rectangle extraction |
| * |
| * Pixel histogram, rank val, averaging and min/max |
| * NUMA *pixGetGrayHistogram() |
| * NUMA *pixGetGrayHistogramMasked() |
| * l_int32 pixGetColorHistogram() |
| * l_int32 pixGetColorHistogramMasked() |
| * l_int32 pixGetRankValueMaskedRGB() |
| * l_int32 pixGetRankValueMasked() |
| * l_int32 pixGetAverageMaskedRGB() |
| * l_int32 pixGetAverageMasked() |
| * l_int32 pixGetAverageTiledRGB() |
| * PIX *pixGetAverageTiled() |
| * l_int32 pixGetExtremeValue() |
| * l_int32 pixGetMaxValueInRect() |
| * |
| * Pixelwise aligned statistics |
| * PIX *pixaGetAlignedStats() |
| * l_int32 pixaExtractColumnFromEachPix() |
| * l_int32 pixGetRowStats() |
| * l_int32 pixGetColumnStats() |
| * l_int32 pixSetPixelColumn() |
| * |
| * Foreground/background estimation |
| * l_int32 pixThresholdForFgBg() |
| * l_int32 pixSplitDistributionFgBg() |
| * |
| * Measurement of properties |
| * l_int32 pixaFindDimensions() |
| * NUMA pixaFindAreaPerimRatio() |
| * l_int32 pixFindAreaPerimRatio() |
| * NUMA pixaFindPerimSizeRatio() |
| * l_int32 pixFindPerimSizeRatio() |
| * NUMA pixaFindAreaFraction() |
| * l_int32 pixFindAreaFraction() |
| * NUMA pixaFindWidthHeightRatio() |
| * NUMA pixaFindWidthHeightProduct() |
| * |
| * Extract rectangle |
| * PIX *pixClipRectangle() |
| * PIX *pixClipMasked() |
| * |
| * Clip to foreground |
| * PIX *pixClipToForeground() |
| * l_int32 pixClipBoxToForeground() |
| * l_int32 pixScanForForeground() |
| * l_int32 pixClipBoxToEdges() |
| * l_int32 pixScanForEdge() |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <math.h> |
| #include "allheaders.h" |
| |
| static const l_uint32 rmask32[] = {0x0, |
| 0x00000001, 0x00000003, 0x00000007, 0x0000000f, |
| 0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff, |
| 0x000001ff, 0x000003ff, 0x000007ff, 0x00000fff, |
| 0x00001fff, 0x00003fff, 0x00007fff, 0x0000ffff, |
| 0x0001ffff, 0x0003ffff, 0x0007ffff, 0x000fffff, |
| 0x001fffff, 0x003fffff, 0x007fffff, 0x00ffffff, |
| 0x01ffffff, 0x03ffffff, 0x07ffffff, 0x0fffffff, |
| 0x1fffffff, 0x3fffffff, 0x7fffffff, 0xffffffff}; |
| |
| #ifndef NO_CONSOLE_IO |
| #define DEBUG_EDGES 0 |
| #endif /* ~NO_CONSOLE_IO */ |
| |
| |
| /*------------------------------------------------------------------* |
| * Pixel histogram and averaging * |
| *------------------------------------------------------------------*/ |
| /*! |
| * pixGetGrayHistogram() |
| * |
| * Input: pixs (1, 2, 4, 8, 16 bpp; can be colormapped) |
| * factor (subsampling factor; integer >= 1) |
| * Return: na (histogram), or null on error |
| * |
| * Notes: |
| * (1) This generates a histogram of gray or cmapped pixels. |
| * The image must not be rgb. |
| * (2) If pixs does not have a colormap, the output histogram is |
| * of size 2^d, where d is the depth of pixs. |
| * (3) If pixs has has a colormap with color entries, the histogram |
| * generated is of the colormap indices, and is of size 2^d. |
| * (4) If pixs has a gray (r=g=b) colormap, a temporary 8 bpp image |
| * without the colormap is used to construct a histogram of |
| * size 256. |
| * (5) Set the subsampling factor > 1 to reduce the amount of computation. |
| */ |
| NUMA * |
| pixGetGrayHistogram(PIX *pixs, |
| l_int32 factor) |
| { |
| l_int32 i, j, w, h, d, wpl, val, size, count, colorfound; |
| l_uint32 *data, *line; |
| l_float32 *array; |
| NUMA *na; |
| PIX *pixg; |
| PIXCMAP *cmap; |
| |
| PROCNAME("pixGetGrayHistogram"); |
| |
| if (!pixs) |
| return (NUMA *)ERROR_PTR("pixs not defined", procName, NULL); |
| d = pixGetDepth(pixs); |
| if (d > 16) |
| return (NUMA *)ERROR_PTR("depth not in {1,2,4,8,16}", procName, NULL); |
| if (factor < 1) |
| return (NUMA *)ERROR_PTR("sampling factor < 1", procName, NULL); |
| |
| if ((cmap = pixGetColormap(pixs)) != NULL) |
| pixcmapHasColor(cmap, &colorfound); |
| if (cmap && !colorfound) |
| pixg = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE); |
| else |
| pixg = pixClone(pixs); |
| |
| pixGetDimensions(pixg, &w, &h, &d); |
| size = 1 << d; |
| if ((na = numaCreate(size)) == NULL) |
| return (NUMA *)ERROR_PTR("na not made", procName, NULL); |
| numaSetCount(na, size); /* all initialized to 0.0 */ |
| array = numaGetFArray(na, L_NOCOPY); |
| |
| if (d == 1) { /* special case */ |
| pixCountPixels(pixg, &count, NULL); |
| array[0] = w * h - count; |
| array[1] = count; |
| pixDestroy(&pixg); |
| return na; |
| } |
| |
| wpl = pixGetWpl(pixg); |
| data = pixGetData(pixg); |
| for (i = 0; i < h; i += factor) { |
| line = data + i * wpl; |
| switch (d) |
| { |
| case 2: |
| for (j = 0; j < w; j += factor) { |
| val = GET_DATA_DIBIT(line, j); |
| array[val] += 1.0; |
| } |
| break; |
| case 4: |
| for (j = 0; j < w; j += factor) { |
| val = GET_DATA_QBIT(line, j); |
| array[val] += 1.0; |
| } |
| break; |
| case 8: |
| for (j = 0; j < w; j += factor) { |
| val = GET_DATA_BYTE(line, j); |
| array[val] += 1.0; |
| } |
| break; |
| case 16: |
| for (j = 0; j < w; j += factor) { |
| val = GET_DATA_TWO_BYTES(line, j); |
| array[val] += 1.0; |
| } |
| break; |
| default: |
| numaDestroy(&na); |
| return (NUMA *)ERROR_PTR("illegal depth", procName, NULL); |
| } |
| } |
| |
| pixDestroy(&pixg); |
| return na; |
| } |
| |
| |
| /*! |
| * pixGetGrayHistogramMasked() |
| * |
| * Input: pixs (8 bpp, or colormapped) |
| * pixm (<optional> 1 bpp mask over which histogram is |
| * to be computed; use use all pixels if null) |
| * x, y (UL corner of pixm relative to the UL corner of pixs; |
| * can be < 0; these values are ignored if pixm is null) |
| * factor (subsampling factor; integer >= 1) |
| * Return: na (histogram), or null on error |
| * |
| * Notes: |
| * (1) If pixs is cmapped, it is converted to 8 bpp gray. |
| * (2) This always returns a 256-value histogram of pixel values. |
| * (3) Set the subsampling factor > 1 to reduce the amount of computation. |
| * (4) Clipping of pixm (if it exists) to pixs is done in the inner loop. |
| * (5) Input x,y are ignored unless pixm exists. |
| */ |
| NUMA * |
| pixGetGrayHistogramMasked(PIX *pixs, |
| PIX *pixm, |
| l_int32 x, |
| l_int32 y, |
| l_int32 factor) |
| { |
| l_int32 i, j, w, h, wm, hm, dm, wplg, wplm, val; |
| l_uint32 *datag, *datam, *lineg, *linem; |
| l_float32 *array; |
| NUMA *na; |
| PIX *pixg; |
| |
| PROCNAME("pixGetGrayHistogramMasked"); |
| |
| if (!pixm) |
| return pixGetGrayHistogram(pixs, factor); |
| |
| if (!pixs) |
| return (NUMA *)ERROR_PTR("pixs not defined", procName, NULL); |
| if (pixGetDepth(pixs) != 8 && !pixGetColormap(pixs)) |
| return (NUMA *)ERROR_PTR("pixs neither 8 bpp nor colormapped", |
| procName, NULL); |
| pixGetDimensions(pixm, &wm, &hm, &dm); |
| if (dm != 1) |
| return (NUMA *)ERROR_PTR("pixm not 1 bpp", procName, NULL); |
| if (factor < 1) |
| return (NUMA *)ERROR_PTR("sampling factor < 1", procName, NULL); |
| |
| if ((na = numaCreate(256)) == NULL) |
| return (NUMA *)ERROR_PTR("na not made", procName, NULL); |
| numaSetCount(na, 256); /* all initialized to 0.0 */ |
| array = numaGetFArray(na, L_NOCOPY); |
| |
| if (pixGetColormap(pixs)) |
| pixg = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE); |
| else |
| pixg = pixClone(pixs); |
| pixGetDimensions(pixg, &w, &h, NULL); |
| datag = pixGetData(pixg); |
| wplg = pixGetWpl(pixg); |
| datam = pixGetData(pixm); |
| wplm = pixGetWpl(pixm); |
| |
| /* Generate the histogram */ |
| for (i = 0; i < hm; i += factor) { |
| if (y + i < 0 || y + i >= h) continue; |
| lineg = datag + (y + i) * wplg; |
| linem = datam + i * wplm; |
| for (j = 0; j < wm; j += factor) { |
| if (x + j < 0 || x + j >= w) continue; |
| if (GET_DATA_BIT(linem, j)) { |
| val = GET_DATA_BYTE(lineg, x + j); |
| array[val] += 1.0; |
| } |
| } |
| } |
| |
| pixDestroy(&pixg); |
| return na; |
| } |
| |
| |
| /*! |
| * pixGetColorHistogram() |
| * |
| * Input: pixs (rgb or colormapped) |
| * factor (subsampling factor; integer >= 1) |
| * &nar (<return> red histogram) |
| * &nag (<return> green histogram) |
| * &nab (<return> blue histogram) |
| * Return: 0 if OK, 1 on error |
| * |
| * Notes: |
| * (1) This generates a set of three 256 entry histograms, |
| * one for each color component (r,g,b). |
| * (2) Set the subsampling factor > 1 to reduce the amount of computation. |
| */ |
| l_int32 |
| pixGetColorHistogram(PIX *pixs, |
| l_int32 factor, |
| NUMA **pnar, |
| NUMA **pnag, |
| NUMA **pnab) |
| { |
| l_int32 i, j, w, h, d, wpl, index, rval, gval, bval; |
| l_uint32 *data, *line; |
| l_float32 *rarray, *garray, *barray; |
| NUMA *nar, *nag, *nab; |
| PIXCMAP *cmap; |
| |
| PROCNAME("pixGetColorHistogram"); |
| |
| if (!pnar || !pnag || !pnab) |
| return ERROR_INT("&nar, &nag, &nab not all defined", procName, 1); |
| *pnar = *pnag = *pnab = NULL; |
| if (!pixs) |
| return ERROR_INT("pixs not defined", procName, 1); |
| pixGetDimensions(pixs, &w, &h, &d); |
| cmap = pixGetColormap(pixs); |
| if (cmap && (d != 2 && d != 4 && d != 8)) |
| return ERROR_INT("colormap and not 2, 4, or 8 bpp", procName, 1); |
| if (!cmap && d != 32) |
| return ERROR_INT("no colormap and not rgb", procName, 1); |
| if (factor < 1) |
| return ERROR_INT("sampling factor < 1", procName, 1); |
| |
| /* Set up the histogram arrays */ |
| nar = numaCreate(256); |
| nag = numaCreate(256); |
| nab = numaCreate(256); |
| numaSetCount(nar, 256); |
| numaSetCount(nag, 256); |
| numaSetCount(nab, 256); |
| rarray = numaGetFArray(nar, L_NOCOPY); |
| garray = numaGetFArray(nag, L_NOCOPY); |
| barray = numaGetFArray(nab, L_NOCOPY); |
| *pnar = nar; |
| *pnag = nag; |
| *pnab = nab; |
| |
| /* Generate the color histograms */ |
| data = pixGetData(pixs); |
| wpl = pixGetWpl(pixs); |
| if (cmap) { |
| for (i = 0; i < h; i += factor) { |
| line = data + i * wpl; |
| for (j = 0; j < w; j += factor) { |
| if (d == 8) |
| index = GET_DATA_BYTE(line, j); |
| else if (d == 4) |
| index = GET_DATA_QBIT(line, j); |
| else /* 2 bpp */ |
| index = GET_DATA_DIBIT(line, j); |
| pixcmapGetColor(cmap, index, &rval, &gval, &bval); |
| rarray[rval] += 1.0; |
| garray[gval] += 1.0; |
| barray[bval] += 1.0; |
| } |
| } |
| } |
| else { /* 32 bpp rgb */ |
| for (i = 0; i < h; i += factor) { |
| line = data + i * wpl; |
| for (j = 0; j < w; j += factor) { |
| extractRGBValues(line[j], &rval, &gval, &bval); |
| rarray[rval] += 1.0; |
| garray[gval] += 1.0; |
| barray[bval] += 1.0; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| |
| /*! |
| * pixGetColorHistogramMasked() |
| * |
| * Input: pixs (32 bpp rgb, or colormapped) |
| * pixm (<optional> 1 bpp mask over which histogram is |
| * to be computed; use use all pixels if null) |
| * x, y (UL corner of pixm relative to the UL corner of pixs; |
| * can be < 0; these values are ignored if pixm is null) |
| * factor (subsampling factor; integer >= 1) |
| * &nar (<return> red histogram) |
| * &nag (<return> green histogram) |
| * &nab (<return> blue histogram) |
| * Return: 0 if OK, 1 on error |
| * |
| * Notes: |
| * (1) This generates a set of three 256 entry histograms, |
| * (2) Set the subsampling factor > 1 to reduce the amount of computation. |
| * (3) Clipping of pixm (if it exists) to pixs is done in the inner loop. |
| * (4) Input x,y are ignored unless pixm exists. |
| */ |
| l_int32 |
| pixGetColorHistogramMasked(PIX *pixs, |
| PIX *pixm, |
| l_int32 x, |
| l_int32 y, |
| l_int32 factor, |
| NUMA **pnar, |
| NUMA **pnag, |
| NUMA **pnab) |
| { |
| l_int32 i, j, w, h, d, wm, hm, dm, wpls, wplm, index, rval, gval, bval; |
| l_uint32 *datas, *datam, *lines, *linem; |
| l_float32 *rarray, *garray, *barray; |
| NUMA *nar, *nag, *nab; |
| PIXCMAP *cmap; |
| |
| PROCNAME("pixGetColorHistogramMasked"); |
| |
| if (!pixm) |
| return pixGetColorHistogram(pixs, factor, pnar, pnag, pnab); |
| |
| if (!pnar || !pnag || !pnab) |
| return ERROR_INT("&nar, &nag, &nab not all defined", procName, 1); |
| *pnar = *pnag = *pnab = NULL; |
| if (!pixs) |
| return ERROR_INT("pixs not defined", procName, 1); |
| pixGetDimensions(pixs, &w, &h, &d); |
| cmap = pixGetColormap(pixs); |
| if (cmap && (d != 2 && d != 4 && d != 8)) |
| return ERROR_INT("colormap and not 2, 4, or 8 bpp", procName, 1); |
| if (!cmap && d != 32) |
| return ERROR_INT("no colormap and not rgb", procName, 1); |
| pixGetDimensions(pixm, &wm, &hm, &dm); |
| if (dm != 1) |
| return ERROR_INT("pixm not 1 bpp", procName, 1); |
| if (factor < 1) |
| return ERROR_INT("sampling factor < 1", procName, 1); |
| |
| /* Set up the histogram arrays */ |
| nar = numaCreate(256); |
| nag = numaCreate(256); |
| nab = numaCreate(256); |
| numaSetCount(nar, 256); |
| numaSetCount(nag, 256); |
| numaSetCount(nab, 256); |
| rarray = numaGetFArray(nar, L_NOCOPY); |
| garray = numaGetFArray(nag, L_NOCOPY); |
| barray = numaGetFArray(nab, L_NOCOPY); |
| *pnar = nar; |
| *pnag = nag; |
| *pnab = nab; |
| |
| /* Generate the color histograms */ |
| datas = pixGetData(pixs); |
| wpls = pixGetWpl(pixs); |
| datam = pixGetData(pixm); |
| wplm = pixGetWpl(pixm); |
| if (cmap) { |
| for (i = 0; i < hm; i += factor) { |
| if (y + i < 0 || y + i >= h) continue; |
| lines = datas + (y + i) * wpls; |
| linem = datam + i * wplm; |
| for (j = 0; j < wm; j += factor) { |
| if (x + j < 0 || x + j >= w) continue; |
| if (GET_DATA_BIT(linem, j)) { |
| if (d == 8) |
| index = GET_DATA_BYTE(lines, x + j); |
| else if (d == 4) |
| index = GET_DATA_QBIT(lines, x + j); |
| else /* 2 bpp */ |
| index = GET_DATA_DIBIT(lines, x + j); |
| pixcmapGetColor(cmap, index, &rval, &gval, &bval); |
| rarray[rval] += 1.0; |
| garray[gval] += 1.0; |
| barray[bval] += 1.0; |
| } |
| } |
| } |
| } |
| else { /* 32 bpp rgb */ |
| for (i = 0; i < hm; i += factor) { |
| if (y + i < 0 || y + i >= h) continue; |
| lines = datas + (y + i) * wpls; |
| linem = datam + i * wplm; |
| for (j = 0; j < wm; j += factor) { |
| if (x + j < 0 || x + j >= w) continue; |
| if (GET_DATA_BIT(linem, j)) { |
| extractRGBValues(lines[x + j], &rval, &gval, &bval); |
| rarray[rval] += 1.0; |
| garray[gval] += 1.0; |
| barray[bval] += 1.0; |
| } |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| |
| /*! |
| * pixGetRankValueMaskedRGB() |
| * |
| * Input: pixs (32 bpp) |
| * pixm (<optional> 1 bpp mask over which rank val is to be taken; |
| * use all pixels if null) |
| * x, y (UL corner of pixm relative to the UL corner of pixs; |
| * can be < 0; these values are ignored if pixm is null) |
| * factor (subsampling factor; integer >= 1) |
| * rank (between 0.0 and 1.0; 1.0 is brightest, 0.0 is darkest) |
| * &rval (<optional return> red component val for to input rank) |
| * &gval (<optional return> green component val for to input rank) |
| * &bval (<optional return> blue component val for to input rank) |
| * Return: 0 if OK, 1 on error |
| * |
| * Notes: |
| * (1) Computes the rank component values of pixels in pixs that |
| * are under the fg of the optional mask. If the mask is null, it |
| * computes the average of the pixels in pixs. |
| * (2) Set the subsampling factor > 1 to reduce the amount of |
| * computation. |
| * (4) Input x,y are ignored unless pixm exists. |
| * (5) The rank must be in [0.0 ... 1.0], where the brightest pixel |
| * has rank 1.0. For the median pixel value, use 0.5. |
| */ |
| l_int32 |
| pixGetRankValueMaskedRGB(PIX *pixs, |
| PIX *pixm, |
| l_int32 x, |
| l_int32 y, |
| l_int32 factor, |
| l_float32 rank, |
| l_float32 *prval, |
| l_float32 *pgval, |
| l_float32 *pbval) |
| { |
| l_float32 scale; |
| PIX *pixmt, *pixt; |
| |
| PROCNAME("pixGetRankValueMaskedRGB"); |
| |
| if (!pixs) |
| return ERROR_INT("pixs not defined", procName, 1); |
| if (pixGetDepth(pixs) != 32) |
| return ERROR_INT("pixs not 32 bpp", procName, 1); |
| if (pixm && pixGetDepth(pixm) != 1) |
| return ERROR_INT("pixm not 1 bpp", procName, 1); |
| if (factor < 1) |
| return ERROR_INT("sampling factor < 1", procName, 1); |
| if (rank < 0.0 || rank > 1.0) |
| return ERROR_INT("rank not in [0.0 ... 1.0]", procName, 1); |
| if (!prval && !pgval && !pbval) |
| return ERROR_INT("no results requested", procName, 1); |
| |
| pixmt = NULL; |
| if (pixm) { |
| scale = 1.0 / (l_float32)factor; |
| pixmt = pixScale(pixm, scale, scale); |
| } |
| if (prval) { |
| pixt = pixScaleRGBToGrayFast(pixs, factor, COLOR_RED); |
| pixGetRankValueMasked(pixt, pixmt, x / factor, y / factor, |
| factor, rank, prval, NULL); |
| pixDestroy(&pixt); |
| } |
| if (pgval) { |
| pixt = pixScaleRGBToGrayFast(pixs, factor, COLOR_GREEN); |
| pixGetRankValueMasked(pixt, pixmt, x / factor, y / factor, |
| factor, rank, pgval, NULL); |
| pixDestroy(&pixt); |
| } |
| if (pbval) { |
| pixt = pixScaleRGBToGrayFast(pixs, factor, COLOR_BLUE); |
| pixGetRankValueMasked(pixt, pixmt, x / factor, y / factor, |
| factor, rank, pbval, NULL); |
| pixDestroy(&pixt); |
| } |
| pixDestroy(&pixmt); |
| return 0; |
| } |
| |
| |
| /*! |
| * pixGetRankValueMasked() |
| * |
| * Input: pixs (8 bpp, or colormapped) |
| * pixm (<optional> 1 bpp mask over which rank val is to be taken; |
| * use all pixels if null) |
| * x, y (UL corner of pixm relative to the UL corner of pixs; |
| * can be < 0; these values are ignored if pixm is null) |
| * factor (subsampling factor; integer >= 1) |
| * rank (between 0.0 and 1.0; 1.0 is brightest, 0.0 is darkest) |
| * &val (<return> pixel value corresponding to input rank) |
| * &na (<optional return> of histogram) |
| * Return: 0 if OK, 1 on error |
| * |
| * Notes: |
| * (1) Computes the rank value of pixels in pixs that are under |
| * the fg of the optional mask. If the mask is null, it |
| * computes the average of the pixels in pixs. |
| * (2) Set the subsampling factor > 1 to reduce the amount of |
| * computation. |
| * (3) Clipping of pixm (if it exists) to pixs is done in the inner loop. |
| * (4) Input x,y are ignored unless pixm exists. |
| * (5) The rank must be in [0.0 ... 1.0], where the brightest pixel |
| * has rank 1.0. For the median pixel value, use 0.5. |
| * (6) The histogram can optionally be returned, so that other rank |
| * values can be extracted without recomputing the histogram. |
| * In that case, just use |
| * numaHistogramGetValFromRank(na, rank, &val); |
| * on the returned Numa for additional rank values. |
| */ |
| l_int32 |
| pixGetRankValueMasked(PIX *pixs, |
| PIX *pixm, |
| l_int32 x, |
| l_int32 y, |
| l_int32 factor, |
| l_float32 rank, |
| l_float32 *pval, |
| NUMA **pna) |
| { |
| NUMA *na; |
| |
| PROCNAME("pixGetRankValueMasked"); |
| |
| if (pna) |
| *pna = NULL; |
| if (!pixs) |
| return ERROR_INT("pixs not defined", procName, 1); |
| if (pixGetDepth(pixs) != 8 && !pixGetColormap(pixs)) |
| return ERROR_INT("pixs neither 8 bpp nor colormapped", procName, 1); |
| if (pixm && pixGetDepth(pixm) != 1) |
| return ERROR_INT("pixm not 1 bpp", procName, 1); |
| if (factor < 1) |
| return ERROR_INT("sampling factor < 1", procName, 1); |
| if (rank < 0.0 || rank > 1.0) |
| return ERROR_INT("rank not in [0.0 ... 1.0]", procName, 1); |
| if (!pval) |
| return ERROR_INT("&val not defined", procName, 1); |
| *pval = 0.0; /* init */ |
| |
| if ((na = pixGetGrayHistogramMasked(pixs, pixm, x, y, factor)) == NULL) |
| return ERROR_INT("na not made", procName, 1); |
| numaHistogramGetValFromRank(na, rank, pval); |
| if (pna) |
| *pna = na; |
| else |
| numaDestroy(&na); |
| |
| return 0; |
| } |
| |
| |
| /*! |
| * pixGetAverageMaskedRGB() |
| * |
| * Input: pixs (32 bpp, or colormapped) |
| * pixm (<optional> 1 bpp mask over which average is to be taken; |
| * use all pixels if null) |
| * x, y (UL corner of pixm relative to the UL corner of pixs; |
| * can be < 0) |
| * factor (subsampling factor; >= 1) |
| * type (L_MEAN_ABSVAL, L_ROOT_MEAN_SQUARE, |
| * L_STANDARD_DEVIATION, L_VARIANCE) |
| * &rval (<return optional> measured red value of given 'type') |
| * &gval (<return optional> measured green value of given 'type') |
| * &bval (<return optional> measured blue value of given 'type') |
| * Return: 0 if OK, 1 on error |
| * |
| * Notes: |
| * (1) For usage, see pixGetAverageMasked(). |
| * (2) If there is a colormap, it is removed before the 8 bpp |
| * component images are extracted. |
| */ |
| l_int32 |
| pixGetAverageMaskedRGB(PIX *pixs, |
| PIX *pixm, |
| l_int32 x, |
| l_int32 y, |
| l_int32 factor, |
| l_int32 type, |
| l_float32 *prval, |
| l_float32 *pgval, |
| l_float32 *pbval) |
| { |
| l_int32 color; |
| PIX *pixt; |
| PIXCMAP *cmap; |
| |
| PROCNAME("pixGetAverageMaskedRGB"); |
| |
| if (!pixs) |
| return ERROR_INT("pixs not defined", procName, 1); |
| color = 0; |
| cmap = pixGetColormap(pixs); |
| if (pixGetDepth(pixs) != 32 && !cmap) |
| return ERROR_INT("pixs neither 32 bpp nor colormapped", procName, 1); |
| if (pixm && pixGetDepth(pixm) != 1) |
| return ERROR_INT("pixm not 1 bpp", procName, 1); |
| if (factor < 1) |
| return ERROR_INT("subsampling factor < 1", procName, 1); |
| if (type != L_MEAN_ABSVAL && type != L_ROOT_MEAN_SQUARE && |
| type != L_STANDARD_DEVIATION && type != L_VARIANCE) |
| return ERROR_INT("invalid measure type", procName, 1); |
| if (!prval && !pgval && !pbval) |
| return ERROR_INT("no values requested", procName, 1); |
| |
| if (prval) { |
| if (cmap) |
| pixt = pixGetRGBComponentCmap(pixs, COLOR_RED); |
| else |
| pixt = pixGetRGBComponent(pixs, COLOR_RED); |
| pixGetAverageMasked(pixt, pixm, x, y, factor, type, prval); |
| pixDestroy(&pixt); |
| } |
| if (pgval) { |
| if (cmap) |
| pixt = pixGetRGBComponentCmap(pixs, COLOR_GREEN); |
| else |
| pixt = pixGetRGBComponent(pixs, COLOR_GREEN); |
| pixGetAverageMasked(pixt, pixm, x, y, factor, type, pgval); |
| pixDestroy(&pixt); |
| } |
| if (pbval) { |
| if (cmap) |
| pixt = pixGetRGBComponentCmap(pixs, COLOR_BLUE); |
| else |
| pixt = pixGetRGBComponent(pixs, COLOR_BLUE); |
| pixGetAverageMasked(pixt, pixm, x, y, factor, type, pbval); |
| pixDestroy(&pixt); |
| } |
| |
| return 0; |
| } |
| |
| |
| /*! |
| * pixGetAverageMasked() |
| * |
| * Input: pixs (8 or 16 bpp, or colormapped) |
| * pixm (<optional> 1 bpp mask over which average is to be taken; |
| * use all pixels if null) |
| * x, y (UL corner of pixm relative to the UL corner of pixs; |
| * can be < 0) |
| * factor (subsampling factor; >= 1) |
| * type (L_MEAN_ABSVAL, L_ROOT_MEAN_SQUARE, |
| * L_STANDARD_DEVIATION, L_VARIANCE) |
| * &val (<return> measured value of given 'type') |
| * Return: 0 if OK, 1 on error |
| * |
| * Notes: |
| * (1) Use L_MEAN_ABSVAL to get the average value of pixels in pixs |
| * that are under the fg of the optional mask. If the mask |
| * is null, it finds the average of the pixels in pixs. |
| * (2) Likewise, use L_ROOT_MEAN_SQUARE to get the rms value of |
| * pixels in pixs, either masked or not; L_STANDARD_DEVIATION |
| * to get the standard deviation from the mean of the pixels; |
| * L_VARIANCE to get the average squared difference from the |
| * expected value. The variance is the square of the stdev. |
| * For the standard deviation, we use |
| * sqrt(<(<x> - x)>^2) = sqrt(<x^2> - <x>^2) |
| * (3) Set the subsampling factor > 1 to reduce the amount of |
| * computation. |
| * (4) Clipping of pixm (if it exists) to pixs is done in the inner loop. |
| * (5) Input x,y are ignored unless pixm exists. |
| */ |
| l_int32 |
| pixGetAverageMasked(PIX *pixs, |
| PIX *pixm, |
| l_int32 x, |
| l_int32 y, |
| l_int32 factor, |
| l_int32 type, |
| l_float32 *pval) |
| { |
| l_int32 i, j, w, h, d, wm, hm, wplg, wplm, val, count; |
| l_uint32 *datag, *datam, *lineg, *linem; |
| l_float64 sumave, summs, ave, meansq, var; |
| PIX *pixg; |
| |
| PROCNAME("pixGetAverageMasked"); |
| |
| if (!pixs) |
| return ERROR_INT("pixs not defined", procName, 1); |
| d = pixGetDepth(pixs); |
| if (d != 8 && d != 16 && !pixGetColormap(pixs)) |
| return ERROR_INT("pixs not 8 or 16 bpp or colormapped", procName, 1); |
| if (pixm && pixGetDepth(pixm) != 1) |
| return ERROR_INT("pixm not 1 bpp", procName, 1); |
| if (factor < 1) |
| return ERROR_INT("subsampling factor < 1", procName, 1); |
| if (type != L_MEAN_ABSVAL && type != L_ROOT_MEAN_SQUARE && |
| type != L_STANDARD_DEVIATION && type != L_VARIANCE) |
| return ERROR_INT("invalid measure type", procName, 1); |
| if (!pval) |
| return ERROR_INT("&val not defined", procName, 1); |
| *pval = 0.0; /* init */ |
| |
| if (pixGetColormap(pixs)) |
| pixg = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE); |
| else |
| pixg = pixClone(pixs); |
| pixGetDimensions(pixg, &w, &h, &d); |
| datag = pixGetData(pixg); |
| wplg = pixGetWpl(pixg); |
| |
| sumave = summs = 0.0; |
| count = 0; |
| if (!pixm) { |
| for (i = 0; i < h; i += factor) { |
| lineg = datag + i * wplg; |
| for (j = 0; j < w; j += factor) { |
| if (d == 8) |
| val = GET_DATA_BYTE(lineg, j); |
| else /* d == 16 */ |
| val = GET_DATA_TWO_BYTES(lineg, j); |
| if (type != L_ROOT_MEAN_SQUARE) |
| sumave += val; |
| if (type != L_MEAN_ABSVAL) |
| summs += val * val; |
| count++; |
| } |
| } |
| } |
| else { |
| pixGetDimensions(pixm, &wm, &hm, NULL); |
| datam = pixGetData(pixm); |
| wplm = pixGetWpl(pixm); |
| for (i = 0; i < hm; i += factor) { |
| if (y + i < 0 || y + i >= h) continue; |
| lineg = datag + (y + i) * wplg; |
| linem = datam + i * wplm; |
| for (j = 0; j < wm; j += factor) { |
| if (x + j < 0 || x + j >= w) continue; |
| if (GET_DATA_BIT(linem, j)) { |
| if (d == 8) |
| val = GET_DATA_BYTE(lineg, x + j); |
| else /* d == 16 */ |
| val = GET_DATA_TWO_BYTES(lineg, x + j); |
| if (type != L_ROOT_MEAN_SQUARE) |
| sumave += val; |
| if (type != L_MEAN_ABSVAL) |
| summs += val * val; |
| count++; |
| } |
| } |
| } |
| } |
| |
| pixDestroy(&pixg); |
| if (count == 0) |
| return ERROR_INT("no pixels sampled", procName, 1); |
| ave = sumave / (l_float64)count; |
| meansq = summs / (l_float64)count; |
| var = meansq - ave * ave; |
| if (type == L_MEAN_ABSVAL) |
| *pval = (l_float32)ave; |
| else if (type == L_ROOT_MEAN_SQUARE) |
| *pval = (l_float32)sqrt(meansq); |
| else if (type == L_STANDARD_DEVIATION) |
| *pval = (l_float32)sqrt(var); |
| else /* type == L_VARIANCE */ |
| *pval = (l_float32)var; |
| |
| return 0; |
| } |
| |
| |
| /*! |
| * pixGetAverageTiledRGB() |
| * |
| * Input: pixs (32 bpp, or colormapped) |
| * sx, sy (tile size; must be at least 2 x 2) |
| * type (L_MEAN_ABSVAL, L_ROOT_MEAN_SQUARE, L_STANDARD_DEVIATION) |
| * &pixr (<optional return> tiled 'average' of red component) |
| * &pixg (<optional return> tiled 'average' of green component) |
| * &pixb (<optional return> tiled 'average' of blue component) |
| * Return: 0 if OK, 1 on error |
| * |
| * Notes: |
| * (1) For usage, see pixGetAverageTiled(). |
| * (2) If there is a colormap, it is removed before the 8 bpp |
| * component images are extracted. |
| */ |
| l_int32 |
| pixGetAverageTiledRGB(PIX *pixs, |
| l_int32 sx, |
| l_int32 sy, |
| l_int32 type, |
| PIX **ppixr, |
| PIX **ppixg, |
| PIX **ppixb) |
| { |
| PIX *pixt; |
| PIXCMAP *cmap; |
| |
| PROCNAME("pixGetAverageTiledRGB"); |
| |
| if (!pixs) |
| return ERROR_INT("pixs not defined", procName, 1); |
| cmap = pixGetColormap(pixs); |
| if (pixGetDepth(pixs) != 32 && !cmap) |
| return ERROR_INT("pixs neither 32 bpp nor colormapped", procName, 1); |
| if (sx < 2 || sy < 2) |
| return ERROR_INT("sx and sy not both > 1", procName, 1); |
| if (type != L_MEAN_ABSVAL && type != L_ROOT_MEAN_SQUARE && |
| type != L_STANDARD_DEVIATION) |
| return ERROR_INT("invalid measure type", procName, 1); |
| if (!ppixr && !ppixg && !ppixb) |
| return ERROR_INT("no returned data requested", procName, 1); |
| |
| if (ppixr) { |
| if (cmap) |
| pixt = pixGetRGBComponentCmap(pixs, COLOR_RED); |
| else |
| pixt = pixGetRGBComponent(pixs, COLOR_RED); |
| *ppixr = pixGetAverageTiled(pixt, sx, sy, type); |
| pixDestroy(&pixt); |
| } |
| if (ppixg) { |
| if (cmap) |
| pixt = pixGetRGBComponentCmap(pixs, COLOR_GREEN); |
| else |
| pixt = pixGetRGBComponent(pixs, COLOR_GREEN); |
| *ppixg = pixGetAverageTiled(pixt, sx, sy, type); |
| pixDestroy(&pixt); |
| } |
| if (ppixb) { |
| if (cmap) |
| pixt = pixGetRGBComponentCmap(pixs, COLOR_BLUE); |
| else |
| pixt = pixGetRGBComponent(pixs, COLOR_BLUE); |
| *ppixb = pixGetAverageTiled(pixt, sx, sy, type); |
| pixDestroy(&pixt); |
| } |
| |
| return 0; |
| } |
| |
| |
| /*! |
| * pixGetAverageTiled() |
| * |
| * Input: pixs (8 bpp, or colormapped) |
| * sx, sy (tile size; must be at least 2 x 2) |
| * type (L_MEAN_ABSVAL, L_ROOT_MEAN_SQUARE, L_STANDARD_DEVIATION) |
| * Return: pixd (average values in each tile), or null on error |
| * |
| * Notes: |
| * (1) Only computes for tiles that are entirely contained in pixs. |
| * (2) Use L_MEAN_ABSVAL to get the average abs value within the tile; |
| * L_ROOT_MEAN_SQUARE to get the rms value within each tile; |
| * L_STANDARD_DEVIATION to get the standard dev. from the average |
| * within each tile. |
| * (3) If colormapped, converts to 8 bpp gray. |
| */ |
| PIX * |
| pixGetAverageTiled(PIX *pixs, |
| l_int32 sx, |
| l_int32 sy, |
| l_int32 type) |
| { |
| l_int32 i, j, k, m, w, h, wd, hd, d, pos, wplt, wpld, valt; |
| l_uint32 *datat, *datad, *linet, *lined, *startt; |
| l_float64 sumave, summs, ave, meansq, normfact; |
| PIX *pixt, *pixd; |
| |
| PROCNAME("pixGetAverageTiled"); |
| |
| if (!pixs) |
| return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); |
| pixGetDimensions(pixs, &w, &h, &d); |
| if (d != 8 && !pixGetColormap(pixs)) |
| return (PIX *)ERROR_PTR("pixs not 8 bpp or cmapped", procName, NULL); |
| if (sx < 2 || sy < 2) |
| return (PIX *)ERROR_PTR("sx and sy not both > 1", procName, NULL); |
| wd = w / sx; |
| hd = h / sy; |
| if (wd < 1 || hd < 1) |
| return (PIX *)ERROR_PTR("wd or hd == 0", procName, NULL); |
| if (type != L_MEAN_ABSVAL && type != L_ROOT_MEAN_SQUARE && |
| type != L_STANDARD_DEVIATION) |
| return (PIX *)ERROR_PTR("invalid measure type", procName, NULL); |
| |
| pixt = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE); |
| pixd = pixCreate(wd, hd, 8); |
| datat = pixGetData(pixt); |
| wplt = pixGetWpl(pixt); |
| datad = pixGetData(pixd); |
| wpld = pixGetWpl(pixd); |
| normfact = 1. / (l_float64)(sx * sy); |
| for (i = 0; i < hd; i++) { |
| lined = datad + i * wpld; |
| linet = datat + i * sy * wplt; |
| for (j = 0; j < wd; j++) { |
| if (type == L_MEAN_ABSVAL || type == L_STANDARD_DEVIATION) { |
| sumave = 0.0; |
| for (k = 0; k < sy; k++) { |
| startt = linet + k * wplt; |
| for (m = 0; m < sx; m++) { |
| pos = j * sx + m; |
| valt = GET_DATA_BYTE(startt, pos); |
| sumave += valt; |
| } |
| } |
| ave = normfact * sumave; |
| } |
| if (type == L_ROOT_MEAN_SQUARE || type == L_STANDARD_DEVIATION) { |
| summs = 0.0; |
| for (k = 0; k < sy; k++) { |
| startt = linet + k * wplt; |
| for (m = 0; m < sx; m++) { |
| pos = j * sx + m; |
| valt = GET_DATA_BYTE(startt, pos); |
| summs += valt * valt; |
| } |
| } |
| meansq = normfact * summs; |
| } |
| if (type == L_MEAN_ABSVAL) |
| valt = (l_int32)(ave + 0.5); |
| else if (type == L_ROOT_MEAN_SQUARE) |
| valt = (l_int32)(sqrt(meansq) + 0.5); |
| else /* type == L_STANDARD_DEVIATION */ |
| valt = (l_int32)(sqrt(meansq - ave * ave) + 0.5); |
| SET_DATA_BYTE(lined, j, valt); |
| } |
| } |
| |
| pixDestroy(&pixt); |
| return pixd; |
| } |
| |
| |
| /*! |
| * pixGetExtremeValue() |
| * |
| * Input: pixs (8 bpp grayscale, 32 bpp rgb, or colormapped) |
| * factor (subsampling factor; >= 1; ignored if colormapped) |
| * type (L_CHOOSE_MIN or L_CHOOSE_MAX) |
| * &rval (<optional return> red component) |
| * &gval (<optional return> green component) |
| * &bval (<optional return> blue component) |
| * &grayval (<optional return> min gray value) |
| * Return: 0 if OK, 1 on error |
| * |
| * Notes: |
| * (1) If pixs is grayscale, the result is returned in &grayval. |
| * Otherwise, if there is a colormap or d == 32, |
| * each requested color component is returned. At least |
| * one color component (address) must be input. |
| */ |
| l_int32 |
| pixGetExtremeValue(PIX *pixs, |
| l_int32 factor, |
| l_int32 type, |
| l_int32 *prval, |
| l_int32 *pgval, |
| l_int32 *pbval, |
| l_int32 *pgrayval) |
| { |
| l_int32 i, j, w, h, d, wpl; |
| l_int32 val, extval, rval, gval, bval, extrval, extgval, extbval; |
| l_uint32 pixel; |
| l_uint32 *data, *line; |
| PIXCMAP *cmap; |
| |
| PROCNAME("pixGetExtremeValue"); |
| |
| if (!pixs) |
| return ERROR_INT("pixs not defined", procName, 1); |
| |
| cmap = pixGetColormap(pixs); |
| if (cmap) |
| return pixcmapGetExtremeValue(cmap, type, prval, pgval, pbval); |
| |
| pixGetDimensions(pixs, &w, &h, &d); |
| if (type != L_CHOOSE_MIN && type != L_CHOOSE_MAX) |
| return ERROR_INT("invalid type", procName, 1); |
| if (factor < 1) |
| return ERROR_INT("subsampling factor < 1", procName, 1); |
| if (d != 8 && d != 32) |
| return ERROR_INT("pixs not 8 or 32 bpp", procName, 1); |
| if (d == 8 && !pgrayval) |
| return ERROR_INT("can't return result in grayval", procName, 1); |
| if (d == 32 && !prval && !pgval && !pbval) |
| return ERROR_INT("can't return result in r/g/b-val", procName, 1); |
| |
| data = pixGetData(pixs); |
| wpl = pixGetWpl(pixs); |
| if (d == 8) { |
| if (type == L_CHOOSE_MIN) |
| extval = 100000; |
| else /* get max */ |
| extval = 0; |
| |
| for (i = 0; i < h; i += factor) { |
| line = data + i * wpl; |
| for (j = 0; j < w; j += factor) { |
| val = GET_DATA_BYTE(line, j); |
| if ((type == L_CHOOSE_MIN && val < extval) || |
| (type == L_CHOOSE_MAX && val > extval)) |
| extval = val; |
| } |
| } |
| *pgrayval = extval; |
| return 0; |
| } |
| |
| /* 32 bpp rgb */ |
| if (type == L_CHOOSE_MIN) { |
| extrval = 100000; |
| extgval = 100000; |
| extbval = 100000; |
| } |
| else { |
| extrval = 0; |
| extgval = 0; |
| extbval = 0; |
| } |
| for (i = 0; i < h; i += factor) { |
| line = data + i * wpl; |
| for (j = 0; j < w; j += factor) { |
| pixel = line[j]; |
| if (prval) { |
| rval = (pixel >> L_RED_SHIFT) & 0xff; |
| if ((type == L_CHOOSE_MIN && rval < extrval) || |
| (type == L_CHOOSE_MAX && rval > extrval)) |
| extrval = rval; |
| } |
| if (pgval) { |
| gval = (pixel >> L_GREEN_SHIFT) & 0xff; |
| if ((type == L_CHOOSE_MIN && gval < extgval) || |
| (type == L_CHOOSE_MAX && gval > extgval)) |
| extgval = gval; |
| } |
| if (pbval) { |
| bval = (pixel >> L_BLUE_SHIFT) & 0xff; |
| if ((type == L_CHOOSE_MIN && bval < extbval) || |
| (type == L_CHOOSE_MAX && bval > extbval)) |
| extbval = bval; |
| } |
| } |
| } |
| if (prval) *prval = extrval; |
| if (pgval) *pgval = extgval; |
| if (pbval) *pbval = extbval; |
| return 0; |
| } |
| |
| |
| /*! |
| * pixGetMaxValueInRect() |
| * |
| * Input: pixs (8 bpp or 32 bpp grayscale; no color space components) |
| * box (<optional> region; set box = NULL to use entire pixs) |
| * &maxval (<optional return> max value in region) |
| * &xmax (<optional return> x location of max value) |
| * &ymax (<optional return> y location of max value) |
| * Return: 0 if OK, 1 on error |
| * |
| * Notes: |
| * (1) This can be used to find the maximum and its location |
| * in a 2-dimensional histogram, where the x and y directions |
| * represent two color components (e.g., saturation and hue). |
| * (2) Note that here a 32 bpp pixs has pixel values that are simply |
| * numbers. They are not 8 bpp components in a colorspace. |
| */ |
| l_int32 |
| pixGetMaxValueInRect(PIX *pixs, |
| BOX *box, |
| l_uint32 *pmaxval, |
| l_int32 *pxmax, |
| l_int32 *pymax) |
| { |
| l_int32 i, j, w, h, d, wpl, bw, bh; |
| l_int32 xstart, ystart, xend, yend, xmax, ymax; |
| l_uint32 val, maxval; |
| l_uint32 *data, *line; |
| |
| PROCNAME("pixGetMaxValueInRect"); |
| |
| if (!pmaxval && !pxmax && !pymax) |
| return ERROR_INT("nothing to do", procName, 1); |
| if (pmaxval) *pmaxval = 0; |
| if (pxmax) *pxmax = 0; |
| if (pymax) *pymax = 0; |
| if (!pixs) |
| return ERROR_INT("pixs not defined", procName, 1); |
| if (pixGetColormap(pixs) != NULL) |
| return ERROR_INT("pixs has colormap", procName, 1); |
| pixGetDimensions(pixs, &w, &h, &d); |
| if (d != 8 && d != 32) |
| return ERROR_INT("pixs not 8 or 32 bpp", procName, 1); |
| |
| xstart = ystart = 0; |
| xend = w - 1; |
| yend = h - 1; |
| if (box) { |
| boxGetGeometry(box, &xstart, &ystart, &bw, &bh); |
| xend = xstart + bw - 1; |
| yend = ystart + bh - 1; |
| } |
| |
| data = pixGetData(pixs); |
| wpl = pixGetWpl(pixs); |
| maxval = 0; |
| xmax = ymax = 0; |
| for (i = ystart; i <= yend; i++) { |
| line = data + i * wpl; |
| for (j = xstart; j <= xend; j++) { |
| if (d == 8) |
| val = GET_DATA_BYTE(line, j); |
| else /* d == 32 */ |
| val = line[j]; |
| if (val > maxval) { |
| maxval = val; |
| xmax = j; |
| ymax = i; |
| } |
| } |
| } |
| if (maxval == 0) { /* no counts; pick the center of the rectangle */ |
| xmax = (xstart + xend) / 2; |
| ymax = (ystart + yend) / 2; |
| } |
| |
| if (pmaxval) *pmaxval = maxval; |
| if (pxmax) *pxmax = xmax; |
| if (pymax) *pymax = ymax; |
| return 0; |
| } |
| |
| |
| /*-------------------------------------------------------------* |
| * Pixelwise aligned statistics * |
| *-------------------------------------------------------------*/ |
| /*! |
| * pixaGetAlignedStats() |
| * |
| * Input: pixa (of identically sized, 8 bpp pix; not cmapped) |
| * type (L_MEAN_ABSVAL, L_MEDIAN_VAL, L_MODE_VAL, L_MODE_COUNT) |
| * nbins (of histogram for median and mode; ignored for mean) |
| * thresh (on histogram for mode val; ignored for all other types) |
| * Return: pix (with pixelwise aligned stats), or null on error. |
| * |
| * Notes: |
| * (1) Each pixel in the returned pix represents an average |
| * (or median, or mode) over the corresponding pixels in each |
| * pix in the pixa. |
| * (2) The @thresh parameter works with L_MODE_VAL only, and |
| * sets a minimum occupancy of the mode bin. |
| * If the occupancy of the mode bin is less than @thresh, the |
| * mode value is returned as 0. To always return the actual |
| * mode value, set @thresh = 0. See pixGetRowStats(). |
| */ |
| PIX * |
| pixaGetAlignedStats(PIXA *pixa, |
| l_int32 type, |
| l_int32 nbins, |
| l_int32 thresh) |
| { |
| l_int32 j, n, w, h, d; |
| l_float32 *colvect; |
| PIX *pixt, *pixd; |
| |
| PROCNAME("pixaGetAlignedStats"); |
| |
| if (!pixa) |
| return (PIX *)ERROR_PTR("pixa not defined", procName, NULL); |
| if (type != L_MEAN_ABSVAL && type != L_MEDIAN_VAL && |
| type != L_MODE_VAL && type != L_MODE_COUNT) |
| return (PIX *)ERROR_PTR("invalid type", procName, NULL); |
| n = pixaGetCount(pixa); |
| if (n == 0) |
| return (PIX *)ERROR_PTR("no pix in pixa", procName, NULL); |
| pixaGetPixDimensions(pixa, 0, &w, &h, &d); |
| if (d != 8) |
| return (PIX *)ERROR_PTR("pix not 8 bpp", procName, NULL); |
| |
| pixd = pixCreate(w, h, 8); |
| pixt = pixCreate(n, h, 8); |
| colvect = (l_float32 *)CALLOC(h, sizeof(l_float32)); |
| for (j = 0; j < w; j++) { |
| pixaExtractColumnFromEachPix(pixa, j, pixt); |
| pixGetRowStats(pixt, type, nbins, thresh, colvect); |
| pixSetPixelColumn(pixd, j, colvect); |
| } |
| |
| FREE(colvect); |
| pixDestroy(&pixt); |
| return pixd; |
| } |
| |
| |
| /*! |
| * pixaExtractColumnFromEachPix() |
| * |
| * Input: pixa (of identically sized, 8 bpp; not cmapped) |
| * col (column index) |
| * pixd (pix into which each column is inserted) |
| * Return: 0 if OK, 1 on error |
| */ |
| l_int32 |
| pixaExtractColumnFromEachPix(PIXA *pixa, |
| l_int32 col, |
| PIX *pixd) |
| { |
| l_int32 i, k, n, w, h, ht, val, wplt, wpld; |
| l_uint32 *datad, *datat; |
| PIX *pixt; |
| |
| PROCNAME("pixaExtractColumnFromEachPix"); |
| |
| if (!pixa) |
| return ERROR_INT("pixa not defined", procName, 1); |
| if (!pixd || pixGetDepth(pixd) != 8) |
| return ERROR_INT("pixa not defined or not 8 bpp", procName, 1); |
| n = pixaGetCount(pixa); |
| pixGetDimensions(pixd, &w, &h, NULL); |
| if (n != w) |
| return ERROR_INT("pix width != n", procName, 1); |
| pixt = pixaGetPix(pixa, 0, L_CLONE); |
| wplt = pixGetWpl(pixt); |
| pixGetDimensions(pixt, NULL, &ht, NULL); |
| pixDestroy(&pixt); |
| if (h != ht) |
| return ERROR_INT("pixd height != column height", procName, 1); |
| |
| datad = pixGetData(pixd); |
| wpld = pixGetWpl(pixd); |
| for (k = 0; k < n; k++) { |
| pixt = pixaGetPix(pixa, k, L_CLONE); |
| datat = pixGetData(pixt); |
| for (i = 0; i < h; i++) { |
| val = GET_DATA_BYTE(datat, col); |
| SET_DATA_BYTE(datad + i * wpld, k, val); |
| datat += wplt; |
| } |
| pixDestroy(&pixt); |
| } |
| |
| return 0; |
| } |
| |
| |
| /*! |
| * pixGetRowStats() |
| * |
| * Input: pixs (8 bpp; not cmapped) |
| * type (L_MEAN_ABSVAL, L_MEDIAN_VAL, L_MODE_VAL, L_MODE_COUNT) |
| * nbins (of histogram for median and mode; ignored for mean) |
| * thresh (on histogram for mode; ignored for mean and median) |
| * colvect (vector of results gathered across the rows of pixs) |
| * Return: 0 if OK, 1 on error |
| * |
| * Notes: |
| * (1) This computes a column vector of statistics using each |
| * row of a Pix. The result is put in @colvect. |
| * (2) The @thresh parameter works with L_MODE_VAL only, and |
| * sets a minimum occupancy of the mode bin. |
| * If the occupancy of the mode bin is less than @thresh, the |
| * mode value is returned as 0. To always return the actual |
| * mode value, set @thresh = 0. |
| * (3) What is the meaning of this @thresh parameter? |
| * For each row, the total count in the histogram is w, the |
| * image width. So @thresh, relative to w, gives a measure |
| * of the ratio of the bin width to the width of the distribution. |
| * The larger @thresh, the narrower the distribution must be |
| * for the mode value to be returned (instead of returning 0). |
| * (4) If the Pix consists of a set of corresponding columns, |
| * one for each Pix in a Pixa, the width of the Pix is the |
| * number of Pix in the Pixa and the column vector can |
| * be stored as a column in a Pix of the same size as |
| * each Pix in the Pixa. |
| */ |
| l_int32 |
| pixGetRowStats(PIX *pixs, |
| l_int32 type, |
| l_int32 nbins, |
| l_int32 thresh, |
| l_float32 *colvect) |
| { |
| l_int32 i, j, k, w, h, val, wpls, sum, target, max, modeval; |
| l_int32 *histo, *gray2bin, *bin2gray; |
| l_uint32 *lines, *datas; |
| |
| PROCNAME("pixGetRowStats"); |
| |
| if (!pixs || pixGetDepth(pixs) != 8) |
| return ERROR_INT("pixs not defined or not 8 bpp", procName, 1); |
| if (!colvect) |
| return ERROR_INT("colvect not defined", procName, 1); |
| if (type != L_MEAN_ABSVAL && type != L_MEDIAN_VAL && |
| type != L_MODE_VAL && type != L_MODE_COUNT) |
| return ERROR_INT("invalid type", procName, 1); |
| if (type != L_MEAN_ABSVAL && (nbins < 1 || nbins > 256)) |
| return ERROR_INT("invalid nbins", procName, 1); |
| pixGetDimensions(pixs, &w, &h, NULL); |
| |
| datas = pixGetData(pixs); |
| wpls = pixGetWpl(pixs); |
| if (type == L_MEAN_ABSVAL) { |
| for (i = 0; i < h; i++) { |
| sum = 0; |
| lines = datas + i * wpls; |
| for (j = 0; j < w; j++) |
| sum += GET_DATA_BYTE(lines, j); |
| colvect[i] = (l_float32)sum / (l_float32)w; |
| } |
| return 0; |
| } |
| |
| /* We need a histogram; binwidth ~ 256 / nbins */ |
| histo = (l_int32 *)CALLOC(nbins, sizeof(l_int32)); |
| gray2bin = (l_int32 *)CALLOC(256, sizeof(l_int32)); |
| bin2gray = (l_int32 *)CALLOC(nbins, sizeof(l_int32)); |
| for (i = 0; i < 256; i++) /* gray value --> histo bin */ |
| gray2bin[i] = (i * nbins) / 256; |
| for (i = 0; i < nbins; i++) /* histo bin --> gray value */ |
| bin2gray[i] = (i * 255 + 128) / nbins; |
| |
| for (i = 0; i < h; i++) { |
| lines = datas + i * wpls; |
| for (k = 0; k < nbins; k++) |
| histo[k] = 0; |
| for (j = 0; j < w; j++) { |
| val = GET_DATA_BYTE(lines, j); |
| histo[gray2bin[val]]++; |
| } |
| |
| if (type == L_MEDIAN_VAL) { |
| sum = 0; |
| target = w / 2; |
| for (k = 0; k < nbins; k++) { |
| sum += histo[k]; |
| if (sum >= target) { |
| colvect[i] = bin2gray[k]; |
| break; |
| } |
| } |
| } |
| else if (type == L_MODE_VAL) { |
| max = 0; |
| modeval = 0; |
| for (k = 0; k < nbins; k++) { |
| if (histo[k] > max) { |
| max = histo[k]; |
| modeval = k; |
| } |
| } |
| if (max < thresh) |
| colvect[i] = 0; |
| else |
| colvect[i] = bin2gray[modeval]; |
| } |
| else { /* type == L_MODE_COUNT */ |
| max = 0; |
| modeval = 0; |
| for (k = 0; k < nbins; k++) { |
| if (histo[k] > max) { |
| max = histo[k]; |
| modeval = k; |
| } |
| } |
| colvect[i] = max; |
| } |
| } |
| |
| FREE(histo); |
| FREE(gray2bin); |
| FREE(bin2gray); |
| return 0; |
| } |
| |
| |
| /*! |
| * pixGetColumnStats() |
| * |
| * Input: pixs (8 bpp; not cmapped) |
| * type (L_MEAN_ABSVAL, L_MEDIAN_VAL, L_MODE_VAL, L_MODE_COUNT) |
| * nbins (of histogram for median and mode; ignored for mean) |
| * thresh (on histogram for mode val; ignored for all other types) |
| * rowvect (vector of results gathered down the columns of pixs) |
| * Return: 0 if OK, 1 on error |
| * |
| * Notes: |
| * (1) This computes a row vector of statistics using each |
| * column of a Pix. The result is put in @rowvect. |
| * (2) The @thresh parameter works with L_MODE_VAL only, and |
| * sets a minimum occupancy of the mode bin. |
| * If the occupancy of the mode bin is less than @thresh, the |
| * mode value is returned as 0. To always return the actual |
| * mode value, set @thresh = 0. |
| * (3) What is the meaning of this @thresh parameter? |
| * For each column, the total count in the histogram is h, the |
| * image height. So @thresh, relative to h, gives a measure |
| * of the ratio of the bin width to the width of the distribution. |
| * The larger @thresh, the narrower the distribution must be |
| * for the mode value to be returned (instead of returning 0). |
| */ |
| l_int32 |
| pixGetColumnStats(PIX *pixs, |
| l_int32 type, |
| l_int32 nbins, |
| l_int32 thresh, |
| l_float32 *rowvect) |
| { |
| l_int32 i, j, k, w, h, val, wpls, sum, target, max, modeval; |
| l_int32 *histo, *gray2bin, *bin2gray; |
| l_uint32 *datas; |
| |
| PROCNAME("pixGetColumnStats"); |
| |
| if (!pixs || pixGetDepth(pixs) != 8) |
| return ERROR_INT("pixs not defined or not 8 bpp", procName, 1); |
| if (!rowvect) |
| return ERROR_INT("rowvect not defined", procName, 1); |
| if (type != L_MEAN_ABSVAL && type != L_MEDIAN_VAL && |
| type != L_MODE_VAL && type != L_MODE_COUNT) |
| return ERROR_INT("invalid type", procName, 1); |
| if (type != L_MEAN_ABSVAL && (nbins < 1 || nbins > 256)) |
| return ERROR_INT("invalid nbins", procName, 1); |
| pixGetDimensions(pixs, &w, &h, NULL); |
| |
| datas = pixGetData(pixs); |
| wpls = pixGetWpl(pixs); |
| if (type == L_MEAN_ABSVAL) { |
| for (j = 0; j < w; j++) { |
| sum = 0; |
| for (i = 0; i < h; i++) |
| sum += GET_DATA_BYTE(datas + i * wpls, j); |
| rowvect[j] = (l_float32)sum / (l_float32)h; |
| } |
| return 0; |
| } |
| |
| /* We need a histogram; binwidth ~ 256 / nbins */ |
| histo = (l_int32 *)CALLOC(nbins, sizeof(l_int32)); |
| gray2bin = (l_int32 *)CALLOC(256, sizeof(l_int32)); |
| bin2gray = (l_int32 *)CALLOC(nbins, sizeof(l_int32)); |
| for (i = 0; i < 256; i++) /* gray value --> histo bin */ |
| gray2bin[i] = (i * nbins) / 256; |
| for (i = 0; i < nbins; i++) /* histo bin --> gray value */ |
| bin2gray[i] = (i * 255 + 128) / nbins; |
| |
| for (j = 0; j < w; j++) { |
| for (i = 0; i < h; i++) { |
| val = GET_DATA_BYTE(datas + i * wpls, j); |
| histo[gray2bin[val]]++; |
| } |
| |
| if (type == L_MEDIAN_VAL) { |
| sum = 0; |
| target = h / 2; |
| for (k = 0; k < nbins; k++) { |
| sum += histo[k]; |
| if (sum >= target) { |
| rowvect[j] = bin2gray[k]; |
| break; |
| } |
| } |
| } |
| else if (type == L_MODE_VAL) { |
| max = 0; |
| modeval = 0; |
| for (k = 0; k < nbins; k++) { |
| if (histo[k] > max) { |
| max = histo[k]; |
| modeval = k; |
| } |
| } |
| if (max < thresh) |
| rowvect[j] = 0; |
| else |
| rowvect[j] = bin2gray[modeval]; |
| } |
| else { /* type == L_MODE_COUNT */ |
| max = 0; |
| modeval = 0; |
| for (k = 0; k < nbins; k++) { |
| if (histo[k] > max) { |
| max = histo[k]; |
| modeval = k; |
| } |
| } |
| rowvect[j] = max; |
| } |
| for (k = 0; k < nbins; k++) |
| histo[k] = 0; |
| } |
| |
| FREE(histo); |
| FREE(gray2bin); |
| FREE(bin2gray); |
| return 0; |
| } |
| |
| |
| /*! |
| * pixSetPixelColumn() |
| * |
| * Input: pix (8 bpp; not cmapped) |
| * col (column index) |
| * colvect (vector of floats) |
| * Return: 0 if OK, 1 on error |
| */ |
| l_int32 |
| pixSetPixelColumn(PIX *pix, |
| l_int32 col, |
| l_float32 *colvect) |
| { |
| l_int32 i, w, h, wpl; |
| l_uint32 *data; |
| |
| PROCNAME("pixSetCPixelColumn"); |
| |
| if (!pix || pixGetDepth(pix) != 8) |
| return ERROR_INT("pix not defined or not 8 bpp", procName, 1); |
| if (!colvect) |
| return ERROR_INT("colvect not defined", procName, 1); |
| pixGetDimensions(pix, &w, &h, NULL); |
| if (col < 0 || col > w) |
| return ERROR_INT("invalid col", procName, 1); |
| |
| data = pixGetData(pix); |
| wpl = pixGetWpl(pix); |
| for (i = 0; i < h; i++) |
| SET_DATA_BYTE(data + i * wpl, col, (l_int32)colvect[i]); |
| |
| return 0; |
| } |
| |
| |
| /*-------------------------------------------------------------* |
| * Foreground/background estimation * |
| *-------------------------------------------------------------*/ |
| /*! |
| * pixThresholdForFgBg() |
| * |
| * Input: pixs (any depth; cmapped ok) |
| * factor (subsampling factor; integer >= 1) |
| * thresh (threshold for generating foreground mask) |
| * &fgval (<optional return> average foreground value) |
| * &bgval (<optional return> average background value) |
| * Return: 0 if OK, 1 on error |
| */ |
| l_int32 |
| pixThresholdForFgBg(PIX *pixs, |
| l_int32 factor, |
| l_int32 thresh, |
| l_int32 *pfgval, |
| l_int32 *pbgval) |
| { |
| l_float32 fval; |
| PIX *pixg, *pixm; |
| |
| PROCNAME("pixThresholdForFgBg"); |
| |
| if (!pixs) |
| return ERROR_INT("pixs not defined", procName, 1); |
| |
| /* Generate a subsampled 8 bpp version and a mask over the fg */ |
| pixg = pixConvertTo8BySampling(pixs, factor, 0); |
| pixm = pixThresholdToBinary(pixg, thresh); |
| |
| if (pfgval) { |
| pixGetAverageMasked(pixg, pixm, 0, 0, 1, L_MEAN_ABSVAL, &fval); |
| *pfgval = (l_int32)(fval + 0.5); |
| } |
| |
| if (pbgval) { |
| pixInvert(pixm, pixm); |
| pixGetAverageMasked(pixg, pixm, 0, 0, 1, L_MEAN_ABSVAL, &fval); |
| *pbgval = (l_int32)(fval + 0.5); |
| } |
| |
| pixDestroy(&pixg); |
| pixDestroy(&pixm); |
| return 0; |
| } |
| |
| |
| /*! |
| * pixSplitDistributionFgBg() |
| * |
| * Input: pixs (any depth; cmapped ok) |
| * scorefract (fraction of the max score, used to determine |
| * the range over which the histogram min is searched) |
| * factor (subsampling factor; integer >= 1) |
| * &thresh (<optional return> best threshold for separating) |
| * &fgval (<optional return> average foreground value) |
| * &bgval (<optional return> average background value) |
| * debugflag (1 for plotting of distribution and split point) |
| * Return: 0 if OK, 1 on error |
| * |
| * Notes: |
| * (1) See numaSplitDistribution() for details on the underlying |
| * method of choosing a threshold. |
| */ |
| l_int32 |
| pixSplitDistributionFgBg(PIX *pixs, |
| l_float32 scorefract, |
| l_int32 factor, |
| l_int32 *pthresh, |
| l_int32 *pfgval, |
| l_int32 *pbgval, |
| l_int32 debugflag) |
| { |
| l_int32 thresh; |
| l_float32 avefg, avebg, maxnum; |
| GPLOT *gplot; |
| NUMA *na, *nascore, *nax, *nay; |
| PIX *pixg; |
| |
| PROCNAME("pixSplitDistributionFgBg"); |
| |
| if (pthresh) *pthresh = 0; |
| if (pfgval) *pfgval = 0; |
| if (pbgval) *pbgval = 0; |
| if (!pixs) |
| return ERROR_INT("pixs not defined", procName, 1); |
| |
| /* Generate a subsampled 8 bpp version */ |
| pixg = pixConvertTo8BySampling(pixs, factor, 0); |
| |
| /* Make the fg/bg estimates */ |
| na = pixGetGrayHistogram(pixg, 1); |
| if (debugflag) { |
| numaSplitDistribution(na, scorefract, &thresh, &avefg, &avebg, |
| NULL, NULL, &nascore); |
| numaDestroy(&nascore); |
| } |
| else |
| numaSplitDistribution(na, scorefract, &thresh, &avefg, &avebg, |
| NULL, NULL, NULL); |
| |
| if (pthresh) *pthresh = thresh; |
| if (pfgval) *pfgval = (l_int32)(avefg + 0.5); |
| if (pbgval) *pbgval = (l_int32)(avebg + 0.5); |
| |
| if (debugflag) { |
| gplot = gplotCreate("junk_histplot", GPLOT_X11, "Histogram", |
| "Grayscale value", "Number of pixels"); |
| gplotAddPlot(gplot, NULL, na, GPLOT_LINES, NULL); |
| nax = numaMakeConstant(thresh, 2); |
| numaGetMax(na, &maxnum, NULL); |
| nay = numaMakeConstant(0, 2); |
| numaReplaceNumber(nay, 1, (l_int32)(0.5 * maxnum)); |
| gplotAddPlot(gplot, nax, nay, GPLOT_LINES, NULL); |
| gplotMakeOutput(gplot); |
| gplotDestroy(&gplot); |
| numaDestroy(&nax); |
| numaDestroy(&nay); |
| } |
| |
| pixDestroy(&pixg); |
| numaDestroy(&na); |
| return 0; |
| } |
| |
| |
| /*-------------------------------------------------------------* |
| * Measurement of properties * |
| *-------------------------------------------------------------*/ |
| /*! |
| * pixaFindDimensions() |
| * |
| * Input: pixa |
| * &naw (<optional return> numa of pix widths) |
| * &nah (<optional return> numa of pix heights) |
| * Return: 0 if OK, 1 on error |
| */ |
| l_int32 |
| pixaFindDimensions(PIXA *pixa, |
| NUMA **pnaw, |
| NUMA **pnah) |
| { |
| l_int32 i, n, w, h; |
| PIX *pixt; |
| |
| PROCNAME("pixaFindDimensions"); |
| |
| if (!pixa) |
| return ERROR_INT("pixa not defined", procName, 1); |
| if (!pnaw && !pnah) |
| return 0; |
| |
| n = pixaGetCount(pixa); |
| if (pnaw) *pnaw = numaCreate(n); |
| if (pnah) *pnah = numaCreate(n); |
| for (i = 0; i < n; i++) { |
| pixt = pixaGetPix(pixa, i, L_CLONE); |
| pixGetDimensions(pixt, &w, &h, NULL); |
| if (pnaw) |
| numaAddNumber(*pnaw, w); |
| if (pnah) |
| numaAddNumber(*pnah, h); |
| pixDestroy(&pixt); |
| } |
| return 0; |
| } |
| |
| |
| /*! |
| * pixaFindAreaPerimRatio() |
| * |
| * Input: pixa (of 1 bpp pix) |
| * Return: na (of area/perimeter ratio for each pix), or null on error |
| * |
| * Notes: |
| * (1) This is typically used for a pixa consisting of |
| * 1 bpp connected components. |
| */ |
| NUMA * |
| pixaFindAreaPerimRatio(PIXA *pixa) |
| { |
| l_int32 i, n; |
| l_int32 *tab; |
| l_float32 fract; |
| NUMA *na; |
| PIX *pixt; |
| |
| PROCNAME("pixaFindAreaPerimRatio"); |
| |
| if (!pixa) |
| return (NUMA *)ERROR_PTR("pixa not defined", procName, NULL); |
| |
| n = pixaGetCount(pixa); |
| na = numaCreate(n); |
| tab = makePixelSumTab8(); |
| for (i = 0; i < n; i++) { |
| pixt = pixaGetPix(pixa, i, L_CLONE); |
| pixFindAreaPerimRatio(pixt, tab, &fract); |
| numaAddNumber(na, fract); |
| pixDestroy(&pixt); |
| } |
| FREE(tab); |
| return na; |
| } |
| |
| |
| /*! |
| * pixFindAreaPerimRatio() |
| * |
| * Input: pixs (1 bpp) |
| * tab (<optional> pixel sum table, can be NULL) |
| * &fract (<return> area/perimeter ratio) |
| * Return: 0 if OK, 1 on error |
| * |
| * Notes: |
| * (1) The area is the number of fg pixels that are not on the |
| * boundary (i.e., not 8-connected to a bg pixel), and the |
| * perimeter is the number of boundary fg pixels. |
| * (2) This is typically used for a pixa consisting of |
| * 1 bpp connected components. |
| */ |
| l_int32 |
| pixFindAreaPerimRatio(PIX *pixs, |
| l_int32 *tab, |
| l_float32 *pfract) |
| { |
| l_int32 *tab8; |
| l_int32 nin, nbound; |
| PIX *pixt; |
| |
| PROCNAME("pixFindAreaPerimRatio"); |
| |
| if (!pfract) |
| return ERROR_INT("&fract not defined", procName, 1); |
| *pfract = 0.0; |
| if (!pixs || pixGetDepth(pixs) != 1) |
| return ERROR_INT("pixs not defined or not 1 bpp", procName, 1); |
| |
| if (!tab) |
| tab8 = makePixelSumTab8(); |
| else |
| tab8 = tab; |
| |
| pixt = pixErodeBrick(NULL, pixs, 3, 3); |
| pixCountPixels(pixt, &nin, tab8); |
| pixXor(pixt, pixt, pixs); |
| pixCountPixels(pixt, &nbound, tab8); |
| *pfract = (l_float32)nin / (l_float32)nbound; |
| |
| if (!tab) |
| FREE(tab8); |
| pixDestroy(&pixt); |
| return 0; |
| } |
| |
| |
| /*! |
| * pixaFindPerimSizeRatio() |
| * |
| * Input: pixa (of 1 bpp pix) |
| * Return: na (of fg perimeter/(w*h) ratio for each pix), or null on error |
| * |
| * Notes: |
| * (1) This is typically used for a pixa consisting of |
| * 1 bpp connected components. |
| */ |
| NUMA * |
| pixaFindPerimSizeRatio(PIXA *pixa) |
| { |
| l_int32 i, n; |
| l_int32 *tab; |
| l_float32 ratio; |
| NUMA *na; |
| PIX *pixt; |
| |
| PROCNAME("pixaFindPerimSizeRatio"); |
| |
| if (!pixa) |
| return (NUMA *)ERROR_PTR("pixa not defined", procName, NULL); |
| |
| n = pixaGetCount(pixa); |
| na = numaCreate(n); |
| tab = makePixelSumTab8(); |
| for (i = 0; i < n; i++) { |
| pixt = pixaGetPix(pixa, i, L_CLONE); |
| pixFindPerimSizeRatio(pixt, tab, &ratio); |
| numaAddNumber(na, ratio); |
| pixDestroy(&pixt); |
| } |
| FREE(tab); |
| return na; |
| } |
| |
| |
| /*! |
| * pixFindPerimSizeRatio() |
| * |
| * Input: pixs (1 bpp) |
| * tab (<optional> pixel sum table, can be NULL) |
| * &ratio (<return> perimeter/size ratio) |
| * Return: 0 if OK, 1 on error |
| * |
| * Notes: |
| * (1) The size is the sum of the width and height of the pix, |
| * and the perimeter is the number of boundary fg pixels. |
| * (2) This has a large value for dendritic, fractal-like components |
| * with highly irregular boundaries. |
| * (3) This is typically used for a single connected component. |
| */ |
| l_int32 |
| pixFindPerimSizeRatio(PIX *pixs, |
| l_int32 *tab, |
| l_float32 *pratio) |
| { |
| l_int32 *tab8; |
| l_int32 w, h, nbound; |
| PIX *pixt; |
| |
| PROCNAME("pixFindPerimSizeRatio"); |
| |
| if (!pratio) |
| return ERROR_INT("&ratio not defined", procName, 1); |
| *pratio = 0.0; |
| if (!pixs || pixGetDepth(pixs) != 1) |
| return ERROR_INT("pixs not defined or not 1 bpp", procName, 1); |
| |
| if (!tab) |
| tab8 = makePixelSumTab8(); |
| else |
| tab8 = tab; |
| |
| pixt = pixErodeBrick(NULL, pixs, 3, 3); |
| pixXor(pixt, pixt, pixs); |
| pixCountPixels(pixt, &nbound, tab8); |
| pixGetDimensions(pixs, &w, &h, NULL); |
| *pratio = (l_float32)nbound / (l_float32)(w + h); |
| |
| if (!tab) |
| FREE(tab8); |
| pixDestroy(&pixt); |
| return 0; |
| } |
| |
| |
| /*! |
| * pixaFindAreaFraction() |
| * |
| * Input: pixa (of 1 bpp pix) |
| * Return: na (of area fractions for each pix), or null on error |
| * |
| * Notes: |
| * (1) This is typically used for a pixa consisting of |
| * 1 bpp connected components. |
| */ |
| NUMA * |
| pixaFindAreaFraction(PIXA *pixa) |
| { |
| l_int32 i, n; |
| l_int32 *tab; |
| l_float32 fract; |
| NUMA *na; |
| PIX *pixt; |
| |
| PROCNAME("pixaFindAreaFraction"); |
| |
| if (!pixa) |
| return (NUMA *)ERROR_PTR("pixa not defined", procName, NULL); |
| |
| n = pixaGetCount(pixa); |
| na = numaCreate(n); |
| tab = makePixelSumTab8(); |
| for (i = 0; i < n; i++) { |
| pixt = pixaGetPix(pixa, i, L_CLONE); |
| pixFindAreaFraction(pixt, tab, &fract); |
| numaAddNumber(na, fract); |
| pixDestroy(&pixt); |
| } |
| FREE(tab); |
| return na; |
| } |
| |
| |
| /*! |
| * pixFindAreaFraction() |
| * |
| * Input: pixs (1 bpp) |
| * tab (<optional> pixel sum table, can be NULL) |
| * &fract (<return> fg area/size ratio) |
| * Return: 0 if OK, 1 on error |
| * |
| * Notes: |
| * (1) This finds the ratio of the number of fg pixels to the |
| * size of the pix (w * h). It is typically used for a |
| * single connected component. |
| */ |
| l_int32 |
| pixFindAreaFraction(PIX *pixs, |
| l_int32 *tab, |
| l_float32 *pfract) |
| { |
| l_int32 w, h, d, sum; |
| l_int32 *tab8; |
| |
| PROCNAME("pixFindAreaFraction"); |
| |
| if (!pfract) |
| return ERROR_INT("&fract not defined", procName, 1); |
| *pfract = 0.0; |
| pixGetDimensions(pixs, &w, &h, &d); |
| if (!pixs || d != 1) |
| return ERROR_INT("pixs not defined or not 1 bpp", procName, 1); |
| |
| if (!tab) |
| tab8 = makePixelSumTab8(); |
| else |
| tab8 = tab; |
| |
| pixCountPixels(pixs, &sum, tab8); |
| *pfract = (l_float32)sum / (l_float32)(w * h); |
| |
| if (!tab) |
| FREE(tab8); |
| return 0; |
| } |
| |
| |
| /*! |
| * pixaFindWidthHeightRatio() |
| * |
| * Input: pixa (of 1 bpp pix) |
| * Return: na (of width/height ratios for each pix), or null on error |
| * |
| * Notes: |
| * (1) This is typically used for a pixa consisting of |
| * 1 bpp connected components. |
| */ |
| NUMA * |
| pixaFindWidthHeightRatio(PIXA *pixa) |
| { |
| l_int32 i, n, w, h; |
| NUMA *na; |
| PIX *pixt; |
| |
| PROCNAME("pixaFindWidthHeightRatio"); |
| |
| if (!pixa) |
| return (NUMA *)ERROR_PTR("pixa not defined", procName, NULL); |
| |
| n = pixaGetCount(pixa); |
| na = numaCreate(n); |
| for (i = 0; i < n; i++) { |
| pixt = pixaGetPix(pixa, i, L_CLONE); |
| pixGetDimensions(pixt, &w, &h, NULL); |
| numaAddNumber(na, (l_float32)w / (l_float32)h); |
| pixDestroy(&pixt); |
| } |
| return na; |
| } |
| |
| |
| /*! |
| * pixaFindWidthHeightProduct() |
| * |
| * Input: pixa (of 1 bpp pix) |
| * Return: na (of width*height products for each pix), or null on error |
| * |
| * Notes: |
| * (1) This is typically used for a pixa consisting of |
| * 1 bpp connected components. |
| */ |
| NUMA * |
| pixaFindWidthHeightProduct(PIXA *pixa) |
| { |
| l_int32 i, n, w, h; |
| NUMA *na; |
| PIX *pixt; |
| |
| PROCNAME("pixaFindWidthHeightProduct"); |
| |
| if (!pixa) |
| return (NUMA *)ERROR_PTR("pixa not defined", procName, NULL); |
| |
| n = pixaGetCount(pixa); |
| na = numaCreate(n); |
| for (i = 0; i < n; i++) { |
| pixt = pixaGetPix(pixa, i, L_CLONE); |
| pixGetDimensions(pixt, &w, &h, NULL); |
| numaAddNumber(na, w * h); |
| pixDestroy(&pixt); |
| } |
| return na; |
| } |
| |
| |
| /*-------------------------------------------------------------* |
| * Extract rectangular region * |
| *-------------------------------------------------------------*/ |
| /*! |
| * pixClipRectangle() |
| * |
| * Input: pixs |
| * box (requested clipping region; const) |
| * &boxc (<optional return> actual box of clipped region) |
| * Return: clipped pix, or null on error or if rectangle |
| * doesn't intersect pixs |
| * |
| * Notes: |
| * |
| * This should be simple, but there are choices to be made. |
| * The box is defined relative to the pix coordinates. However, |
| * if the box is not contained within the pix, we have two choices: |
| * |
| * (1) clip the box to the pix |
| * (2) make a new pix equal to the full box dimensions, |
| * but let rasterop do the clipping and positioning |
| * of the src with respect to the dest |
| * |
| * Choice (2) immediately brings up the problem of what pixel values |
| * to use that were not taken from the src. For example, on a grayscale |
| * image, do you want the pixels not taken from the src to be black |
| * or white or something else? To implement choice 2, one needs to |
| * specify the color of these extra pixels. |
| * |
| * So we adopt (1), and clip the box first, if necessary, |
| * before making the dest pix and doing the rasterop. But there |
| * is another issue to consider. If you want to paste the |
| * clipped pix back into pixs, it must be properly aligned, and |
| * it is necessary to use the clipped box for alignment. |
| * Accordingly, this function has a third (optional) argument, which is |
| * the input box clipped to the src pix. |
| */ |
| PIX * |
| pixClipRectangle(PIX *pixs, |
| BOX *box, |
| BOX **pboxc) |
| { |
| l_int32 w, h, d, bx, by, bw, bh; |
| BOX *boxc; |
| PIX *pixd; |
| |
| PROCNAME("pixClipRectangle"); |
| |
| if (pboxc) |
| *pboxc = NULL; |
| if (!pixs) |
| return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); |
| if (!box) |
| return (PIX *)ERROR_PTR("box not defined", procName, NULL); |
| |
| /* Clip the input box to the pix */ |
| pixGetDimensions(pixs, &w, &h, &d); |
| if ((boxc = boxClipToRectangle(box, w, h)) == NULL) { |
| L_WARNING("box doesn't overlap pix", procName); |
| return NULL; |
| } |
| boxGetGeometry(boxc, &bx, &by, &bw, &bh); |
| |
| /* Extract the block */ |
| if ((pixd = pixCreate(bw, bh, d)) == NULL) |
| return (PIX *)ERROR_PTR("pixd not made", procName, NULL); |
| pixCopyResolution(pixd, pixs); |
| pixCopyColormap(pixd, pixs); |
| pixRasterop(pixd, 0, 0, bw, bh, PIX_SRC, pixs, bx, by); |
| |
| if (pboxc) |
| *pboxc = boxc; |
| else |
| boxDestroy(&boxc); |
| |
| return pixd; |
| } |
| |
| |
| /*! |
| * pixClipMasked() |
| * |
| * Input: pixs (1, 2, 4, 8, 16, 32 bpp; colormap ok) |
| * pixm (clipping mask, 1 bpp) |
| * x, y (origin of clipping mask relative to pixs) |
| * outval (val to use for pixels that are outside the mask) |
| * Return: pixd, (clipped pix) or null on error or if pixm doesn't |
| * intersect pixs |
| * |
| * Notes: |
| * (1) If pixs has a colormap, it is preserved in pixd. |
| * (2) The depth of pixd is the same as that of pixs. |
| * (3) If the depth of pixs is 1, use @outval = 0 for white background |
| * and 1 for black; otherwise, use the max value for white |
| * and 0 for black. If pixs has a colormap, the max value for |
| * @outval is 0xffffffff; otherwise, it is 2^d - 1. |
| * (4) When using 1 bpp pixs, this is a simple clip and |
| * blend operation. For example, if both pix1 and pix2 are |
| * black text on white background, and you want to OR the |
| * fg on the two images, let pixm be the inverse of pix2. |
| * Then the operation takes all of pix1 that's in the bg of |
| * pix2, and for the remainder (which are the pixels |
| * corresponding to the fg of the pix2), paint them black |
| * (1) in pix1. The function call looks like |
| * pixClipMasked(pix2, pixInvert(pix1, pix1), x, y, 1); |
| */ |
| PIX * |
| pixClipMasked(PIX *pixs, |
| PIX *pixm, |
| l_int32 x, |
| l_int32 y, |
| l_uint32 outval) |
| { |
| l_int32 wm, hm, d, index, rval, gval, bval; |
| l_uint32 pixel; |
| BOX *box; |
| PIX *pixmi, *pixd; |
| PIXCMAP *cmap; |
| |
| PROCNAME("pixClipMasked"); |
| |
| if (!pixs) |
| return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); |
| if (!pixm || pixGetDepth(pixm) != 1) |
| return (PIX *)ERROR_PTR("pixm undefined or not 1 bpp", procName, NULL); |
| |
| /* Clip out the region specified by pixm and (x,y) */ |
| pixGetDimensions(pixm, &wm, &hm, NULL); |
| box = boxCreate(x, y, wm, hm); |
| pixd = pixClipRectangle(pixs, box, NULL); |
| |
| /* Paint 'outval' (or something close to it if cmapped) through |
| * the pixels not masked by pixm */ |
| cmap = pixGetColormap(pixd); |
| pixmi = pixInvert(NULL, pixm); |
| d = pixGetDepth(pixd); |
| if (cmap) { |
| extractRGBValues(outval, &rval, &gval, &bval); |
| pixcmapGetNearestIndex(cmap, rval, gval, bval, &index); |
| pixcmapGetColor(cmap, index, &rval, &gval, &bval); |
| composeRGBPixel(rval, gval, bval, &pixel); |
| pixPaintThroughMask(pixd, pixmi, 0, 0, pixel); |
| } |
| else |
| pixPaintThroughMask(pixd, pixmi, 0, 0, outval); |
| |
| boxDestroy(&box); |
| pixDestroy(&pixmi); |
| return pixd; |
| } |
| |
| |
| /*---------------------------------------------------------------------* |
| * Clipping to Foreground * |
| *---------------------------------------------------------------------*/ |
| /*! |
| * pixClipToForeground() |
| * |
| * Input: pixs (1 bpp) |
| * &pixd (<optional return> clipped pix returned) |
| * &box (<optional return> bounding box) |
| * Return: 0 if OK; 1 on error or if there are no fg pixels |
| * |
| * Notes: |
| * (1) At least one of {&pixd, &box} must be specified. |
| * (2) If there are no fg pixels, the returned ptrs are null. |
| */ |
| l_int32 |
| pixClipToForeground(PIX *pixs, |
| PIX **ppixd, |
| BOX **pbox) |
| { |
| l_int32 w, h, wpl, nfullwords, extra, i, j; |
| l_int32 minx, miny, maxx, maxy; |
| l_uint32 result, mask; |
| l_uint32 *data, *line; |
| BOX *box; |
| |
| PROCNAME("pixClipToForeground"); |
| |
| if (!ppixd && !pbox) |
| return ERROR_INT("neither &pixd nor &box defined", procName, 1); |
| if (ppixd) |
| *ppixd = NULL; |
| if (pbox) |
| *pbox = NULL; |
| if (!pixs || (pixGetDepth(pixs) != 1)) |
| return ERROR_INT("pixs not defined or not 1 bpp", procName, 1); |
| |
| pixGetDimensions(pixs, &w, &h, NULL); |
| nfullwords = w / 32; |
| extra = w & 31; |
| mask = ~rmask32[32 - extra]; |
| wpl = pixGetWpl(pixs); |
| data = pixGetData(pixs); |
| |
| result = 0; |
| for (i = 0, miny = 0; i < h; i++, miny++) { |
| line = data + i * wpl; |
| for (j = 0; j < nfullwords; j++) |
| result |= line[j]; |
| if (extra) |
| result |= (line[j] & mask); |
| if (result) |
| break; |
| } |
| if (miny == h) /* no ON pixels */ |
| return 1; |
| |
| result = 0; |
| for (i = h - 1, maxy = h - 1; i >= 0; i--, maxy--) { |
| line = data + i * wpl; |
| for (j = 0; j < nfullwords; j++) |
| result |= line[j]; |
| if (extra) |
| result |= (line[j] & mask); |
| if (result) |
| break; |
| } |
| |
| minx = 0; |
| for (j = 0, minx = 0; j < w; j++, minx++) { |
| for (i = 0; i < h; i++) { |
| line = data + i * wpl; |
| if (GET_DATA_BIT(line, j)) |
| goto minx_found; |
| } |
| } |
| |
| minx_found: |
| for (j = w - 1, maxx = w - 1; j >= 0; j--, maxx--) { |
| for (i = 0; i < h; i++) { |
| line = data + i * wpl; |
| if (GET_DATA_BIT(line, j)) |
| goto maxx_found; |
| } |
| } |
| |
| maxx_found: |
| box = boxCreate(minx, miny, maxx - minx + 1, maxy - miny + 1); |
| |
| if (ppixd) |
| *ppixd = pixClipRectangle(pixs, box, NULL); |
| if (pbox) |
| *pbox = box; |
| else |
| boxDestroy(&box); |
| |
| return 0; |
| } |
| |
| |
| /*! |
| * pixClipBoxToForeground() |
| * |
| * Input: pixs (1 bpp) |
| * boxs (<optional> ; use full image if null) |
| * &pixd (<optional return> clipped pix returned) |
| * &boxd (<optional return> bounding box) |
| * Return: 0 if OK; 1 on error or if there are no fg pixels |
| * |
| * Notes: |
| * (1) At least one of {&pixd, &boxd} must be specified. |
| * (2) If there are no fg pixels, the returned ptrs are null. |
| * (3) Do not use &pixs for the 3rd arg or &boxs for the 4th arg; |
| * this will leak memory. |
| */ |
| l_int32 |
| pixClipBoxToForeground(PIX *pixs, |
| BOX *boxs, |
| PIX **ppixd, |
| BOX **pboxd) |
| { |
| l_int32 w, h, bx, by, bw, bh, cbw, cbh, left, right, top, bottom; |
| BOX *boxt, *boxd; |
| |
| PROCNAME("pixClipBoxToForeground"); |
| |
| if (!ppixd && !pboxd) |
| return ERROR_INT("neither &pixd nor &boxd defined", procName, 1); |
| if (ppixd) *ppixd = NULL; |
| if (pboxd) *pboxd = NULL; |
| if (!pixs || (pixGetDepth(pixs) != 1)) |
| return ERROR_INT("pixs not defined or not 1 bpp", procName, 1); |
| |
| if (!boxs) |
| return pixClipToForeground(pixs, ppixd, pboxd); |
| |
| pixGetDimensions(pixs, &w, &h, NULL); |
| if (boxs) { |
| boxGetGeometry(boxs, &bx, &by, &bw, &bh); |
| cbw = L_MIN(bw, w - bx); |
| cbh = L_MIN(bh, h - by); |
| if (cbw < 0 || cbh < 0) |
| return ERROR_INT("box not within image", procName, 1); |
| boxt = boxCreate(bx, by, cbw, cbh); |
| } |
| else |
| boxt = boxCreate(0, 0, w, h); |
| |
| |
| if (pixScanForForeground(pixs, boxt, L_FROM_LEFT, &left)) { |
| boxDestroy(&boxt); |
| return 1; |
| } |
| pixScanForForeground(pixs, boxt, L_FROM_RIGHT, &right); |
| pixScanForForeground(pixs, boxt, L_FROM_TOP, &top); |
| pixScanForForeground(pixs, boxt, L_FROM_BOTTOM, &bottom); |
| |
| boxd = boxCreate(left, top, right - left + 1, bottom - top + 1); |
| if (ppixd) |
| *ppixd = pixClipRectangle(pixs, boxd, NULL); |
| if (pboxd) |
| *pboxd = boxd; |
| else |
| boxDestroy(&boxd); |
| |
| boxDestroy(&boxt); |
| return 0; |
| } |
| |
| |
| /*! |
| * pixScanForForeground() |
| * |
| * Input: pixs (1 bpp) |
| * box (<optional> within which the search is conducted) |
| * scanflag (direction of scan; e.g., L_FROM_LEFT) |
| * &loc (location in scan direction of first black pixel) |
| * Return: 0 if OK; 1 on error or if no fg pixels are found |
| * |
| * Notes: |
| * (1) If there are no fg pixels, the position is set to 0. |
| * Caller must check the return value! |
| * (2) Use @box == NULL to scan from edge of pixs |
| */ |
| l_int32 |
| pixScanForForeground(PIX *pixs, |
| BOX *box, |
| l_int32 scanflag, |
| l_int32 *ploc) |
| { |
| l_int32 bx, by, bw, bh, x, xstart, xend, y, ystart, yend, wpl; |
| l_uint32 *data, *line; |
| BOX *boxt; |
| |
| PROCNAME("pixScanForForeground"); |
| |
| if (!ploc) |
| return ERROR_INT("&ploc not defined", procName, 1); |
| *ploc = 0; |
| if (!pixs || (pixGetDepth(pixs) != 1)) |
| return ERROR_INT("pixs not defined or not 1 bpp", procName, 1); |
| |
| /* Clip box to pixs if it exists */ |
| pixGetDimensions(pixs, &bw, &bh, NULL); |
| if (box) { |
| if ((boxt = boxClipToRectangle(box, bw, bh)) == NULL) |
| return ERROR_INT("invalid box", procName, 1); |
| boxGetGeometry(boxt, &bx, &by, &bw, &bh); |
| boxDestroy(&boxt); |
| } |
| else |
| bx = by = 0; |
| xstart = bx; |
| ystart = by; |
| xend = bx + bw - 1; |
| yend = by + bh - 1; |
| |
| data = pixGetData(pixs); |
| wpl = pixGetWpl(pixs); |
| if (scanflag == L_FROM_LEFT) { |
| for (x = xstart; x <= xend; x++) { |
| for (y = ystart; y <= yend; y++) { |
| line = data + y * wpl; |
| if (GET_DATA_BIT(line, x)) { |
| *ploc = x; |
| return 0; |
| } |
| } |
| } |
| } |
| else if (scanflag == L_FROM_RIGHT) { |
| for (x = xend; x >= xstart; x--) { |
| for (y = ystart; y <= yend; y++) { |
| line = data + y * wpl; |
| if (GET_DATA_BIT(line, x)) { |
| *ploc = x; |
| return 0; |
| } |
| } |
| } |
| } |
| else if (scanflag == L_FROM_TOP) { |
| for (y = ystart; y <= yend; y++) { |
| line = data + y * wpl; |
| for (x = xstart; x <= xend; x++) { |
| if (GET_DATA_BIT(line, x)) { |
| *ploc = y; |
| return 0; |
| } |
| } |
| } |
| } |
| else if (scanflag == L_FROM_BOTTOM) { |
| for (y = yend; y >= ystart; y--) { |
| line = data + y * wpl; |
| for (x = xstart; x <= xend; x++) { |
| if (GET_DATA_BIT(line, x)) { |
| *ploc = y; |
| return 0; |
| } |
| } |
| } |
| } |
| else |
| return ERROR_INT("invalid scanflag", procName, 1); |
| |
| return 1; /* no fg found */ |
| } |
| |
| |
| /*! |
| * pixClipBoxToEdges() |
| * |
| * Input: pixs (1 bpp) |
| * boxs (<optional> ; use full image if null) |
| * lowthresh (threshold to choose clipping location) |
| * highthresh (threshold required to find an edge) |
| * maxwidth (max allowed width between low and high thresh locs) |
| * factor (sampling factor along pixel counting direction) |
| * &pixd (<optional return> clipped pix returned) |
| * &boxd (<optional return> bounding box) |
| * Return: 0 if OK; 1 on error or if a fg edge is not found from |
| * all four sides. |
| * |
| * Notes: |
| * (1) At least one of {&pixd, &boxd} must be specified. |
| * (2) If there are no fg pixels, the returned ptrs are null. |
| * (3) This function attempts to locate rectangular "image" regions |
| * of high-density fg pixels, that have well-defined edges |
| * on the four sides. |
| * (4) Edges are searched for on each side, iterating in order |
| * from left, right, top and bottom. As each new edge is |
| * found, the search box is resized to use that location. |
| * Once an edge is found, it is held. If no more edges |
| * are found in one iteration, the search fails. |
| * (5) See pixScanForEdge() for usage of the thresholds and @maxwidth. |
| * (6) The thresholds must be at least 1, and the low threshold |
| * cannot be larger than the high threshold. |
| * (7) If the low and high thresholds are both 1, this is equivalent |
| * to pixClipBoxToForeground(). |
| */ |
| l_int32 |
| pixClipBoxToEdges(PIX *pixs, |
| BOX *boxs, |
| l_int32 lowthresh, |
| l_int32 highthresh, |
| l_int32 maxwidth, |
| l_int32 factor, |
| PIX **ppixd, |
| BOX **pboxd) |
| { |
| l_int32 w, h, bx, by, bw, bh, cbw, cbh, left, right, top, bottom; |
| l_int32 lfound, rfound, tfound, bfound, change; |
| BOX *boxt, *boxd; |
| |
| PROCNAME("pixClipBoxToEdges"); |
| |
| if (!ppixd && !pboxd) |
| return ERROR_INT("neither &pixd nor &boxd defined", procName, 1); |
| if (ppixd) *ppixd = NULL; |
| if (pboxd) *pboxd = NULL; |
| if (!pixs || (pixGetDepth(pixs) != 1)) |
| return ERROR_INT("pixs not defined or not 1 bpp", procName, 1); |
| if (lowthresh < 1 || highthresh < 1 || |
| lowthresh > highthresh || maxwidth < 1) |
| return ERROR_INT("invalid thresholds", procName, 1); |
| factor = L_MIN(1, factor); |
| |
| if (lowthresh == 1 && highthresh == 1) |
| return pixClipBoxToForeground(pixs, boxs, ppixd, pboxd); |
| |
| pixGetDimensions(pixs, &w, &h, NULL); |
| if (boxs) { |
| boxGetGeometry(boxs, &bx, &by, &bw, &bh); |
| cbw = L_MIN(bw, w - bx); |
| cbh = L_MIN(bh, h - by); |
| if (cbw < 0 || cbh < 0) |
| return ERROR_INT("box not within image", procName, 1); |
| boxt = boxCreate(bx, by, cbw, cbh); |
| } |
| else |
| boxt = boxCreate(0, 0, w, h); |
| |
| lfound = rfound = tfound = bfound = 0; |
| while (!lfound || !rfound || !tfound || !bfound) { |
| change = 0; |
| if (!lfound) { |
| if (!pixScanForEdge(pixs, boxt, lowthresh, highthresh, maxwidth, |
| factor, L_FROM_LEFT, &left)) { |
| lfound = 1; |
| change = 1; |
| boxRelocateOneSide(boxt, boxt, left, L_FROM_LEFT); |
| } |
| } |
| if (!rfound) { |
| if (!pixScanForEdge(pixs, boxt, lowthresh, highthresh, maxwidth, |
| factor, L_FROM_RIGHT, &right)) { |
| rfound = 1; |
| change = 1; |
| boxRelocateOneSide(boxt, boxt, right, L_FROM_RIGHT); |
| } |
| } |
| if (!tfound) { |
| if (!pixScanForEdge(pixs, boxt, lowthresh, highthresh, maxwidth, |
| factor, L_FROM_TOP, &top)) { |
| tfound = 1; |
| change = 1; |
| boxRelocateOneSide(boxt, boxt, top, L_FROM_TOP); |
| } |
| } |
| if (!bfound) { |
| if (!pixScanForEdge(pixs, boxt, lowthresh, highthresh, maxwidth, |
| factor, L_FROM_BOTTOM, &bottom)) { |
| bfound = 1; |
| change = 1; |
| boxRelocateOneSide(boxt, boxt, bottom, L_FROM_BOTTOM); |
| } |
| } |
| |
| #if DEBUG_EDGES |
| fprintf(stderr, "iter: %d %d %d %d\n", lfound, rfound, tfound, bfound); |
| #endif /* DEBUG_EDGES */ |
| |
| if (change == 0) break; |
| } |
| boxDestroy(&boxt); |
| |
| if (change == 0) |
| return ERROR_INT("not all edges found", procName, 1); |
| |
| boxd = boxCreate(left, top, right - left + 1, bottom - top + 1); |
| if (ppixd) |
| *ppixd = pixClipRectangle(pixs, boxd, NULL); |
| if (pboxd) |
| *pboxd = boxd; |
| else |
| boxDestroy(&boxd); |
| |
| return 0; |
| } |
| |
| |
| /*! |
| * pixScanForEdge() |
| * |
| * Input: pixs (1 bpp) |
| * box (<optional> within which the search is conducted) |
| * lowthresh (threshold to choose clipping location) |
| * highthresh (threshold required to find an edge) |
| * maxwidth (max allowed width between low and high thresh locs) |
| * factor (sampling factor along pixel counting direction) |
| * scanflag (direction of scan; e.g., L_FROM_LEFT) |
| * &loc (location in scan direction of first black pixel) |
| * Return: 0 if OK; 1 on error or if the edge is not found |
| * |
| * Notes: |
| * (1) If there are no fg pixels, the position is set to 0. |
| * Caller must check the return value! |
| * (2) Use @box == NULL to scan from edge of pixs |
| * (3) As the scan progresses, the location where the sum of |
| * pixels equals or excees @lowthresh is noted (loc). The |
| * scan is stopped when the sum of pixels equals or exceeds |
| * @highthresh. If the scan distance between loc and that |
| * point does not exceed @maxwidth, an edge is found and |
| * its position is taken to be loc. @maxwidth implicitly |
| * sets a minimum on the required gradient of the edge. |
| * (4) The thresholds must be at least 1, and the low threshold |
| * cannot be larger than the high threshold. |
| */ |
| l_int32 |
| pixScanForEdge(PIX *pixs, |
| BOX *box, |
| l_int32 lowthresh, |
| l_int32 highthresh, |
| l_int32 maxwidth, |
| l_int32 factor, |
| l_int32 scanflag, |
| l_int32 *ploc) |
| { |
| l_int32 bx, by, bw, bh, foundmin, loc, sum, wpl; |
| l_int32 x, xstart, xend, y, ystart, yend; |
| l_uint32 *data, *line; |
| BOX *boxt; |
| |
| PROCNAME("pixScanForEdge"); |
| |
| if (!ploc) |
| return ERROR_INT("&ploc not defined", procName, 1); |
| *ploc = 0; |
| if (!pixs || (pixGetDepth(pixs) != 1)) |
| return ERROR_INT("pixs not defined or not 1 bpp", procName, 1); |
| if (lowthresh < 1 || highthresh < 1 || |
| lowthresh > highthresh || maxwidth < 1) |
| return ERROR_INT("invalid thresholds", procName, 1); |
| factor = L_MIN(1, factor); |
| |
| /* Clip box to pixs if it exists */ |
| pixGetDimensions(pixs, &bw, &bh, NULL); |
| if (box) { |
| if ((boxt = boxClipToRectangle(box, bw, bh)) == NULL) |
| return ERROR_INT("invalid box", procName, 1); |
| boxGetGeometry(boxt, &bx, &by, &bw, &bh); |
| boxDestroy(&boxt); |
| } |
| else |
| bx = by = 0; |
| xstart = bx; |
| ystart = by; |
| xend = bx + bw - 1; |
| yend = by + bh - 1; |
| |
| data = pixGetData(pixs); |
| wpl = pixGetWpl(pixs); |
| foundmin = 0; |
| if (scanflag == L_FROM_LEFT) { |
| for (x = xstart; x <= xend; x++) { |
| sum = 0; |
| for (y = ystart; y <= yend; y += factor) { |
| line = data + y * wpl; |
| if (GET_DATA_BIT(line, x)) |
| sum++; |
| } |
| if (!foundmin && sum < lowthresh) |
| continue; |
| if (!foundmin) { /* save the loc of the beginning of the edge */ |
| foundmin = 1; |
| loc = x; |
| } |
| if (sum >= highthresh) { |
| #if DEBUG_EDGES |
| fprintf(stderr, "Left: x = %d, loc = %d\n", x, loc); |
| #endif /* DEBUG_EDGES */ |
| if (x - loc < maxwidth) { |
| *ploc = loc; |
| return 0; |
| } |
| else return 1; |
| } |
| } |
| } |
| else if (scanflag == L_FROM_RIGHT) { |
| for (x = xend; x >= xstart; x--) { |
| sum = 0; |
| for (y = ystart; y <= yend; y += factor) { |
| line = data + y * wpl; |
| if (GET_DATA_BIT(line, x)) |
| sum++; |
| } |
| if (!foundmin && sum < lowthresh) |
| continue; |
| if (!foundmin) { |
| foundmin = 1; |
| loc = x; |
| } |
| if (sum >= highthresh) { |
| #if DEBUG_EDGES |
| fprintf(stderr, "Right: x = %d, loc = %d\n", x, loc); |
| #endif /* DEBUG_EDGES */ |
| if (loc - x < maxwidth) { |
| *ploc = loc; |
| return 0; |
| } |
| else return 1; |
| } |
| } |
| } |
| else if (scanflag == L_FROM_TOP) { |
| for (y = ystart; y <= yend; y++) { |
| sum = 0; |
| line = data + y * wpl; |
| for (x = xstart; x <= xend; x += factor) { |
| if (GET_DATA_BIT(line, x)) |
| sum++; |
| } |
| if (!foundmin && sum < lowthresh) |
| continue; |
| if (!foundmin) { |
| foundmin = 1; |
| loc = y; |
| } |
| if (sum >= highthresh) { |
| #if DEBUG_EDGES |
| fprintf(stderr, "Top: y = %d, loc = %d\n", y, loc); |
| #endif /* DEBUG_EDGES */ |
| if (y - loc < maxwidth) { |
| *ploc = loc; |
| return 0; |
| } |
| else return 1; |
| } |
| } |
| } |
| else if (scanflag == L_FROM_BOTTOM) { |
| for (y = yend; y >= ystart; y--) { |
| sum = 0; |
| line = data + y * wpl; |
| for (x = xstart; x <= xend; x += factor) { |
| if (GET_DATA_BIT(line, x)) |
| sum++; |
| } |
| if (!foundmin && sum < lowthresh) |
| continue; |
| if (!foundmin) { |
| foundmin = 1; |
| loc = y; |
| } |
| if (sum >= highthresh) { |
| #if DEBUG_EDGES |
| fprintf(stderr, "Bottom: y = %d, loc = %d\n", y, loc); |
| #endif /* DEBUG_EDGES */ |
| if (loc - y < maxwidth) { |
| *ploc = loc; |
| return 0; |
| } |
| else return 1; |
| } |
| } |
| } |
| else |
| return ERROR_INT("invalid scanflag", procName, 1); |
| |
| return 1; /* edge not found */ |
| } |
| |