/*====================================================================* - Copyright (C) 2001 Leptonica. All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials - provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *====================================================================*/ /*! * \file pix4.c *
 *
 *    This file has these operations:
 *
 *      (1) Pixel histograms
 *      (2) Pixel row/column statistics
 *      (3) Foreground/background estimation
 *
 *    Pixel histogram, rank val, averaging and min/max
 *           NUMA       *pixGetGrayHistogram()
 *           NUMA       *pixGetGrayHistogramMasked()
 *           NUMA       *pixGetGrayHistogramInRect()
 *           NUMAA      *pixGetGrayHistogramTiled()
 *           l_int32     pixGetColorHistogram()
 *           l_int32     pixGetColorHistogramMasked()
 *           NUMA       *pixGetCmapHistogram()
 *           NUMA       *pixGetCmapHistogramMasked()
 *           NUMA       *pixGetCmapHistogramInRect()
 *           l_int32     pixCountRGBColorsByHash()
 *           l_int32     pixCountRGBColors()
 *           L_AMAP     *pixGetColorAmapHistogram()
 *           l_int32     amapGetCountForColor()
 *           l_int32     pixGetRankValue()
 *           l_int32     pixGetRankValueMaskedRGB()
 *           l_int32     pixGetRankValueMasked()
 *           l_int32     pixGetPixelAverage()
 *           l_int32     pixGetPixelStats()
 *           l_int32     pixGetAverageMaskedRGB()
 *           l_int32     pixGetAverageMasked()
 *           l_int32     pixGetAverageTiledRGB()
 *           PIX        *pixGetAverageTiled()
 *           NUMA       *pixRowStats()
 *           NUMA       *pixColumnStats()
 *           l_int32     pixGetRangeValues()
 *           l_int32     pixGetExtremeValue()
 *           l_int32     pixGetMaxValueInRect()
 *           l_int32     pixGetMaxColorIndex()
 *           l_int32     pixGetBinnedComponentRange()
 *           l_int32     pixGetRankColorArray()
 *           l_int32     pixGetBinnedColor()
 *           PIX        *pixDisplayColorArray()
 *           PIX        *pixRankBinByStrip()
 *
 *    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()
 * 
*/ #ifdef HAVE_CONFIG_H #include #endif /* HAVE_CONFIG_H */ #include #include #include "allheaders.h" /*------------------------------------------------------------------* * Pixel histogram and averaging * *------------------------------------------------------------------*/ /*! * \brief pixGetGrayHistogram() * * \param[in] pixs 1, 2, 4, 8, 16 bpp; can be colormapped * \param[in] factor subsampling factor; integer >= 1 * \return na histogram, or NULL on error * *
 * Notes:
 *      (1) If pixs has a colormap, it is converted to 8 bpp gray.
 *          If you want a histogram of the colormap indices, use
 *          pixGetCmapHistogram().
 *      (2) If pixs does not have a colormap, the output histogram is
 *          of size 2^d, where d is the depth of pixs.
 *      (3) 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; l_uint32 *data, *line; l_float32 *array; NUMA *na; PIX *pixg; 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 must be >= 1", procName, NULL); if (pixGetColormap(pixs)) pixg = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE); else pixg = pixClone(pixs); pixGetDimensions(pixg, &w, &h, &d); size = 1 << d; if ((na = numaCreate(size)) == NULL) { pixDestroy(&pixg); 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; if (d == 2) { for (j = 0; j < w; j += factor) { val = GET_DATA_DIBIT(line, j); array[val] += 1.0; } } else if (d == 4) { for (j = 0; j < w; j += factor) { val = GET_DATA_QBIT(line, j); array[val] += 1.0; } } else if (d == 8) { for (j = 0; j < w; j += factor) { val = GET_DATA_BYTE(line, j); array[val] += 1.0; } } else { /* d == 16 */ for (j = 0; j < w; j += factor) { val = GET_DATA_TWO_BYTES(line, j); array[val] += 1.0; } } } pixDestroy(&pixg); return na; } /*! * \brief pixGetGrayHistogramMasked() * * \param[in] pixs 8 bpp, or colormapped * \param[in] pixm [optional] 1 bpp mask over which histogram is * to be computed; use all pixels if null * \param[in] x, y UL corner of pixm relative to the UL corner of pixs; * can be < 0; these values are ignored if pixm is null * \param[in] 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.
 *          If you want a histogram of the colormap indices, use
 *          pixGetCmapHistogramMasked().
 *      (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 must be >= 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; } /*! * \brief pixGetGrayHistogramInRect() * * \param[in] pixs 8 bpp, or colormapped * \param[in] box [optional] over which histogram is to be computed; * use full image if NULL * \param[in] 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.
 *          If you want a histogram of the colormap indices, use
 *          pixGetCmapHistogramInRect().
 *      (2) This always returns a 256-value histogram of pixel values.
 *      (3) Set the subsampling %factor > 1 to reduce the amount of computation.
 * 
*/ NUMA * pixGetGrayHistogramInRect(PIX *pixs, BOX *box, l_int32 factor) { l_int32 i, j, bx, by, bw, bh, w, h, wplg, val; l_uint32 *datag, *lineg; l_float32 *array; NUMA *na; PIX *pixg; PROCNAME("pixGetGrayHistogramInRect"); if (!box) 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); if (factor < 1) return (NUMA *)ERROR_PTR("sampling must be >= 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); boxGetGeometry(box, &bx, &by, &bw, &bh); /* Generate the histogram */ for (i = 0; i < bh; i += factor) { if (by + i < 0 || by + i >= h) continue; lineg = datag + (by + i) * wplg; for (j = 0; j < bw; j += factor) { if (bx + j < 0 || bx + j >= w) continue; val = GET_DATA_BYTE(lineg, bx + j); array[val] += 1.0; } } pixDestroy(&pixg); return na; } /*! * \brief pixGetGrayHistogramTiled() * * \param[in] pixs any depth, colormap OK * \param[in] factor subsampling factor; integer >= 1 * \param[in] nx, ny tiling; >= 1; typically small * \return naa set of histograms, or NULL on error * *
 * Notes:
 *      (1) If pixs is cmapped, it is converted to 8 bpp gray.
 *      (2) This returns a set of 256-value histograms of pixel values.
 *      (3) Set the subsampling factor > 1 to reduce the amount of computation.
 * 
*/ NUMAA * pixGetGrayHistogramTiled(PIX *pixs, l_int32 factor, l_int32 nx, l_int32 ny) { l_int32 i, n; NUMA *na; NUMAA *naa; PIX *pix1, *pix2; PIXA *pixa; PROCNAME("pixGetGrayHistogramTiled"); if (!pixs) return (NUMAA *)ERROR_PTR("pixs not defined", procName, NULL); if (factor < 1) return (NUMAA *)ERROR_PTR("sampling must be >= 1", procName, NULL); if (nx < 1 || ny < 1) return (NUMAA *)ERROR_PTR("nx and ny must both be > 0", procName, NULL); n = nx * ny; if ((naa = numaaCreate(n)) == NULL) return (NUMAA *)ERROR_PTR("naa not made", procName, NULL); pix1 = pixConvertTo8(pixs, FALSE); pixa = pixaSplitPix(pix1, nx, ny, 0, 0); for (i = 0; i < n; i++) { pix2 = pixaGetPix(pixa, i, L_CLONE); na = pixGetGrayHistogram(pix2, factor); numaaAddNuma(naa, na, L_INSERT); pixDestroy(&pix2); } pixDestroy(&pix1); pixaDestroy(&pixa); return naa; } /*! * \brief pixGetColorHistogram() * * \param[in] pixs rgb or colormapped * \param[in] factor subsampling factor; integer >= 1 * \param[out] pnar red histogram * \param[out] pnag green histogram * \param[out] pnab 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_ok 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) *pnar = NULL; if (pnag) *pnag = NULL; if (pnab) *pnab = NULL; if (!pnar || !pnag || !pnab) return ERROR_INT("&nar, &nag, &nab not all defined", procName, 1); 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 must be >= 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; } /*! * \brief pixGetColorHistogramMasked() * * \param[in] pixs 32 bpp rgb, or colormapped * \param[in] pixm [optional] 1 bpp mask over which histogram is * to be computed; use all pixels if null * \param[in] x, y UL corner of pixm relative to the UL corner of pixs; * can be < 0; these values are ignored if pixm is null * \param[in] factor subsampling factor; integer >= 1 * \param[out] pnar red histogram * \param[out] pnag green histogram * \param[out] pnab 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_ok 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) *pnar = NULL; if (pnag) *pnag = NULL; if (pnab) *pnab = NULL; if (!pnar || !pnag || !pnab) return ERROR_INT("&nar, &nag, &nab not all defined", procName, 1); 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 must be >= 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; } /*! * \brief pixGetCmapHistogram() * * \param[in] pixs colormapped: d = 2, 4 or 8 * \param[in] factor subsampling factor; integer >= 1 * \return na histogram of cmap indices, or NULL on error * *
 * Notes:
 *      (1) This generates a histogram of colormap pixel indices,
 *          and is of size 2^d.
 *      (2) Set the subsampling %factor > 1 to reduce the amount of computation.
 * 
*/ NUMA * pixGetCmapHistogram(PIX *pixs, l_int32 factor) { l_int32 i, j, w, h, d, wpl, val, size; l_uint32 *data, *line; l_float32 *array; NUMA *na; PROCNAME("pixGetCmapHistogram"); if (!pixs) return (NUMA *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetColormap(pixs) == NULL) return (NUMA *)ERROR_PTR("pixs not cmapped", procName, NULL); if (factor < 1) return (NUMA *)ERROR_PTR("sampling must be >= 1", procName, NULL); pixGetDimensions(pixs, &w, &h, &d); if (d != 2 && d != 4 && d != 8) return (NUMA *)ERROR_PTR("d not 2, 4 or 8", procName, NULL); 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); wpl = pixGetWpl(pixs); data = pixGetData(pixs); for (i = 0; i < h; i += factor) { line = data + i * wpl; for (j = 0; j < w; j += factor) { if (d == 8) val = GET_DATA_BYTE(line, j); else if (d == 4) val = GET_DATA_QBIT(line, j); else /* d == 2 */ val = GET_DATA_DIBIT(line, j); array[val] += 1.0; } } return na; } /*! * \brief pixGetCmapHistogramMasked() * * \param[in] pixs colormapped: d = 2, 4 or 8 * \param[in] pixm [optional] 1 bpp mask over which histogram is * to be computed; use all pixels if null * \param[in] x, y UL corner of pixm relative to the UL corner of pixs; * can be < 0; these values are ignored if pixm is null * \param[in] factor subsampling factor; integer >= 1 * \return na histogram, or NULL on error * *
 * Notes:
 *      (1) This generates a histogram of colormap pixel indices,
 *          and is of size 2^d.
 *      (2) Set the subsampling %factor > 1 to reduce the amount of computation.
 *      (3) Clipping of pixm to pixs is done in the inner loop.
 * 
*/ NUMA * pixGetCmapHistogramMasked(PIX *pixs, PIX *pixm, l_int32 x, l_int32 y, l_int32 factor) { l_int32 i, j, w, h, d, wm, hm, dm, wpls, wplm, val, size; l_uint32 *datas, *datam, *lines, *linem; l_float32 *array; NUMA *na; PROCNAME("pixGetCmapHistogramMasked"); if (!pixm) return pixGetCmapHistogram(pixs, factor); if (!pixs) return (NUMA *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetColormap(pixs) == NULL) return (NUMA *)ERROR_PTR("pixs not cmapped", 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 must be >= 1", procName, NULL); pixGetDimensions(pixs, &w, &h, &d); if (d != 2 && d != 4 && d != 8) return (NUMA *)ERROR_PTR("d not 2, 4 or 8", procName, NULL); 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); datas = pixGetData(pixs); wpls = pixGetWpl(pixs); datam = pixGetData(pixm); wplm = pixGetWpl(pixm); 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) val = GET_DATA_BYTE(lines, x + j); else if (d == 4) val = GET_DATA_QBIT(lines, x + j); else /* d == 2 */ val = GET_DATA_DIBIT(lines, x + j); array[val] += 1.0; } } } return na; } /*! * \brief pixGetCmapHistogramInRect() * * \param[in] pixs colormapped: d = 2, 4 or 8 * \param[in] box [optional] over which histogram is to be computed; * use full image if NULL * \param[in] factor subsampling factor; integer >= 1 * \return na histogram, or NULL on error * *
 * Notes:
 *      (1) This generates a histogram of colormap pixel indices,
 *          and is of size 2^d.
 *      (2) Set the subsampling %factor > 1 to reduce the amount of computation.
 *      (3) Clipping to the box is done in the inner loop.
 * 
*/ NUMA * pixGetCmapHistogramInRect(PIX *pixs, BOX *box, l_int32 factor) { l_int32 i, j, bx, by, bw, bh, w, h, d, wpls, val, size; l_uint32 *datas, *lines; l_float32 *array; NUMA *na; PROCNAME("pixGetCmapHistogramInRect"); if (!box) return pixGetCmapHistogram(pixs, factor); if (!pixs) return (NUMA *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetColormap(pixs) == NULL) return (NUMA *)ERROR_PTR("pixs not cmapped", procName, NULL); if (factor < 1) return (NUMA *)ERROR_PTR("sampling must be >= 1", procName, NULL); pixGetDimensions(pixs, &w, &h, &d); if (d != 2 && d != 4 && d != 8) return (NUMA *)ERROR_PTR("d not 2, 4 or 8", procName, NULL); 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); datas = pixGetData(pixs); wpls = pixGetWpl(pixs); boxGetGeometry(box, &bx, &by, &bw, &bh); for (i = 0; i < bh; i += factor) { if (by + i < 0 || by + i >= h) continue; lines = datas + (by + i) * wpls; for (j = 0; j < bw; j += factor) { if (bx + j < 0 || bx + j >= w) continue; if (d == 8) val = GET_DATA_BYTE(lines, bx + j); else if (d == 4) val = GET_DATA_QBIT(lines, bx + j); else /* d == 2 */ val = GET_DATA_DIBIT(lines, bx + j); array[val] += 1.0; } } return na; } /*! * \brief pixCountRGBColorsByHash() * * \param[in] pixs rgb or rgba * \param[out] pncolors number of colors found * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) This is about 3x faster than pixCountRGBColors(),
 *          which uses an ordered map.
 * 
*/ l_ok pixCountRGBColorsByHash(PIX *pixs, l_int32 *pncolors) { L_DNA *da1, *da2; PROCNAME("pixCountRGBColorsByHash"); if (!pncolors) return ERROR_INT("&ncolors not defined", procName, 1); *pncolors = 0; if (!pixs || pixGetDepth(pixs) != 32) return ERROR_INT("pixs not defined or not 32 bpp", procName, 1); da1 = pixConvertDataToDna(pixs); l_dnaRemoveDupsByHash(da1, &da2, NULL); *pncolors = l_dnaGetCount(da2); l_dnaDestroy(&da1); l_dnaDestroy(&da2); return 0; } /*! * \brief pixCountRGBColors() * * \param[in] pixs rgb or rgba * \param[in] factor subsampling factor; integer >= 1 * \param[out] pncolors number of colors found * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) If %factor == 1, this gives the exact number of colors.
 *      (2) This is about 3x slower than pixCountRGBColorsByHash().
 * 
*/ l_ok pixCountRGBColors(PIX *pixs, l_int32 factor, l_int32 *pncolors) { L_AMAP *amap; PROCNAME("pixCountRGBColors"); if (!pncolors) return ERROR_INT("&ncolors not defined", procName, 1); *pncolors = 0; if (!pixs || pixGetDepth(pixs) != 32) return ERROR_INT("pixs not defined or not 32 bpp", procName, 1); if (factor <= 0) return ERROR_INT("factor must be > 0", procName, 1); amap = pixGetColorAmapHistogram(pixs, factor); *pncolors = l_amapSize(amap); l_amapDestroy(&amap); return 0; } /*! * \brief pixGetColorAmapHistogram() * * \param[in] pixs rgb or rgba * \param[in] factor subsampling factor; integer >= 1 * \return amap, or NULL on error * *
 * Notes:
 *      (1) This generates an ordered map from pixel value to histogram count.
 *      (2) Use amapGetCountForColor() to use the map to look up a count.
 * 
*/ L_AMAP * pixGetColorAmapHistogram(PIX *pixs, l_int32 factor) { l_int32 i, j, w, h, wpl; l_uint32 *data, *line; L_AMAP *amap; RB_TYPE key, value; RB_TYPE *pval; PROCNAME("pixGetColorAmapHistogram"); if (!pixs) return (L_AMAP *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 32) return (L_AMAP *)ERROR_PTR("pixs not 32 bpp", procName, NULL); if (factor <= 0) return (L_AMAP *)ERROR_PTR("factor must be > 0", procName, NULL); pixGetDimensions(pixs, &w, &h, NULL); data = pixGetData(pixs); wpl = pixGetWpl(pixs); amap = l_amapCreate(L_UINT_TYPE); for (i = 0; i < h; i += factor) { line = data + i * wpl; for (j = 0; j < w; j += factor) { key.utype = line[j]; pval = l_amapFind(amap, key); if (!pval) value.itype = 1; else value.itype = 1 + pval->itype; l_amapInsert(amap, key, value); } } return amap; } /*! * \brief amapGetCountForColor() * * \param[in] amap map from pixel value to count * \param[in] val rgb or rgba pixel value * \return count, or -1 on error * *
 * Notes:
 *      (1) The ordered map is made by pixGetColorAmapHistogram().
 * 
*/ l_int32 amapGetCountForColor(L_AMAP *amap, l_uint32 val) { RB_TYPE key; RB_TYPE *pval; PROCNAME("amapGetCountForColor"); if (!amap) return ERROR_INT("amap not defined", procName, -1); key.utype = val; pval = l_amapFind(amap, key); return (pval) ? pval->itype : 0; } /*! * \brief pixGetRankValue() * * \param[in] pixs 8 bpp, 32 bpp or colormapped * \param[in] factor subsampling factor; integer >= 1 * \param[in] rank between 0.0 and 1.0; 1.0 is brightest, 0.0 is darkest * \param[out] pvalue pixel value corresponding to input rank * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) Simple function to get a rank value (color) of an image.
 *          For a color image, the median value (rank = 0.5) can be
 *          used to linearly remap the colors based on the median
 *          of a target image, using pixLinearMapToTargetColor().
 *      (2) For RGB, this treats each color component independently.
 *          It calls pixGetGrayHistogramMasked() on each component, and
 *          uses the returned gray histogram to get the rank value.
 *          It then combines the 3 rank values into a color pixel.
 * 
*/ l_ok pixGetRankValue(PIX *pixs, l_int32 factor, l_float32 rank, l_uint32 *pvalue) { l_int32 d; l_float32 val, rval, gval, bval; PIX *pixt; PIXCMAP *cmap; PROCNAME("pixGetRankValue"); if (!pvalue) return ERROR_INT("&value not defined", procName, 1); *pvalue = 0; if (!pixs) return ERROR_INT("pixs not defined", procName, 1); d = pixGetDepth(pixs); cmap = pixGetColormap(pixs); if (d != 8 && d != 32 && !cmap) return ERROR_INT("pixs not 8 or 32 bpp, or cmapped", procName, 1); if (cmap) pixt = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC); else pixt = pixClone(pixs); d = pixGetDepth(pixt); if (d == 8) { pixGetRankValueMasked(pixt, NULL, 0, 0, factor, rank, &val, NULL); *pvalue = lept_roundftoi(val); } else { pixGetRankValueMaskedRGB(pixt, NULL, 0, 0, factor, rank, &rval, &gval, &bval); composeRGBPixel(lept_roundftoi(rval), lept_roundftoi(gval), lept_roundftoi(bval), pvalue); } pixDestroy(&pixt); return 0; } /*! * \brief pixGetRankValueMaskedRGB() * * \param[in] pixs 32 bpp * \param[in] pixm [optional] 1 bpp mask over which rank val is to be taken; * use all pixels if null * \param[in] x, y UL corner of pixm relative to the UL corner of pixs; * can be < 0; these values are ignored if pixm is null * \param[in] factor subsampling factor; integer >= 1 * \param[in] rank between 0.0 and 1.0; 1.0 is brightest, 0.0 is darkest * \param[out] prval [optional] red component val for input rank * \param[out] pgval [optional] green component val for input rank * \param[out] pbval [optional] blue component val for 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_ok 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 (prval) *prval = 0.0; if (pgval) *pgval = 0.0; if (pbval) *pbval = 0.0; if (!prval && !pgval && !pbval) return ERROR_INT("no results requested", procName, 1); 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 must be >= 1", procName, 1); if (rank < 0.0 || rank > 1.0) return ERROR_INT("rank not in [0.0 ... 1.0]", 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; } /*! * \brief pixGetRankValueMasked() * * \param[in] pixs 8 bpp, or colormapped * \param[in] pixm [optional] 1 bpp mask, over which the rank val * is to be taken; use all pixels if null * \param[in] x, y UL corner of pixm relative to the UL corner of pixs; * can be < 0; these values are ignored if pixm is null * \param[in] factor subsampling factor; integer >= 1 * \param[in] rank between 0.0 and 1.0; 1.0 is brightest, 0.0 is darkest * \param[out] pval pixel value corresponding to input rank * \param[out] pna [optional] 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_ok 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 (!pval) return ERROR_INT("&val not defined", procName, 1); *pval = 0.0; 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 must be >= 1", procName, 1); if (rank < 0.0 || rank > 1.0) return ERROR_INT("rank not in [0.0 ... 1.0]", procName, 1); 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; } /*! * \brief pixGetPixelAverage() * * \param[in] pixs 8 or 32 bpp, or colormapped * \param[in] pixm [optional] 1 bpp mask over which average is * to be taken; use all pixels if null * \param[in] x, y UL corner of pixm relative to the UL corner of pixs; * can be < 0 * \param[in] factor subsampling factor; >= 1 * \param[out] pval average pixel value * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) For rgb pix, this is a more direct computation of the
 *          average value of the pixels in %pixs that are under the
 *          mask %pixm. It is faster than pixGetPixelStats(), which
 *          calls pixGetAverageMaskedRGB() and has the overhead of
 *          generating a temporary pix of each of the three components;
 *          this can take most of the time if %factor > 1.
 *      (2) If %pixm is null, this gives the average value of all
 *          pixels in %pixs.  The returned value is an integer.
 *      (3) For color %pixs, the returned pixel value is in the standard
 *          uint32 RGBA packing.
 *      (4) Clipping of pixm (if it exists) to pixs is done in the inner loop.
 *      (5) Input x,y are ignored if %pixm does not exist.
 *      (6) For general averaging of 1, 2, 4 or 8 bpp grayscale, use
 *          pixAverageInRect().
 * 
*/ l_ok pixGetPixelAverage(PIX *pixs, PIX *pixm, l_int32 x, l_int32 y, l_int32 factor, l_uint32 *pval) { l_int32 i, j, w, h, d, wm, hm, wpl1, wplm, val, rval, gval, bval, count; l_uint32 *data1, *datam, *line1, *linem; l_float64 sum, rsum, gsum, bsum; PIX *pix1; PROCNAME("pixGetPixelAverage"); if (!pval) return ERROR_INT("&val not defined", procName, 1); *pval = 0; if (!pixs) return ERROR_INT("pixs not defined", procName, 1); d = pixGetDepth(pixs); if (d != 32 && !pixGetColormap(pixs)) return ERROR_INT("pixs not rgb or 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 must be >= 1", procName, 1); if (pixGetColormap(pixs)) pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC); else pix1 = pixClone(pixs); pixGetDimensions(pix1, &w, &h, &d); if (d == 1) { pixDestroy(&pix1); return ERROR_INT("pix1 is just 1 bpp", procName, 1); } data1 = pixGetData(pix1); wpl1 = pixGetWpl(pix1); sum = rsum = gsum = bsum = 0.0; count = 0; if (!pixm) { for (i = 0; i < h; i += factor) { line1 = data1 + i * wpl1; for (j = 0; j < w; j += factor) { if (d == 8) { val = GET_DATA_BYTE(line1, j); sum += val; } else { /* rgb */ extractRGBValues(*(line1 + j), &rval, &gval, &bval); rsum += rval; gsum += gval; bsum += bval; } count++; } } } else { /* masked */ 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; line1 = data1 + (y + i) * wpl1; 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(line1, x + j); sum += val; } else { /* rgb */ extractRGBValues(*(line1 + x + j), &rval, &gval, &bval); rsum += rval; gsum += gval; bsum += bval; } count++; } } } } pixDestroy(&pix1); if (count == 0) return ERROR_INT("no pixels sampled", procName, 1); if (d == 8) { *pval = (l_uint32)(sum / (l_float64)count); } else { /* d == 32 */ rval = (l_uint32)(rsum / (l_float64)count); gval = (l_uint32)(gsum / (l_float64)count); bval = (l_uint32)(bsum / (l_float64)count); composeRGBPixel(rval, gval, bval, pval); } return 0; } /*! * \brief pixGetPixelStats() * * \param[in] pixs 8 bpp, 32 bpp or colormapped * \param[in] factor subsampling factor; integer >= 1 * \param[in] type L_MEAN_ABSVAL, L_ROOT_MEAN_SQUARE, * L_STANDARD_DEVIATION, L_VARIANCE * \param[out] pvalue pixel value corresponding to input type * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) Simple function to get one of four statistical values of an image.
 *      (2) It does not take a mask: it uses the entire image.
 *      (3) To get the average pixel value of an RGB image, suggest using
 *          pixGetPixelAverage(), which is considerably faster.
 * 
*/ l_ok pixGetPixelStats(PIX *pixs, l_int32 factor, l_int32 type, l_uint32 *pvalue) { l_int32 d; l_float32 val, rval, gval, bval; PIX *pixt; PIXCMAP *cmap; PROCNAME("pixGetPixelStats"); if (!pvalue) return ERROR_INT("&value not defined", procName, 1); *pvalue = 0; if (!pixs) return ERROR_INT("pixs not defined", procName, 1); d = pixGetDepth(pixs); cmap = pixGetColormap(pixs); if (d != 8 && d != 32 && !cmap) return ERROR_INT("pixs not 8 or 32 bpp, or cmapped", procName, 1); if (cmap) pixt = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC); else pixt = pixClone(pixs); d = pixGetDepth(pixt); if (d == 8) { pixGetAverageMasked(pixt, NULL, 0, 0, factor, type, &val); *pvalue = lept_roundftoi(val); } else { pixGetAverageMaskedRGB(pixt, NULL, 0, 0, factor, type, &rval, &gval, &bval); composeRGBPixel(lept_roundftoi(rval), lept_roundftoi(gval), lept_roundftoi(bval), pvalue); } pixDestroy(&pixt); return 0; } /*! * \brief pixGetAverageMaskedRGB() * * \param[in] pixs 32 bpp, or colormapped * \param[in] pixm [optional] 1 bpp mask over which average is * to be taken; use all pixels if null * \param[in] x, y UL corner of pixm relative to the UL corner of pixs; * can be < 0 * \param[in] factor subsampling factor; >= 1 * \param[in] type L_MEAN_ABSVAL, L_ROOT_MEAN_SQUARE, * L_STANDARD_DEVIATION, L_VARIANCE * \param[out] prval [optional] measured red value of given 'type' * \param[out] pgval [optional] measured green value of given 'type' * \param[out] pbval [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.
 *      (3) A better name for this would be: pixGetPixelStatsRGB()
 * 
*/ l_ok 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 empty; PIX *pixt; PIXCMAP *cmap; PROCNAME("pixGetAverageMaskedRGB"); if (prval) *prval = 0.0; if (pgval) *pgval = 0.0; if (pbval) *pbval = 0.0; if (!prval && !pgval && !pbval) return ERROR_INT("no values requested", procName, 1); 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 (pixm && pixGetDepth(pixm) != 1) return ERROR_INT("pixm not 1 bpp", procName, 1); if (factor < 1) return ERROR_INT("sampling factor must be >= 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 (pixm) { pixZero(pixm, &empty); if (empty) return ERROR_INT("empty mask", 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; } /*! * \brief pixGetAverageMasked() * * \param[in] pixs 8 or 16 bpp, or colormapped * \param[in] pixm [optional] 1 bpp mask over which average is * to be taken; use all pixels if null * \param[in] x, y UL corner of pixm relative to the UL corner of pixs; * can be < 0 * \param[in] factor subsampling factor; >= 1 * \param[in] type L_MEAN_ABSVAL, L_ROOT_MEAN_SQUARE, * L_STANDARD_DEVIATION, L_VARIANCE * \param[out] pval 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.
 *      (6) A better name for this would be: pixGetPixelStatsGray()
 * 
*/ l_ok 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, empty; l_uint32 *datag, *datam, *lineg, *linem; l_float64 sumave, summs, ave, meansq, var; PIX *pixg; PROCNAME("pixGetAverageMasked"); if (!pval) return ERROR_INT("&val not defined", procName, 1); *pval = 0.0; 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("sampling factor must be >= 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 (pixm) { pixZero(pixm, &empty); if (empty) return ERROR_INT("empty mask", procName, 1); } 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 += (l_float64)(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 += (l_float64)(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; } /*! * \brief pixGetAverageTiledRGB() * * \param[in] pixs 32 bpp, or colormapped * \param[in] sx, sy tile size; must be at least 2 x 2 * \param[in] type L_MEAN_ABSVAL, L_ROOT_MEAN_SQUARE, L_STANDARD_DEVIATION * \param[out] ppixr [optional] tiled 'average' of red component * \param[out] ppixg [optional] tiled 'average' of green component * \param[out] ppixb [optional] 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_ok 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 (ppixr) *ppixr = NULL; if (ppixg) *ppixg = NULL; if (ppixb) *ppixb = NULL; if (!ppixr && !ppixg && !ppixb) return ERROR_INT("no data requested", procName, 1); 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) { 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; } /*! * \brief pixGetAverageTiled() * * \param[in] pixs 8 bpp, or colormapped * \param[in] sx, sy tile size; must be at least 2 x 2 * \param[in] 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 += (l_float64)(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; } /*! * \brief pixRowStats() * * \param[in] pixs 8 bpp; not cmapped * \param[in] box [optional] clipping box; can be null * \param[out] pnamean [optional] numa of mean values * \param[out] pnamedian [optional] numa of median values * \param[out] pnamode [optional] numa of mode intensity values * \param[out] pnamodecount [optional] numa of mode counts * \param[out] pnavar [optional] numa of variance * \param[out] pnarootvar [optional] numa of square root of variance * \return na numa of requested statistic for each row, or NULL on error * *
 * Notes:
 *      (1) This computes numas that represent column vectors of statistics,
 *          with each of its values derived from the corresponding row of a Pix.
 *      (2) Use NULL on input to prevent computation of any of the 5 numas.
 *      (3) Other functions that compute pixel row statistics are:
 *             pixCountPixelsByRow()
 *             pixAverageByRow()
 *             pixVarianceByRow()
 *             pixGetRowStats()
 * 
*/ l_int32 pixRowStats(PIX *pixs, BOX *box, NUMA **pnamean, NUMA **pnamedian, NUMA **pnamode, NUMA **pnamodecount, NUMA **pnavar, NUMA **pnarootvar) { l_int32 i, j, k, w, h, val, wpls, sum, sumsq, target, max, modeval; l_int32 xstart, xend, ystart, yend, bw, bh; l_int32 *histo; l_uint32 *lines, *datas; l_float32 norm; l_float32 *famean, *fameansq, *favar, *farootvar; l_float32 *famedian, *famode, *famodecount; PROCNAME("pixRowStats"); if (pnamean) *pnamean = NULL; if (pnamedian) *pnamedian = NULL; if (pnamode) *pnamode = NULL; if (pnamodecount) *pnamodecount = NULL; if (pnavar) *pnavar = NULL; if (pnarootvar) *pnarootvar = NULL; if (!pixs || pixGetDepth(pixs) != 8) return ERROR_INT("pixs undefined or not 8 bpp", procName, 1); famean = fameansq = favar = farootvar = NULL; famedian = famode = famodecount = NULL; pixGetDimensions(pixs, &w, &h, NULL); if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, ¥d, &bw, &bh) == 1) return ERROR_INT("invalid clipping box", procName, 1); /* We need the mean for variance and root variance */ datas = pixGetData(pixs); wpls = pixGetWpl(pixs); if (pnamean || pnavar || pnarootvar) { norm = 1. / (l_float32)bw; famean = (l_float32 *)LEPT_CALLOC(bh, sizeof(l_float32)); fameansq = (l_float32 *)LEPT_CALLOC(bh, sizeof(l_float32)); if (pnavar || pnarootvar) { favar = (l_float32 *)LEPT_CALLOC(bh, sizeof(l_float32)); if (pnarootvar) farootvar = (l_float32 *)LEPT_CALLOC(bh, sizeof(l_float32)); } for (i = ystart; i < yend; i++) { sum = sumsq = 0; lines = datas + i * wpls; for (j = xstart; j < xend; j++) { val = GET_DATA_BYTE(lines, j); sum += val; sumsq += val * val; } famean[i] = norm * sum; fameansq[i] = norm * sumsq; if (pnavar || pnarootvar) { favar[i] = fameansq[i] - famean[i] * famean[i]; if (pnarootvar) farootvar[i] = sqrtf(favar[i]); } } LEPT_FREE(fameansq); if (pnamean) *pnamean = numaCreateFromFArray(famean, bh, L_INSERT); else LEPT_FREE(famean); if (pnavar) *pnavar = numaCreateFromFArray(favar, bh, L_INSERT); else LEPT_FREE(favar); if (pnarootvar) *pnarootvar = numaCreateFromFArray(farootvar, bh, L_INSERT); } /* We need a histogram to find the median and/or mode values */ if (pnamedian || pnamode || pnamodecount) { histo = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32)); if (pnamedian) { *pnamedian = numaMakeConstant(0, bh); famedian = numaGetFArray(*pnamedian, L_NOCOPY); } if (pnamode) { *pnamode = numaMakeConstant(0, bh); famode = numaGetFArray(*pnamode, L_NOCOPY); } if (pnamodecount) { *pnamodecount = numaMakeConstant(0, bh); famodecount = numaGetFArray(*pnamodecount, L_NOCOPY); } for (i = ystart; i < yend; i++) { lines = datas + i * wpls; memset(histo, 0, 1024); for (j = xstart; j < xend; j++) { val = GET_DATA_BYTE(lines, j); histo[val]++; } if (pnamedian) { sum = 0; target = (bw + 1) / 2; for (k = 0; k < 256; k++) { sum += histo[k]; if (sum >= target) { famedian[i] = k; break; } } } if (pnamode || pnamodecount) { max = 0; modeval = 0; for (k = 0; k < 256; k++) { if (histo[k] > max) { max = histo[k]; modeval = k; } } if (pnamode) famode[i] = modeval; if (pnamodecount) famodecount[i] = max; } } LEPT_FREE(histo); } return 0; } /*! * \brief pixColumnStats() * * \param[in] pixs 8 bpp; not cmapped * \param[in] box [optional] clipping box; can be null * \param[out] pnamean [optional] numa of mean values * \param[out] pnamedian [optional] numa of median values * \param[out] pnamode [optional] numa of mode intensity values * \param[out] pnamodecount [optional] numa of mode counts * \param[out] pnavar [optional] numa of variance * \param[out] pnarootvar [optional] numa of square root of variance * \return na numa of requested statistic for each column, * or NULL on error * *
 * Notes:
 *      (1) This computes numas that represent row vectors of statistics,
 *          with each of its values derived from the corresponding col of a Pix.
 *      (2) Use NULL on input to prevent computation of any of the 5 numas.
 *      (3) Other functions that compute pixel column statistics are:
 *             pixCountPixelsByColumn()
 *             pixAverageByColumn()
 *             pixVarianceByColumn()
 *             pixGetColumnStats()
 * 
*/ l_int32 pixColumnStats(PIX *pixs, BOX *box, NUMA **pnamean, NUMA **pnamedian, NUMA **pnamode, NUMA **pnamodecount, NUMA **pnavar, NUMA **pnarootvar) { l_int32 i, j, k, w, h, val, wpls, sum, sumsq, target, max, modeval; l_int32 xstart, xend, ystart, yend, bw, bh; l_int32 *histo; l_uint32 *lines, *datas; l_float32 norm; l_float32 *famean, *fameansq, *favar, *farootvar; l_float32 *famedian, *famode, *famodecount; PROCNAME("pixColumnStats"); if (pnamean) *pnamean = NULL; if (pnamedian) *pnamedian = NULL; if (pnamode) *pnamode = NULL; if (pnamodecount) *pnamodecount = NULL; if (pnavar) *pnavar = NULL; if (pnarootvar) *pnarootvar = NULL; if (!pixs || pixGetDepth(pixs) != 8) return ERROR_INT("pixs undefined or not 8 bpp", procName, 1); famean = fameansq = favar = farootvar = NULL; famedian = famode = famodecount = NULL; pixGetDimensions(pixs, &w, &h, NULL); if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, ¥d, &bw, &bh) == 1) return ERROR_INT("invalid clipping box", procName, 1); /* We need the mean for variance and root variance */ datas = pixGetData(pixs); wpls = pixGetWpl(pixs); if (pnamean || pnavar || pnarootvar) { norm = 1. / (l_float32)bh; famean = (l_float32 *)LEPT_CALLOC(bw, sizeof(l_float32)); fameansq = (l_float32 *)LEPT_CALLOC(bw, sizeof(l_float32)); if (pnavar || pnarootvar) { favar = (l_float32 *)LEPT_CALLOC(bw, sizeof(l_float32)); if (pnarootvar) farootvar = (l_float32 *)LEPT_CALLOC(bw, sizeof(l_float32)); } for (j = xstart; j < xend; j++) { sum = sumsq = 0; for (i = ystart, lines = datas; i < yend; lines += wpls, i++) { val = GET_DATA_BYTE(lines, j); sum += val; sumsq += val * val; } famean[j] = norm * sum; fameansq[j] = norm * sumsq; if (pnavar || pnarootvar) { favar[j] = fameansq[j] - famean[j] * famean[j]; if (pnarootvar) farootvar[j] = sqrtf(favar[j]); } } LEPT_FREE(fameansq); if (pnamean) *pnamean = numaCreateFromFArray(famean, bw, L_INSERT); else LEPT_FREE(famean); if (pnavar) *pnavar = numaCreateFromFArray(favar, bw, L_INSERT); else LEPT_FREE(favar); if (pnarootvar) *pnarootvar = numaCreateFromFArray(farootvar, bw, L_INSERT); } /* We need a histogram to find the median and/or mode values */ if (pnamedian || pnamode || pnamodecount) { histo = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32)); if (pnamedian) { *pnamedian = numaMakeConstant(0, bw); famedian = numaGetFArray(*pnamedian, L_NOCOPY); } if (pnamode) { *pnamode = numaMakeConstant(0, bw); famode = numaGetFArray(*pnamode, L_NOCOPY); } if (pnamodecount) { *pnamodecount = numaMakeConstant(0, bw); famodecount = numaGetFArray(*pnamodecount, L_NOCOPY); } for (j = xstart; j < xend; j++) { memset(histo, 0, 1024); for (i = ystart, lines = datas; i < yend; lines += wpls, i++) { val = GET_DATA_BYTE(lines, j); histo[val]++; } if (pnamedian) { sum = 0; target = (bh + 1) / 2; for (k = 0; k < 256; k++) { sum += histo[k]; if (sum >= target) { famedian[j] = k; break; } } } if (pnamode || pnamodecount) { max = 0; modeval = 0; for (k = 0; k < 256; k++) { if (histo[k] > max) { max = histo[k]; modeval = k; } } if (pnamode) famode[j] = modeval; if (pnamodecount) famodecount[j] = max; } } LEPT_FREE(histo); } return 0; } /*! * \brief pixGetRangeValues() * * \param[in] pixs 8 bpp grayscale, 32 bpp rgb, or colormapped * \param[in] factor subsampling factor; >= 1; ignored if colormapped * \param[in] color L_SELECT_RED, L_SELECT_GREEN or L_SELECT_BLUE * \param[out] pminval [optional] minimum value of component * \param[out] pmaxval [optional] maximum value of component * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) If pixs is 8 bpp grayscale, the color selection type is ignored.
 * 
*/ l_ok pixGetRangeValues(PIX *pixs, l_int32 factor, l_int32 color, l_int32 *pminval, l_int32 *pmaxval) { l_int32 d; PIXCMAP *cmap; PROCNAME("pixGetRangeValues"); if (pminval) *pminval = 0; if (pmaxval) *pmaxval = 0; if (!pminval && !pmaxval) return ERROR_INT("no result requested", procName, 1); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); cmap = pixGetColormap(pixs); if (cmap) return pixcmapGetRangeValues(cmap, color, pminval, pmaxval, NULL, NULL); if (factor < 1) return ERROR_INT("sampling factor must be >= 1", procName, 1); d = pixGetDepth(pixs); if (d != 8 && d != 32) return ERROR_INT("pixs not 8 or 32 bpp", procName, 1); if (d == 8) { pixGetExtremeValue(pixs, factor, L_SELECT_MIN, NULL, NULL, NULL, pminval); pixGetExtremeValue(pixs, factor, L_SELECT_MAX, NULL, NULL, NULL, pmaxval); } else if (color == L_SELECT_RED) { pixGetExtremeValue(pixs, factor, L_SELECT_MIN, pminval, NULL, NULL, NULL); pixGetExtremeValue(pixs, factor, L_SELECT_MAX, pmaxval, NULL, NULL, NULL); } else if (color == L_SELECT_GREEN) { pixGetExtremeValue(pixs, factor, L_SELECT_MIN, NULL, pminval, NULL, NULL); pixGetExtremeValue(pixs, factor, L_SELECT_MAX, NULL, pmaxval, NULL, NULL); } else if (color == L_SELECT_BLUE) { pixGetExtremeValue(pixs, factor, L_SELECT_MIN, NULL, NULL, pminval, NULL); pixGetExtremeValue(pixs, factor, L_SELECT_MAX, NULL, NULL, pmaxval, NULL); } else { return ERROR_INT("invalid color", procName, 1); } return 0; } /*! * \brief pixGetExtremeValue() * * \param[in] pixs 8 bpp grayscale, 32 bpp rgb, or colormapped * \param[in] factor subsampling factor; >= 1; ignored if colormapped * \param[in] type L_SELECT_MIN or L_SELECT_MAX * \param[out] prval [optional] red component * \param[out] pgval [optional] green component * \param[out] pbval [optional] blue component * \param[out] pgrayval [optional] min or max 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_ok 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 (prval) *prval = -1; if (pgval) *pgval = -1; if (pbval) *pbval = -1; if (pgrayval) *pgrayval = -1; if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (type != L_SELECT_MIN && type != L_SELECT_MAX) return ERROR_INT("invalid type", procName, 1); cmap = pixGetColormap(pixs); if (cmap) { if (type == L_SELECT_MIN) { if (prval) pixcmapGetRangeValues(cmap, L_SELECT_RED, prval, NULL, NULL, NULL); if (pgval) pixcmapGetRangeValues(cmap, L_SELECT_GREEN, pgval, NULL, NULL, NULL); if (pbval) pixcmapGetRangeValues(cmap, L_SELECT_BLUE, pbval, NULL, NULL, NULL); } else { /* type == L_SELECT_MAX */ if (prval) pixcmapGetRangeValues(cmap, L_SELECT_RED, NULL, prval, NULL, NULL); if (pgval) pixcmapGetRangeValues(cmap, L_SELECT_GREEN, NULL, pgval, NULL, NULL); if (pbval) pixcmapGetRangeValues(cmap, L_SELECT_BLUE, NULL, pbval, NULL, NULL); } return 0; } pixGetDimensions(pixs, &w, &h, &d); if (factor < 1) return ERROR_INT("sampling factor must be >= 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_SELECT_MIN) extval = 100000; else /* get max */ extval = -1; 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_SELECT_MIN && val < extval) || (type == L_SELECT_MAX && val > extval)) extval = val; } } *pgrayval = extval; return 0; } /* 32 bpp rgb */ if (type == L_SELECT_MIN) { extrval = 100000; extgval = 100000; extbval = 100000; } else { extrval = -1; extgval = -1; extbval = -1; } 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_SELECT_MIN && rval < extrval) || (type == L_SELECT_MAX && rval > extrval)) extrval = rval; } if (pgval) { gval = (pixel >> L_GREEN_SHIFT) & 0xff; if ((type == L_SELECT_MIN && gval < extgval) || (type == L_SELECT_MAX && gval > extgval)) extgval = gval; } if (pbval) { bval = (pixel >> L_BLUE_SHIFT) & 0xff; if ((type == L_SELECT_MIN && bval < extbval) || (type == L_SELECT_MAX && bval > extbval)) extbval = bval; } } } if (prval) *prval = extrval; if (pgval) *pgval = extgval; if (pbval) *pbval = extbval; return 0; } /*! * \brief pixGetMaxValueInRect() * * \param[in] pixs 8, 16 or 32 bpp grayscale; no color space components * \param[in] box [optional] region; set box = NULL to use entire pixs * \param[out] pmaxval [optional] max value in region * \param[out] pxmax [optional] x location of max value * \param[out] pymax [optional] 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_ok 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) *pmaxval = 0; if (pxmax) *pxmax = 0; if (pymax) *pymax = 0; if (!pmaxval && !pxmax && !pymax) return ERROR_INT("no data requested", procName, 1); 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 != 16 && d != 32) return ERROR_INT("pixs not 8, 16 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 if (d == 16) val = GET_DATA_TWO_BYTES(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; } /*! * \brief pixGetMaxColorIndex() * * \param[in] pixs 1, 2, 4 or 8 bpp colormapped * \param[out] pmaxindex max colormap index value * \return 0 if OK, 1 on error */ l_ok pixGetMaxColorIndex(PIX *pixs, l_int32 *pmaxindex) { l_int32 i, j, w, h, d, wpl, val, max, maxval, empty; l_uint32 *data, *line; PROCNAME("pixGetMaxColorIndex"); if (!pmaxindex) return ERROR_INT("&maxindex not defined", procName, 1); *pmaxindex = 0; if (!pixs) return ERROR_INT("pixs not defined", procName, 1); pixGetDimensions(pixs, &w, &h, &d); if (d != 1 && d != 2 && d != 4 && d != 8) return ERROR_INT("invalid pixs depth; not in (1,2,4,8}", procName, 1); wpl = pixGetWpl(pixs); data = pixGetData(pixs); max = 0; maxval = (1 << d) - 1; if (d == 1) { pixZero(pixs, &empty); if (!empty) max = 1; *pmaxindex = max; return 0; } for (i = 0; i < h; i++) { line = data + i * wpl; if (d == 2) { for (j = 0; j < w; j++) { val = GET_DATA_DIBIT(line, j); if (val > max) max = val; } } else if (d == 4) { for (j = 0; j < w; j++) { val = GET_DATA_QBIT(line, j); if (val > max) max = val; } } else if (d == 8) { for (j = 0; j < w; j++) { val = GET_DATA_BYTE(line, j); if (val > max) max = val; } } if (max == maxval) break; } *pmaxindex = max; return 0; } /*! * \brief pixGetBinnedComponentRange() * * \param[in] pixs 32 bpp rgb * \param[in] nbins number of equal population bins; must be > 1 * \param[in] factor subsampling factor; >= 1 * \param[in] color L_SELECT_RED, L_SELECT_GREEN or L_SELECT_BLUE * \param[out] pminval [optional] minimum value of component * \param[out] pmaxval [optional] maximum value of component * \param[out] pcarray [optional] color array of bins * \param[in] fontsize [optional] 0 for no debug; for debug, valid set * is {4,6,8,10,12,14,16,18,20}. * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) This returns the min and max average values of the
 *          selected color component in the set of rank bins,
 *          where the ranking is done using the specified component.
 * 
*/ l_ok pixGetBinnedComponentRange(PIX *pixs, l_int32 nbins, l_int32 factor, l_int32 color, l_int32 *pminval, l_int32 *pmaxval, l_uint32 **pcarray, l_int32 fontsize) { l_int32 i, minval, maxval, rval, gval, bval; l_uint32 *carray; PIX *pixt; PROCNAME("pixGetBinnedComponentRange"); if (pminval) *pminval = 0; if (pmaxval) *pmaxval = 0; if (pcarray) *pcarray = NULL; if (!pminval && !pmaxval) return ERROR_INT("no result requested", procName, 1); if (!pixs || pixGetDepth(pixs) != 32) return ERROR_INT("pixs not defined or not 32 bpp", procName, 1); if (factor < 1) return ERROR_INT("sampling factor must be >= 1", procName, 1); if (color != L_SELECT_RED && color != L_SELECT_GREEN && color != L_SELECT_BLUE) return ERROR_INT("invalid color", procName, 1); if (fontsize < 0 || fontsize > 20 || fontsize & 1 || fontsize == 2) return ERROR_INT("invalid fontsize", procName, 1); pixGetRankColorArray(pixs, nbins, color, factor, &carray, NULL, 0); if (!carray) return ERROR_INT("carray not made", procName, 1); if (fontsize > 0) { for (i = 0; i < nbins; i++) L_INFO("c[%d] = %x\n", procName, i, carray[i]); pixt = pixDisplayColorArray(carray, nbins, 200, 5, fontsize); pixDisplay(pixt, 100, 100); pixDestroy(&pixt); } extractRGBValues(carray[0], &rval, &gval, &bval); minval = rval; if (color == L_SELECT_GREEN) minval = gval; else if (color == L_SELECT_BLUE) minval = bval; extractRGBValues(carray[nbins - 1], &rval, &gval, &bval); maxval = rval; if (color == L_SELECT_GREEN) maxval = gval; else if (color == L_SELECT_BLUE) maxval = bval; if (pminval) *pminval = minval; if (pmaxval) *pmaxval = maxval; if (pcarray) *pcarray = carray; else LEPT_FREE(carray); return 0; } /*! * \brief pixGetRankColorArray() * * \param[in] pixs 32 bpp or cmapped * \param[in] nbins number of equal population bins; must be > 1 * \param[in] type color selection flag * \param[in] factor subsampling factor; integer >= 1 * \param[out] pcarray array of colors, ranked by intensity * \param[in] pixadb [optional] debug: caller passes this in. * Use to display color squares and to * capture plots of color components * \param[in] fontsize [optional] debug: only used if pixadb exists. * Valid set is {4,6,8,10,12,14,16,18,20}. * fontsize == 6 is typical. * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) The color selection flag is one of: L_SELECT_RED, L_SELECT_GREEN,
 *          L_SELECT_BLUE, L_SELECT_MIN, L_SELECT_MAX, L_SELECT_AVERAGE,
 *          L_SELECT_HUE, L_SELECT_SATURATION.
 *      (2) The pixels are ordered by the value of the selected color
            value, and an equal number are placed in %nbins.  The average
 *          color in each bin is returned in a color array with %nbins colors.
 *      (3) Set the subsampling factor > 1 to reduce the amount of
 *          computation.  Typically you want at least 10,000 pixels
 *          for reasonable statistics.  Must be at least 10 samples/bin.
 *      (4) A crude "rank color" as a function of rank can be found from
 *             rankint = (l_int32)(rank * (nbins - 1) + 0.5);
 *             extractRGBValues(array[rankint], &rval, &gval, &bval);
 *          where the rank is in [0.0 ... 1.0].
 * 
*/ l_ok pixGetRankColorArray(PIX *pixs, l_int32 nbins, l_int32 type, l_int32 factor, l_uint32 **pcarray, PIXA *pixadb, l_int32 fontsize) { l_int32 ret, w, h, samplesperbin; l_uint32 *array; PIX *pix1, *pixc, *pixg, *pixd; PIXCMAP *cmap; PROCNAME("pixGetRankColorArray"); if (!pcarray) return ERROR_INT("&carray not defined", procName, 1); *pcarray = NULL; if (factor < 1) return ERROR_INT("sampling factor must be >= 1", procName, 1); if (nbins < 2) return ERROR_INT("nbins must be at least 2", procName, 1); 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 cmapped", procName, 1); if (type != L_SELECT_RED && type != L_SELECT_GREEN && type != L_SELECT_BLUE && type != L_SELECT_MIN && type != L_SELECT_MAX && type != L_SELECT_AVERAGE && type != L_SELECT_HUE && type != L_SELECT_SATURATION) return ERROR_INT("invalid type", procName, 1); if (pixadb) { if (fontsize < 0 || fontsize > 20 || fontsize & 1 || fontsize == 2) { L_WARNING("invalid fontsize %d; setting to 6\n", procName, fontsize); fontsize = 6; } } pixGetDimensions(pixs, &w, &h, NULL); samplesperbin = (w * h) / (factor * factor * nbins); if (samplesperbin < 10) { L_ERROR("samplesperbin = %d < 10\n", procName, samplesperbin); return 1; } /* Downscale by factor and remove colormap if it exists */ pix1 = pixScaleByIntSampling(pixs, factor); if (cmap) pixc = pixRemoveColormap(pix1, REMOVE_CMAP_TO_FULL_COLOR); else pixc = pixClone(pix1); pixDestroy(&pix1); /* Convert to an 8 bit version for ordering the pixels */ pixg = pixConvertRGBToGrayGeneral(pixc, type, 0.0, 0.0, 0.0); /* Get the average color in each bin for pixels whose grayscale * values are in the range for that bin. */ pixGetBinnedColor(pixc, pixg, 1, nbins, pcarray, pixadb); ret = 0; if ((array = *pcarray) == NULL) { L_ERROR("color array not returned\n", procName); ret = 1; } if (array && pixadb) { pixd = pixDisplayColorArray(array, nbins, 200, 5, fontsize); pixWriteDebug("/tmp/lept/regout/rankhisto.png", pixd, IFF_PNG); pixDestroy(&pixd); } pixDestroy(&pixc); pixDestroy(&pixg); return ret; } /*! * \brief pixGetBinnedColor() * * \param[in] pixs 32 bpp * \param[in] pixg 8 bpp grayscale version of pixs * \param[in] factor sampling factor along pixel counting direction * \param[in] nbins number of bins based on grayscale value {1,...,100} * \param[out] pcarray array of average color values in each bin * \param[in] pixadb [optional] debug: caller passes this in. * Use to display output color squares and plots of * color components. * \return 0 if OK; 1 on error * *
 * Notes:
 *      (1) This takes a color image, a grayscale version, and the number
 *          of requested bins.  The pixels are ordered by the corresponding
 *          gray value and an equal number of pixels are put in each bin.
 *          The average color for each bin is returned as an array
 *          of l_uint32 colors in our standard RGBA ordering.  We require
 *          at least 5 pixels in each bin.
 *      (2) This is used by pixGetRankColorArray(), which generates the
 *          grayscale image %pixg from the color image %pixs.
 *      (3) Arrays of float64 are used for intermediate storage, without
 *          loss of precision, of the sampled uint32 pixel values.
 * 
*/ l_ok pixGetBinnedColor(PIX *pixs, PIX *pixg, l_int32 factor, l_int32 nbins, l_uint32 **pcarray, PIXA *pixadb) { l_int32 i, j, k, w, h, wpls, wplg; l_int32 count, bincount, binindex, binsize, npts, avepts, ntot; l_int32 rval, gval, bval, grayval, rave, gave, bave; l_uint32 *datas, *datag, *lines, *lineg, *carray; l_float64 val64, rsum, gsum, bsum; L_DNAA *daa; NUMA *naeach; PIX *pix1; PROCNAME("pixGetBinnedColor"); if (!pcarray) return ERROR_INT("&carray not defined", procName, 1); *pcarray = NULL; if (!pixs || pixGetDepth(pixs) != 32) return ERROR_INT("pixs undefined or not 32 bpp", procName, 1); if (!pixg || pixGetDepth(pixg) != 8) return ERROR_INT("pixg undefined or not 8 bpp", procName, 1); if (factor < 1) { L_WARNING("sampling factor less than 1; setting to 1\n", procName); factor = 1; } if (nbins < 1 || nbins > 100) return ERROR_INT("nbins not in [1,100]", procName, 1); /* Require that each bin has at least 5 pixels. */ pixGetDimensions(pixs, &w, &h, NULL); npts = (w + factor - 1) * (h + factor - 1) / (factor * factor); avepts = (npts + nbins - 1) / nbins; /* average number of pts in a bin */ if (avepts < 5) { L_ERROR("avepts = %d; must be >= 5\n", procName, avepts); return 1; } /* ------------------------------------------------------------ * * Find the average color for each bin. The colors are ordered * * by the gray value in the corresponding pixel in %pixg. * * The bins have equal numbers of pixels (within 1). * * ------------------------------------------------------------ */ /* Generate a dnaa, where each dna has the colors corresponding * to the grayscale value given by the index of the dna in the dnaa */ datas = pixGetData(pixs); wpls = pixGetWpl(pixs); datag = pixGetData(pixg); wplg = pixGetWpl(pixg); daa = l_dnaaCreateFull(256, 0); for (i = 0; i < h; i += factor) { lines = datas + i * wpls; lineg = datag + i * wplg; for (j = 0; j < w; j += factor) { grayval = GET_DATA_BYTE(lineg, j); l_dnaaAddNumber(daa, grayval, lines[j]); } } if (pixadb) { NUMA *na, *nabinval, *narank; na = numaCreate(256); /* grayscale histogram */ for (i = 0; i < 256; i++) numaAddNumber(na, l_dnaaGetDnaCount(daa, i)); /* Plot the gray bin value and the rank(gray) values */ numaDiscretizeHistoInBins(na, nbins, &nabinval, &narank); pix1 = gplotSimplePix1(nabinval, "Gray value in each bin"); pixaAddPix(pixadb, pix1, L_INSERT); pix1 = gplotSimplePix1(narank, "rank as function of gray value"); pixaAddPix(pixadb, pix1, L_INSERT); numaDestroy(&na); numaDestroy(&nabinval); numaDestroy(&narank); } /* Get the number of items in each bin */ ntot = l_dnaaGetNumberCount(daa); if ((naeach = numaGetUniformBinSizes(ntot, nbins)) == NULL) { l_dnaaDestroy(&daa); return ERROR_INT("naeach not made", procName, 1); } /* Get the average color in each bin. This algorithm is * esssentially the same as in numaDiscretizeHistoInBins() */ carray = (l_uint32 *)LEPT_CALLOC(nbins, sizeof(l_uint32)); rsum = gsum = bsum = 0.0; bincount = 0; binindex = 0; numaGetIValue(naeach, 0, &binsize); k = 0; /* count up to ntot */ for (i = 0; i < 256; i++) { count = l_dnaaGetDnaCount(daa, i); for (j = 0; j < count; j++) { k++; bincount++; l_dnaaGetValue(daa, i, j, &val64); extractRGBValues((l_uint32)val64, &rval, &gval, &bval); rsum += rval; gsum += gval; bsum += bval; if (bincount == binsize) { /* add bin entry */ rave = (l_int32)(rsum / binsize + 0.5); gave = (l_int32)(gsum / binsize + 0.5); bave = (l_int32)(bsum / binsize + 0.5); composeRGBPixel(rave, gave, bave, carray + binindex); rsum = gsum = bsum = 0.0; bincount = 0; binindex++; if (binindex == nbins) break; numaGetIValue(naeach, binindex, &binsize); } } if (binindex == nbins) break; } if (binindex != nbins) L_ERROR("binindex = %d != nbins = %d\n", procName, binindex, nbins); if (pixadb) { NUMA *nared, *nagreen, *nablue; nared = numaCreate(nbins); nagreen = numaCreate(nbins); nablue = numaCreate(nbins); for (i = 0; i < nbins; i++) { extractRGBValues(carray[i], &rval, &gval, &bval); numaAddNumber(nared, rval); numaAddNumber(nagreen, gval); numaAddNumber(nablue, bval); } lept_mkdir("lept/regout"); pix1 = gplotSimplePix1(nared, "Average red val vs. rank bin"); pixaAddPix(pixadb, pix1, L_INSERT); pix1 = gplotSimplePix1(nagreen, "Average green val vs. rank bin"); pixaAddPix(pixadb, pix1, L_INSERT); pix1 = gplotSimplePix1(nablue, "Average blue val vs. rank bin"); pixaAddPix(pixadb, pix1, L_INSERT); numaDestroy(&nared); numaDestroy(&nagreen); numaDestroy(&nablue); } *pcarray = carray; numaDestroy(&naeach); l_dnaaDestroy(&daa); return 0; } /*! * \brief pixDisplayColorArray() * * \param[in] carray array of colors: 0xrrggbb00 * \param[in] ncolors size of array * \param[in] side size of each color square; suggest 200 * \param[in] ncols number of columns in output color matrix * \param[in] fontsize to label each square with text. * Valid set is {4,6,8,10,12,14,16,18,20}. * Suggest 6 for 200x200 square. Use 0 to disable. * \return pixd color array, or NULL on error * *
 * Notes:
 *      (1) This generates an array of labeled color squares from an
 *          array of color values.
 *      (2) To make a single color square, use pixMakeColorSquare().
 * 
*/ PIX * pixDisplayColorArray(l_uint32 *carray, l_int32 ncolors, l_int32 side, l_int32 ncols, l_int32 fontsize) { char textstr[256]; l_int32 i, rval, gval, bval; L_BMF *bmf; PIX *pix1, *pix2, *pix3, *pix4; PIXA *pixa; PROCNAME("pixDisplayColorArray"); if (!carray) return (PIX *)ERROR_PTR("carray not defined", procName, NULL); if (fontsize < 0 || fontsize > 20 || fontsize & 1 || fontsize == 2) return (PIX *)ERROR_PTR("invalid fontsize", procName, NULL); bmf = (fontsize == 0) ? NULL : bmfCreate(NULL, fontsize); pixa = pixaCreate(ncolors); for (i = 0; i < ncolors; i++) { pix1 = pixCreate(side, side, 32); pixSetAllArbitrary(pix1, carray[i]); pix2 = pixAddBorder(pix1, 2, 1); if (bmf) { extractRGBValues(carray[i], &rval, &gval, &bval); snprintf(textstr, sizeof(textstr), "%d: (%d %d %d)", i, rval, gval, bval); pix3 = pixAddSingleTextblock(pix2, bmf, textstr, 0xff000000, L_ADD_BELOW, NULL); } else { pix3 = pixClone(pix2); } pixaAddPix(pixa, pix3, L_INSERT); pixDestroy(&pix1); pixDestroy(&pix2); } pix4 = pixaDisplayTiledInColumns(pixa, ncols, 1.0, 20, 2); pixaDestroy(&pixa); bmfDestroy(&bmf); return pix4; } /*! * \brief pixRankBinByStrip() * * \param[in] pixs 32 bpp or cmapped * \param[in] direction L_SCAN_HORIZONTAL or L_SCAN_VERTICAL * \param[in] size of strips in scan direction * \param[in] nbins number of equal population bins; must be > 1 * \param[in] type color selection flag * \return pixd result, or NULL on error * *
 * Notes:
 *      (1) This generates a pix of height %nbins, where each column
 *          represents a horizontal or vertical strip of the input image.
 *          If %direction == L_SCAN_HORIZONTAL, the input image is
 *          tiled into vertical strips of width %size, where %size is
 *          chosen as a compromise between getting better spatial
 *          columnwise resolution (small %size) and getting better
 *          columnwise statistical information (larger %size).  Likewise
 *          with rows of the image if %direction == L_SCAN_VERTICAL.
 *      (2) For L_HORIZONTAL_SCAN, the output pix contains rank binned
 *          median colors in each column that correspond to a vertical
 *          strip of width %size in the input image.
 *      (3) The color selection flag is one of: L_SELECT_RED, L_SELECT_GREEN,
 *          L_SELECT_BLUE, L_SELECT_MIN, L_SELECT_MAX, L_SELECT_AVERAGE,
 *          L_SELECT_HUE, L_SELECT_SATURATION.
 *          It determines how the rank ordering is done.
 *      (4) Typical input values might be %size = 5, %nbins = 10.
 * 
*/ PIX * pixRankBinByStrip(PIX *pixs, l_int32 direction, l_int32 size, l_int32 nbins, l_int32 type) { l_int32 i, j, w, h, mindim, nstrips; l_uint32 *array; BOXA *boxa; PIX *pix1, *pix2, *pixd; PIXA *pixa; PIXCMAP *cmap; PROCNAME("pixRankBinByStrip"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); cmap = pixGetColormap(pixs); if (pixGetDepth(pixs) != 32 && !cmap) return (PIX *)ERROR_PTR("pixs neither 32 bpp nor cmapped", procName, NULL); if (direction != L_SCAN_HORIZONTAL && direction != L_SCAN_VERTICAL) return (PIX *)ERROR_PTR("invalid direction", procName, NULL); if (size < 1) return (PIX *)ERROR_PTR("size < 1", procName, NULL); if (nbins < 2) return (PIX *)ERROR_PTR("nbins must be at least 2", procName, NULL); if (type != L_SELECT_RED && type != L_SELECT_GREEN && type != L_SELECT_BLUE && type != L_SELECT_MIN && type != L_SELECT_MAX && type != L_SELECT_AVERAGE && type != L_SELECT_HUE && type != L_SELECT_SATURATION) return (PIX *)ERROR_PTR("invalid type", procName, NULL); pixGetDimensions(pixs, &w, &h, NULL); mindim = L_MIN(w, h); if (mindim < 20 || nbins > mindim) return (PIX *)ERROR_PTR("pix too small and/or too many bins", procName, NULL); /* Remove colormap if it exists */ if (cmap) pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_TO_FULL_COLOR); else pix1 = pixClone(pixs); pixGetDimensions(pixs, &w, &h, NULL); pixd = NULL; boxa = makeMosaicStrips(w, h, direction, size); pixa = pixClipRectangles(pix1, boxa); nstrips = pixaGetCount(pixa); if (direction == L_SCAN_HORIZONTAL) { pixd = pixCreate(nstrips, nbins, 32); for (i = 0; i < nstrips; i++) { pix2 = pixaGetPix(pixa, i, L_CLONE); pixGetRankColorArray(pix2, nbins, type, 1, &array, NULL, 0); if (array) { for (j = 0; j < nbins; j++) pixSetPixel(pixd, i, j, array[j]); LEPT_FREE(array); } pixDestroy(&pix2); } } else { /* L_SCAN_VERTICAL */ pixd = pixCreate(nbins, nstrips, 32); for (i = 0; i < nstrips; i++) { pix2 = pixaGetPix(pixa, i, L_CLONE); pixGetRankColorArray(pix2, nbins, type, 1, &array, NULL, 0); if (array) { for (j = 0; j < nbins; j++) pixSetPixel(pixd, j, i, array[j]); LEPT_FREE(array); } pixDestroy(&pix2); } } pixDestroy(&pix1); boxaDestroy(&boxa); pixaDestroy(&pixa); return pixd; } /*-------------------------------------------------------------* * Pixelwise aligned statistics * *-------------------------------------------------------------*/ /*! * \brief pixaGetAlignedStats() * * \param[in] pixa of identically sized, 8 bpp pix; not cmapped * \param[in] type L_MEAN_ABSVAL, L_MEDIAN_VAL, L_MODE_VAL, L_MODE_COUNT * \param[in] nbins of histogram for median and mode; ignored for mean * \param[in] 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 *)LEPT_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); } LEPT_FREE(colvect); pixDestroy(&pixt); return pixd; } /*! * \brief pixaExtractColumnFromEachPix() * * \param[in] pixa of identically sized, 8 bpp; not cmapped * \param[in] col column index * \param[in] pixd pix into which each column is inserted * \return 0 if OK, 1 on error */ l_ok 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("pixd 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; } /*! * \brief pixGetRowStats() * * \param[in] pixs 8 bpp; not cmapped * \param[in] type L_MEAN_ABSVAL, L_MEDIAN_VAL, L_MODE_VAL, L_MODE_COUNT * \param[in] nbins of histogram for median and mode; ignored for mean * \param[in] thresh on histogram for mode; ignored for mean and median * \param[in] 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_ok 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 *)LEPT_CALLOC(nbins, sizeof(l_int32)); gray2bin = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32)); bin2gray = (l_int32 *)LEPT_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 * 256 + 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 + 1) / 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; for (k = 0; k < nbins; k++) { if (histo[k] > max) max = histo[k]; } colvect[i] = max; } } LEPT_FREE(histo); LEPT_FREE(gray2bin); LEPT_FREE(bin2gray); return 0; } /*! * \brief pixGetColumnStats() * * \param[in] pixs 8 bpp; not cmapped * \param[in] type L_MEAN_ABSVAL, L_MEDIAN_VAL, L_MODE_VAL, L_MODE_COUNT * \param[in] nbins of histogram for median and mode; ignored for mean * \param[in] thresh on histogram for mode val; ignored for all other types * \param[in] 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_ok 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 *)LEPT_CALLOC(nbins, sizeof(l_int32)); gray2bin = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32)); bin2gray = (l_int32 *)LEPT_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 * 256 + 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 + 1) / 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; for (k = 0; k < nbins; k++) { if (histo[k] > max) max = histo[k]; } rowvect[j] = max; } for (k = 0; k < nbins; k++) histo[k] = 0; } LEPT_FREE(histo); LEPT_FREE(gray2bin); LEPT_FREE(bin2gray); return 0; } /*! * \brief pixSetPixelColumn() * * \param[in] pix 8 bpp; not cmapped * \param[in] col column index * \param[in] colvect vector of floats * \return 0 if OK, 1 on error */ l_ok 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 * *-------------------------------------------------------------*/ /*! * \brief pixThresholdForFgBg() * * \param[in] pixs any depth; cmapped ok * \param[in] factor subsampling factor; integer >= 1 * \param[in] thresh threshold for generating foreground mask * \param[out] pfgval [optional] average foreground value * \param[out] pbgval [optional] average background value * \return 0 if OK, 1 on error */ l_ok pixThresholdForFgBg(PIX *pixs, l_int32 factor, l_int32 thresh, l_int32 *pfgval, l_int32 *pbgval) { l_float32 fval; PIX *pixg, *pixm; PROCNAME("pixThresholdForFgBg"); if (pfgval) *pfgval = 0; if (pbgval) *pbgval = 0; if (!pfgval && !pbgval) return ERROR_INT("no data requested", procName, 1); 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; } /*! * \brief pixSplitDistributionFgBg() * * \param[in] pixs any depth; cmapped ok * \param[in] scorefract fraction of the max score, used to determine * the range over which the histogram min is searched * \param[in] factor subsampling factor; integer >= 1 * \param[out] pthresh [optional] best threshold for separating * \param[out] pfgval [optional] average foreground value * \param[out] pbgval [optional] average background value * \param[out] ppixdb [optional] plot 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_ok pixSplitDistributionFgBg(PIX *pixs, l_float32 scorefract, l_int32 factor, l_int32 *pthresh, l_int32 *pfgval, l_int32 *pbgval, PIX **ppixdb) { char buf[256]; 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 (ppixdb) *ppixdb = NULL; if (!pthresh && !pfgval && !pbgval) return ERROR_INT("no data requested", procName, 1); 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 (ppixdb) { 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 (ppixdb) { lept_mkdir("lept/redout"); gplot = gplotCreate("/tmp/lept/redout/histplot", GPLOT_PNG, "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)); snprintf(buf, sizeof(buf), "score fract = %3.1f", scorefract); gplotAddPlot(gplot, nax, nay, GPLOT_LINES, buf); *ppixdb = gplotMakeOutputPix(gplot); gplotDestroy(&gplot); numaDestroy(&nax); numaDestroy(&nay); } pixDestroy(&pixg); numaDestroy(&na); return 0; }