/*====================================================================* - 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 */ }