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