twain3.0/3rdparty/hgOCR/leptonica/readbarcode.c

1495 lines
52 KiB
C

/*====================================================================*
- 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 readbarcode.c
* <pre>
*
* Basic operations to locate and identify the line widths
* in 1D barcodes.
*
* Top level
* SARRAY *pixProcessBarcodes()
*
* Next levels
* PIXA *pixExtractBarcodes()
* SARRAY *pixReadBarcodes()
* l_int32 pixReadBarcodeWidths()
*
* Location
* BOXA *pixLocateBarcodes()
* static PIX *pixGenerateBarcodeMask()
*
* Extraction and deskew
* PIXA *pixDeskewBarcodes()
*
* Process to get line widths
* NUMA *pixExtractBarcodeWidths1()
* NUMA *pixExtractBarcodeWidths2()
* NUMA *pixExtractBarcodeCrossings()
*
* Average adjacent rasters
* static NUMA *pixAverageRasterScans()
*
* Signal processing for barcode widths
* NUMA *numaQuantizeCrossingsByWidth()
* static l_int32 numaGetCrossingDistances()
* static NUMA *numaLocatePeakRanges()
* static NUMA *numaGetPeakCentroids()
* static NUMA *numaGetPeakWidthLUT()
* NUMA *numaQuantizeCrossingsByWindow()
* static l_int32 numaEvalBestWidthAndShift()
* static l_int32 numaEvalSyncError()
*
*
* NOTE CAREFULLY: This is "early beta" code. It has not been tuned
* to work robustly on a large database of barcode images. I'm putting
* it out so that people can play with it, find out how it breaks, and
* contribute decoders for other barcode formats. Both the functional
* interfaces and ABI will almost certainly change in the coming
* few months. The actual decoder, in bardecode.c, at present only
* works on the following codes: Code I2of5, Code 2of5, Code 39, Code 93
* Codabar and UPCA. To add another barcode format, it is necessary
* to make changes in readbarcode.h and bardecode.c.
* The program prog/barcodetest shows how to run from the top level
* (image --> decoded data).
* </pre>
*/
#include <string.h>
#include "allheaders.h"
#include "readbarcode.h"
/* Parameters for pixGenerateBarcodeMask() */
static const l_int32 MAX_SPACE_WIDTH = 19; /* was 15 */
static const l_int32 MAX_NOISE_WIDTH = 50; /* smaller than barcode width */
static const l_int32 MAX_NOISE_HEIGHT = 30; /* smaller than barcode height */
/* Static functions */
static PIX *pixGenerateBarcodeMask(PIX *pixs, l_int32 maxspace,
l_int32 nwidth, l_int32 nheight);
static NUMA *pixAverageRasterScans(PIX *pixs, l_int32 nscans);
static l_int32 numaGetCrossingDistances(NUMA *nas, NUMA **pnaedist,
NUMA **pnaodist, l_float32 *pmindist,
l_float32 *pmaxdist);
static NUMA *numaLocatePeakRanges(NUMA *nas, l_float32 minfirst,
l_float32 minsep, l_float32 maxmin);
static NUMA *numaGetPeakCentroids(NUMA *nahist, NUMA *narange);
static NUMA *numaGetPeakWidthLUT(NUMA *narange, NUMA *nacent);
static l_int32 numaEvalBestWidthAndShift(NUMA *nas, l_int32 nwidth,
l_int32 nshift, l_float32 minwidth,
l_float32 maxwidth,
l_float32 *pbestwidth,
l_float32 *pbestshift,
l_float32 *pbestscore);
static l_int32 numaEvalSyncError(NUMA *nas, l_int32 ifirst, l_int32 ilast,
l_float32 width, l_float32 shift,
l_float32 *pscore, NUMA **pnad);
#ifndef NO_CONSOLE_IO
#define DEBUG_DESKEW 1
#define DEBUG_WIDTHS 0
#endif /* ~NO_CONSOLE_IO */
/*------------------------------------------------------------------------*
* Top level *
*------------------------------------------------------------------------*/
/*!
* \brief pixProcessBarcodes()
*
* \param[in] pixs any depth
* \param[in] format L_BF_ANY, L_BF_CODEI2OF5, L_BF_CODE93, ...
* \param[in] method L_USE_WIDTHS, L_USE_WINDOWS
* \param[out] psaw [optional] sarray of bar widths
* \param[in] debugflag use 1 to generate debug output
* \return sarray text of barcodes, or NULL if none found or on error
*/
SARRAY *
pixProcessBarcodes(PIX *pixs,
l_int32 format,
l_int32 method,
SARRAY **psaw,
l_int32 debugflag)
{
PIX *pixg;
PIXA *pixa;
SARRAY *sad;
PROCNAME("pixProcessBarcodes");
if (psaw) *psaw = NULL;
if (!pixs)
return (SARRAY *)ERROR_PTR("pixs not defined", procName, NULL);
if (format != L_BF_ANY && !barcodeFormatIsSupported(format))
return (SARRAY *)ERROR_PTR("unsupported format", procName, NULL);
if (method != L_USE_WIDTHS && method != L_USE_WINDOWS)
return (SARRAY *)ERROR_PTR("invalid method", procName, NULL);
/* Get an 8 bpp image, no cmap */
if (pixGetDepth(pixs) == 8 && !pixGetColormap(pixs))
pixg = pixClone(pixs);
else
pixg = pixConvertTo8(pixs, 0);
if ((pixa = pixExtractBarcodes(pixg, debugflag)) == NULL) {
pixDestroy(&pixg);
return (SARRAY *)ERROR_PTR("no barcode(s) found", procName, NULL);
}
sad = pixReadBarcodes(pixa, format, method, psaw, debugflag);
pixDestroy(&pixg);
pixaDestroy(&pixa);
return sad;
}
/*!
* \brief pixExtractBarcodes()
*
* \param[in] pixs 8 bpp, no colormap
* \param[in] debugflag use 1 to generate debug output
* \return pixa deskewed and cropped barcodes, or NULL if none found
* or on error
*/
PIXA *
pixExtractBarcodes(PIX *pixs,
l_int32 debugflag)
{
l_int32 i, n;
l_float32 angle, conf;
BOX *box;
BOXA *boxa;
PIX *pixb, *pixm, *pixt;
PIXA *pixa;
PROCNAME("pixExtractBarcodes");
if (!pixs || pixGetDepth(pixs) != 8 || pixGetColormap(pixs))
return (PIXA *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL);
/* Locate them; use small threshold for edges. */
boxa = pixLocateBarcodes(pixs, 20, &pixb, &pixm);
n = boxaGetCount(boxa);
L_INFO("%d possible barcode(s) found\n", procName, n);
if (n == 0) {
boxaDestroy(&boxa);
pixDestroy(&pixb);
pixDestroy(&pixm);
return NULL;
}
if (debugflag) {
boxaWriteStream(stderr, boxa);
pixDisplay(pixb, 100, 100);
pixDisplay(pixm, 800, 100);
}
/* Deskew each barcode individually */
pixa = pixaCreate(n);
for (i = 0; i < n; i++) {
box = boxaGetBox(boxa, i, L_CLONE);
pixt = pixDeskewBarcode(pixs, pixb, box, 15, 20, &angle, &conf);
L_INFO("angle = %6.2f, conf = %6.2f\n", procName, angle, conf);
if (conf > 5.0) {
pixaAddPix(pixa, pixt, L_INSERT);
pixaAddBox(pixa, box, L_INSERT);
} else {
pixDestroy(&pixt);
boxDestroy(&box);
}
}
#if DEBUG_DESKEW
pixt = pixaDisplayTiledInRows(pixa, 8, 1000, 1.0, 0, 30, 2);
pixWrite("junkpixt", pixt, IFF_PNG);
pixDestroy(&pixt);
#endif /* DEBUG_DESKEW */
pixDestroy(&pixb);
pixDestroy(&pixm);
boxaDestroy(&boxa);
return pixa;
}
/*!
* \brief pixReadBarcodes()
*
* \param[in] pixa of 8 bpp deskewed and cropped barcodes
* \param[in] format L_BF_ANY, L_BF_CODEI2OF5, L_BF_CODE93, ...
* \param[in] method L_USE_WIDTHS, L_USE_WINDOWS;
* \param[out] psaw [optional] sarray of bar widths
* \param[in] debugflag use 1 to generate debug output
* \return sa sarray of widths, one string for each barcode found,
* or NULL on error
*/
SARRAY *
pixReadBarcodes(PIXA *pixa,
l_int32 format,
l_int32 method,
SARRAY **psaw,
l_int32 debugflag)
{
char *barstr, *data;
char emptystring[] = "";
l_int32 i, j, n, nbars, ival;
NUMA *na;
PIX *pixt;
SARRAY *saw, *sad;
PROCNAME("pixReadBarcodes");
if (psaw) *psaw = NULL;
if (!pixa)
return (SARRAY *)ERROR_PTR("pixa not defined", procName, NULL);
if (format != L_BF_ANY && !barcodeFormatIsSupported(format))
return (SARRAY *)ERROR_PTR("unsupported format", procName, NULL);
if (method != L_USE_WIDTHS && method != L_USE_WINDOWS)
return (SARRAY *)ERROR_PTR("invalid method", procName, NULL);
n = pixaGetCount(pixa);
saw = sarrayCreate(n);
sad = sarrayCreate(n);
for (i = 0; i < n; i++) {
/* Extract the widths of the lines in each barcode */
pixt = pixaGetPix(pixa, i, L_CLONE);
na = pixReadBarcodeWidths(pixt, method, debugflag);
pixDestroy(&pixt);
if (!na) {
ERROR_INT("valid barcode widths not returned", procName, 1);
continue;
}
/* Save the widths as a string */
nbars = numaGetCount(na);
barstr = (char *)LEPT_CALLOC(nbars + 1, sizeof(char));
for (j = 0; j < nbars; j++) {
numaGetIValue(na, j, &ival);
barstr[j] = 0x30 + ival;
}
sarrayAddString(saw, barstr, L_INSERT);
numaDestroy(&na);
/* Decode the width strings */
data = barcodeDispatchDecoder(barstr, format, debugflag);
if (!data) {
ERROR_INT("barcode not decoded", procName, 1);
sarrayAddString(sad, emptystring, L_COPY);
continue;
}
sarrayAddString(sad, data, L_INSERT);
}
/* If nothing found, clean up */
if (sarrayGetCount(saw) == 0) {
sarrayDestroy(&saw);
sarrayDestroy(&sad);
return (SARRAY *)ERROR_PTR("no valid barcode data", procName, NULL);
}
if (psaw)
*psaw = saw;
else
sarrayDestroy(&saw);
return sad;
}
/*!
* \brief pixReadBarcodeWidths()
*
* \param[in] pixs of 8 bpp deskewed and cropped barcode
* \param[in] method L_USE_WIDTHS, L_USE_WINDOWS;
* \param[in] debugflag use 1 to generate debug output
* \return na numa of widths (each in set {1,2,3,4}, or NULL on error
*/
NUMA *
pixReadBarcodeWidths(PIX *pixs,
l_int32 method,
l_int32 debugflag)
{
l_float32 winwidth;
NUMA *na;
PROCNAME("pixReadBarcodeWidths");
if (!pixs)
return (NUMA *)ERROR_PTR("pixs not defined", procName, NULL);
if (pixGetDepth(pixs) != 8)
return (NUMA *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
if (method != L_USE_WIDTHS && method != L_USE_WINDOWS)
return (NUMA *)ERROR_PTR("invalid method", procName, NULL);
/* Extract the widths of the lines in each barcode */
if (method == L_USE_WIDTHS)
na = pixExtractBarcodeWidths1(pixs, 120, 0.25, NULL, NULL,
debugflag);
else /* method == L_USE_WINDOWS */
na = pixExtractBarcodeWidths2(pixs, 120, &winwidth,
NULL, debugflag);
#if DEBUG_WIDTHS
if (method == L_USE_WINDOWS)
fprintf(stderr, "Window width for barcode: %7.3f\n", winwidth);
numaWriteStream(stderr, na);
#endif /* DEBUG_WIDTHS */
if (!na)
return (NUMA *)ERROR_PTR("barcode widths invalid", procName, NULL);
return na;
}
/*------------------------------------------------------------------------*
* Locate barcode in image *
*------------------------------------------------------------------------*/
/*!
* \brief pixLocateBarcodes()
*
* \param[in] pixs any depth
* \param[in] thresh for binarization of edge filter output; typ. 20
* \param[out] ppixb [optional] binarized edge filtered input image
* \param[out] ppixm [optional] mask over barcodes
* \return boxa location of barcodes, or NULL if none found or on error
*/
BOXA *
pixLocateBarcodes(PIX *pixs,
l_int32 thresh,
PIX **ppixb,
PIX **ppixm)
{
BOXA *boxa;
PIX *pix8, *pixe, *pixb, *pixm;
PROCNAME("pixLocateBarcodes");
if (!pixs)
return (BOXA *)ERROR_PTR("pixs not defined", procName, NULL);
/* Get an 8 bpp image, no cmap */
if (pixGetDepth(pixs) == 8 && !pixGetColormap(pixs))
pix8 = pixClone(pixs);
else
pix8 = pixConvertTo8(pixs, 0);
/* Get a 1 bpp image of the edges */
pixe = pixSobelEdgeFilter(pix8, L_ALL_EDGES);
pixb = pixThresholdToBinary(pixe, thresh);
pixInvert(pixb, pixb);
pixDestroy(&pix8);
pixDestroy(&pixe);
pixm = pixGenerateBarcodeMask(pixb, MAX_SPACE_WIDTH, MAX_NOISE_WIDTH,
MAX_NOISE_HEIGHT);
boxa = pixConnComp(pixm, NULL, 8);
if (ppixb)
*ppixb = pixb;
else
pixDestroy(&pixb);
if (ppixm)
*ppixm = pixm;
else
pixDestroy(&pixm);
return boxa;
}
/*!
* \brief pixGenerateBarcodeMask()
*
* \param[in] pixs 1 bpp
* \param[in] maxspace largest space in the barcode, in pixels
* \param[in] nwidth opening 'width' to remove noise
* \param[in] nheight opening 'height' to remove noise
* \return pixm mask over barcodes, or NULL if none found or on error
*
* <pre>
* Notes:
* (1) For noise removal, 'width' and 'height' are referred to the
* barcode orientation.
* (2) If there is skew, the mask will not cover the barcode corners.
* </pre>
*/
static PIX *
pixGenerateBarcodeMask(PIX *pixs,
l_int32 maxspace,
l_int32 nwidth,
l_int32 nheight)
{
PIX *pixt1, *pixt2, *pixd;
PROCNAME("pixGenerateBarcodeMask");
if (!pixs || pixGetDepth(pixs) != 1)
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
/* Identify horizontal barcodes */
pixt1 = pixCloseBrick(NULL, pixs, maxspace + 1, 1);
pixt2 = pixOpenBrick(NULL, pixs, maxspace + 1, 1);
pixXor(pixt2, pixt2, pixt1);
pixOpenBrick(pixt2, pixt2, nwidth, nheight);
pixDestroy(&pixt1);
/* Identify vertical barcodes */
pixt1 = pixCloseBrick(NULL, pixs, 1, maxspace + 1);
pixd = pixOpenBrick(NULL, pixs, 1, maxspace + 1);
pixXor(pixd, pixd, pixt1);
pixOpenBrick(pixd, pixd, nheight, nwidth);
pixDestroy(&pixt1);
/* Combine to get all barcodes */
pixOr(pixd, pixd, pixt2);
pixDestroy(&pixt2);
return pixd;
}
/*------------------------------------------------------------------------*
* Extract and deskew barcode *
*------------------------------------------------------------------------*/
/*!
* \brief pixDeskewBarcode()
*
* \param[in] pixs input image; 8 bpp
* \param[in] pixb binarized edge-filtered input image
* \param[in] box identified region containing barcode
* \param[in] margin of extra pixels around box to extract
* \param[in] threshold for binarization; ~20
* \param[out] pangle [optional] in degrees, clockwise is positive
* \param[out] pconf [optional] confidence
* \return pixd deskewed barcode, or NULL on error
*
* <pre>
* Notes:
* (1) The (optional) angle returned is the angle in degrees (cw positive)
* necessary to rotate the image so that it is deskewed.
* </pre>
*/
PIX *
pixDeskewBarcode(PIX *pixs,
PIX *pixb,
BOX *box,
l_int32 margin,
l_int32 threshold,
l_float32 *pangle,
l_float32 *pconf)
{
l_int32 x, y, w, h, n;
l_float32 angle, angle1, angle2, conf, conf1, conf2, score1, score2, deg2rad;
BOX *boxe, *boxt;
BOXA *boxa, *boxat;
PIX *pixt1, *pixt2, *pixt3, *pixt4, *pixt5, *pixt6, *pixd;
PROCNAME("pixDeskewBarcode");
if (!pixs || pixGetDepth(pixs) != 8)
return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL);
if (!pixb || pixGetDepth(pixb) != 1)
return (PIX *)ERROR_PTR("pixb undefined or not 1 bpp", procName, NULL);
if (!box)
return (PIX *)ERROR_PTR("box not defined or 1 bpp", procName, NULL);
/* Clip out */
deg2rad = 3.1415926535 / 180.;
boxGetGeometry(box, &x, &y, &w, &h);
boxe = boxCreate(x - 25, y - 25, w + 51, h + 51);
pixt1 = pixClipRectangle(pixb, boxe, NULL);
pixt2 = pixClipRectangle(pixs, boxe, NULL);
boxDestroy(&boxe);
/* Deskew, looking at all possible orientations over 180 degrees */
pixt3 = pixRotateOrth(pixt1, 1); /* look for vertical bar lines */
pixt4 = pixClone(pixt1); /* look for horizontal bar lines */
pixFindSkewSweepAndSearchScore(pixt3, &angle1, &conf1, &score1,
1, 1, 0.0, 45.0, 2.5, 0.01);
pixFindSkewSweepAndSearchScore(pixt4, &angle2, &conf2, &score2,
1, 1, 0.0, 45.0, 2.5, 0.01);
/* Because we're using the boundary pixels of the barcodes,
* the peak can be sharper (and the confidence ratio higher)
* from the signal across the top and bottom of the barcode.
* However, the max score, which is the magnitude of the signal
* at the optimum skew angle, will be smaller, so we use the
* max score as the primary indicator of orientation. */
if (score1 >= score2) {
conf = conf1;
if (conf1 > 6.0 && L_ABS(angle1) > 0.1) {
angle = angle1;
pixt5 = pixRotate(pixt2, deg2rad * angle1, L_ROTATE_AREA_MAP,
L_BRING_IN_WHITE, 0, 0);
} else {
angle = 0.0;
pixt5 = pixClone(pixt2);
}
} else { /* score2 > score1 */
conf = conf2;
pixt6 = pixRotateOrth(pixt2, 1);
if (conf2 > 6.0 && L_ABS(angle2) > 0.1) {
angle = 90.0 + angle2;
pixt5 = pixRotate(pixt6, deg2rad * angle2, L_ROTATE_AREA_MAP,
L_BRING_IN_WHITE, 0, 0);
} else {
angle = 90.0;
pixt5 = pixClone(pixt6);
}
pixDestroy(&pixt6);
}
pixDestroy(&pixt3);
pixDestroy(&pixt4);
/* Extract barcode plus a margin around it */
boxa = pixLocateBarcodes(pixt5, threshold, 0, 0);
if ((n = boxaGetCount(boxa)) != 1) {
L_WARNING("barcode mask in %d components\n", procName, n);
boxat = boxaSort(boxa, L_SORT_BY_AREA, L_SORT_DECREASING, NULL);
} else {
boxat = boxaCopy(boxa, L_CLONE);
}
boxt = boxaGetBox(boxat, 0, L_CLONE);
boxGetGeometry(boxt, &x, &y, &w, &h);
boxe = boxCreate(x - margin, y - margin, w + 2 * margin,
h + 2 * margin);
pixd = pixClipRectangle(pixt5, boxe, NULL);
boxDestroy(&boxt);
boxDestroy(&boxe);
boxaDestroy(&boxa);
boxaDestroy(&boxat);
if (pangle) *pangle = angle;
if (pconf) *pconf = conf;
pixDestroy(&pixt1);
pixDestroy(&pixt2);
pixDestroy(&pixt5);
return pixd;
}
/*------------------------------------------------------------------------*
* Process to get line widths *
*------------------------------------------------------------------------*/
/*!
* \brief pixExtractBarcodeWidths1()
*
* \param[in] pixs input image; 8 bpp
* \param[in] thresh estimated pixel threshold for crossing
* white <--> black; typ. ~120
* \param[in] binfract histo binsize as a fraction of minsize; e.g., 0.25
* \param[out] pnaehist [optional] histogram of black widths; NULL ok
* \param[out] pnaohist [optional] histogram of white widths; NULL ok
* \param[in] debugflag use 1 to generate debug output
* \return nad numa of barcode widths in encoded integer units,
* or NULL on error
*
* <pre>
* Notes:
* (1) The widths are alternating black/white, starting with black
* and ending with black.
* (2) This method uses the widths of the bars directly, in terms
* of the (float) number of pixels between transitions.
* The histograms of these widths for black and white bars is
* generated and interpreted.
* </pre>
*/
NUMA *
pixExtractBarcodeWidths1(PIX *pixs,
l_float32 thresh,
l_float32 binfract,
NUMA **pnaehist,
NUMA **pnaohist,
l_int32 debugflag)
{
NUMA *nac, *nad;
PROCNAME("pixExtractBarcodeWidths1");
if (!pixs || pixGetDepth(pixs) != 8)
return (NUMA *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL);
/* Get the best estimate of the crossings, in pixel units */
nac = pixExtractBarcodeCrossings(pixs, thresh, debugflag);
/* Get the array of bar widths, starting with a black bar */
nad = numaQuantizeCrossingsByWidth(nac, binfract, pnaehist,
pnaohist, debugflag);
numaDestroy(&nac);
return nad;
}
/*!
* \brief pixExtractBarcodeWidths2()
*
* \param[in] pixs input image; 8 bpp
* \param[in] thresh estimated pixel threshold for crossing
* white <--> black; typ. ~120
* \param[out] pwidth [optional] best decoding window width, in pixels
* \param[out] pnac [optional] number of transitions in each window
* \param[in] debugflag use 1 to generate debug output
* \return nad numa of barcode widths in encoded integer units,
* or NULL on error
*
* <pre>
* Notes:
* (1) The widths are alternating black/white, starting with black
* and ending with black.
* (2) The optional best decoding window width is the width of the window
* that is used to make a decision about whether a transition occurs.
* It is approximately the average width in pixels of the narrowest
* white and black bars (i.e., those corresponding to unit width).
* (3) The optional return signal %nac is a sequence of 0s, 1s,
* and perhaps a few 2s, giving the number of crossings in each window.
* On the occasion where there is a '2', it is interpreted as
* as ending two runs: the previous one and another one that has length 1.
* </pre>
*/
NUMA *
pixExtractBarcodeWidths2(PIX *pixs,
l_float32 thresh,
l_float32 *pwidth,
NUMA **pnac,
l_int32 debugflag)
{
NUMA *nacp, *nad;
PROCNAME("pixExtractBarcodeWidths2");
if (!pixs || pixGetDepth(pixs) != 8)
return (NUMA *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL);
/* Get the best estimate of the crossings, in pixel units */
nacp = pixExtractBarcodeCrossings(pixs, thresh, debugflag);
/* Quantize the crossings to get actual windowed data */
nad = numaQuantizeCrossingsByWindow(nacp, 2.0, pwidth, NULL, pnac, debugflag);
numaDestroy(&nacp);
return nad;
}
/*!
* \brief pixExtractBarcodeCrossings()
*
* \param[in] pixs input image; 8 bpp
* \param[in] thresh estimated pixel threshold for crossing
* white <--> black; typ. ~120
* \param[in] debugflag use 1 to generate debug output
* \return numa of crossings, in pixel units, or NULL on error
*/
NUMA *
pixExtractBarcodeCrossings(PIX *pixs,
l_float32 thresh,
l_int32 debugflag)
{
l_int32 w;
l_float32 bestthresh;
NUMA *nas, *nax, *nay, *nad;
PROCNAME("pixExtractBarcodeCrossings");
if (!pixs || pixGetDepth(pixs) != 8)
return (NUMA *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL);
/* Scan pixels horizontally and average results */
nas = pixAverageRasterScans(pixs, 51);
/* Interpolate to get 4x the number of values */
w = pixGetWidth(pixs);
numaInterpolateEqxInterval(0.0, 1.0, nas, L_QUADRATIC_INTERP, 0.0,
(l_float32)(w - 1), 4 * w + 1, &nax, &nay);
if (debugflag) {
lept_mkdir("lept/barcode");
GPLOT *gplot = gplotCreate("/tmp/lept/barcode/signal", GPLOT_PNG,
"Pixel values", "dist in pixels", "value");
gplotAddPlot(gplot, nax, nay, GPLOT_LINES, "plot 1");
gplotMakeOutput(gplot);
gplotDestroy(&gplot);
}
/* Locate the crossings. Run multiple times with different
* thresholds, and choose a threshold in the center of the
* run of thresholds that all give the maximum number of crossings. */
numaSelectCrossingThreshold(nax, nay, thresh, &bestthresh);
/* Get the crossings with the best threshold. */
nad = numaCrossingsByThreshold(nax, nay, bestthresh);
numaDestroy(&nas);
numaDestroy(&nax);
numaDestroy(&nay);
return nad;
}
/*------------------------------------------------------------------------*
* Average adjacent rasters *
*------------------------------------------------------------------------*/
/*!
* \brief pixAverageRasterScans()
*
* \param[in] pixs input image; 8 bpp
* \param[in] nscans number of adjacent scans, about the center vertically
* \return numa of average pixel values across image, or NULL on error
*/
static NUMA *
pixAverageRasterScans(PIX *pixs,
l_int32 nscans)
{
l_int32 w, h, first, last, i, j, wpl, val;
l_uint32 *line, *data;
l_float32 *array;
NUMA *nad;
PROCNAME("pixAverageRasterScans");
if (!pixs || pixGetDepth(pixs) != 8)
return (NUMA *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL);
pixGetDimensions(pixs, &w, &h, NULL);
if (nscans <= h) {
first = 0;
last = h - 1;
nscans = h;
} else {
first = (h - nscans) / 2;
last = first + nscans - 1;
}
nad = numaCreate(w);
numaSetCount(nad, w);
array = numaGetFArray(nad, L_NOCOPY);
wpl = pixGetWpl(pixs);
data = pixGetData(pixs);
for (j = 0; j < w; j++) {
for (i = first; i <= last; i++) {
line = data + i * wpl;
val = GET_DATA_BYTE(line, j);
array[j] += val;
}
array[j] = array[j] / (l_float32)nscans;
}
return nad;
}
/*------------------------------------------------------------------------*
* Signal processing for barcode widths *
*------------------------------------------------------------------------*/
/*!
* \brief numaQuantizeCrossingsByWidth()
*
* \param[in] nas numa of crossing locations, in pixel units
* \param[in] binfract histo binsize as a fraction of minsize; e.g., 0.25
* \param[out] pnaehist [optional] histo of even (black) bar widths
* \param[out] pnaohist [optional] histo of odd (white) bar widths
* \param[in] debugflag 1 to generate plots of histograms of bar widths
* \return nad sequence of widths, in unit sizes, or NULL on error
*
* <pre>
* Notes:
* (1) This first computes the histogram of black and white bar widths,
* binned in appropriate units. There should be well-defined
* peaks, each corresponding to a specific width. The sequence
* of barcode widths (namely, the integers from the set {1,2,3,4})
* is returned.
* (2) The optional returned histograms are binned in width units
* that are inversely proportional to %binfract. For example,
* if %binfract = 0.25, there are 4.0 bins in the distance of
* the width of the narrowest bar.
* </pre>
*/
NUMA *
numaQuantizeCrossingsByWidth(NUMA *nas,
l_float32 binfract,
NUMA **pnaehist,
NUMA **pnaohist,
l_int32 debugflag)
{
l_int32 i, n, ned, nod, iw, width;
l_float32 val, minsize, maxsize, factor;
GPLOT *gplot;
NUMA *naedist, *naodist, *naehist, *naohist, *naecent, *naocent;
NUMA *naerange, *naorange, *naelut, *naolut, *nad;
PROCNAME("numaQuantizeCrossingsByWidth");
if (!nas)
return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
n = numaGetCount(nas);
if (n < 2)
return (NUMA *)ERROR_PTR("n < 2", procName, NULL);
if (binfract <= 0.0)
return (NUMA *)ERROR_PTR("binfract <= 0.0", procName, NULL);
/* Get even and odd crossing distances */
numaGetCrossingDistances(nas, &naedist, &naodist, &minsize, &maxsize);
/* Bin the spans in units of binfract * minsize. These
* units are convenient because they scale to make at least
* 1/binfract bins in the smallest span (width). We want this
* number to be large enough to clearly separate the
* widths, but small enough so that the histogram peaks
* have very few if any holes (zeroes) within them. */
naehist = numaMakeHistogramClipped(naedist, binfract * minsize,
(1.25 / binfract) * maxsize);
naohist = numaMakeHistogramClipped(naodist, binfract * minsize,
(1.25 / binfract) * maxsize);
if (debugflag) {
lept_mkdir("lept/barcode");
gplot = gplotCreate("/tmp/lept/barcode/histw", GPLOT_PNG,
"Raw width histogram", "Width", "Number");
gplotAddPlot(gplot, NULL, naehist, GPLOT_LINES, "plot black");
gplotAddPlot(gplot, NULL, naohist, GPLOT_LINES, "plot white");
gplotMakeOutput(gplot);
gplotDestroy(&gplot);
}
/* Compute the peak ranges, still in units of binfract * minsize. */
naerange = numaLocatePeakRanges(naehist, 1.0 / binfract,
1.0 / binfract, 0.0);
naorange = numaLocatePeakRanges(naohist, 1.0 / binfract,
1.0 / binfract, 0.0);
/* Find the centroid values of each peak */
naecent = numaGetPeakCentroids(naehist, naerange);
naocent = numaGetPeakCentroids(naohist, naorange);
/* Generate the lookup tables that map from the bar width, in
* units of (binfract * minsize), to the integerized barcode
* units (1, 2, 3, 4), which are the output integer widths
* between transitions. */
naelut = numaGetPeakWidthLUT(naerange, naecent);
naolut = numaGetPeakWidthLUT(naorange, naocent);
/* Get the widths. Because the LUT accepts our funny units,
* we first must convert the pixel widths to these units,
* which is what 'factor' does. */
nad = numaCreate(0);
ned = numaGetCount(naedist);
nod = numaGetCount(naodist);
if (nod != ned - 1)
L_WARNING("ned != nod + 1\n", procName);
factor = 1.0 / (binfract * minsize); /* for converting units */
for (i = 0; i < ned - 1; i++) {
numaGetFValue(naedist, i, &val);
width = (l_int32)(factor * val);
numaGetIValue(naelut, width, &iw);
numaAddNumber(nad, iw);
/* fprintf(stderr, "even: val = %7.3f, width = %d, iw = %d\n",
val, width, iw); */
numaGetFValue(naodist, i, &val);
width = (l_int32)(factor * val);
numaGetIValue(naolut, width, &iw);
numaAddNumber(nad, iw);
/* fprintf(stderr, "odd: val = %7.3f, width = %d, iw = %d\n",
val, width, iw); */
}
numaGetFValue(naedist, ned - 1, &val);
width = (l_int32)(factor * val);
numaGetIValue(naelut, width, &iw);
numaAddNumber(nad, iw);
if (debugflag) {
fprintf(stderr, " ---- Black bar widths (pixels) ------ \n");
numaWriteStream(stderr, naedist);
fprintf(stderr, " ---- Histogram of black bar widths ------ \n");
numaWriteStream(stderr, naehist);
fprintf(stderr, " ---- Peak ranges in black bar histogram bins --- \n");
numaWriteStream(stderr, naerange);
fprintf(stderr, " ---- Peak black bar centroid width values ------ \n");
numaWriteStream(stderr, naecent);
fprintf(stderr, " ---- Black bar lookup table ------ \n");
numaWriteStream(stderr, naelut);
fprintf(stderr, " ---- White bar widths (pixels) ------ \n");
numaWriteStream(stderr, naodist);
fprintf(stderr, " ---- Histogram of white bar widths ------ \n");
numaWriteStream(stderr, naohist);
fprintf(stderr, " ---- Peak ranges in white bar histogram bins --- \n");
numaWriteStream(stderr, naorange);
fprintf(stderr, " ---- Peak white bar centroid width values ------ \n");
numaWriteStream(stderr, naocent);
fprintf(stderr, " ---- White bar lookup table ------ \n");
numaWriteStream(stderr, naolut);
}
numaDestroy(&naedist);
numaDestroy(&naodist);
numaDestroy(&naerange);
numaDestroy(&naorange);
numaDestroy(&naecent);
numaDestroy(&naocent);
numaDestroy(&naelut);
numaDestroy(&naolut);
if (pnaehist)
*pnaehist = naehist;
else
numaDestroy(&naehist);
if (pnaohist)
*pnaohist = naohist;
else
numaDestroy(&naohist);
return nad;
}
/*!
* \brief numaGetCrossingDistances()
*
* \param[in] nas numa of crossing locations
* \param[out] pnaedist [optional] even distances between crossings
* \param[out] pnaodist [optional] odd distances between crossings
* \param[out] pmindist [optional] min distance between crossings
* \param[out] pmaxdist [optional] max distance between crossings
* \return 0 if OK, 1 on error
*/
static l_int32
numaGetCrossingDistances(NUMA *nas,
NUMA **pnaedist,
NUMA **pnaodist,
l_float32 *pmindist,
l_float32 *pmaxdist)
{
l_int32 i, n;
l_float32 val, newval, mindist, maxdist, dist;
NUMA *naedist, *naodist;
PROCNAME("numaGetCrossingDistances");
if (pnaedist) *pnaedist = NULL;
if (pnaodist) *pnaodist = NULL;
if (pmindist) *pmindist = 0.0;
if (pmaxdist) *pmaxdist = 0.0;
if (!nas)
return ERROR_INT("nas not defined", procName, 1);
if ((n = numaGetCount(nas)) < 2)
return ERROR_INT("n < 2", procName, 1);
/* Get numas of distances between crossings. Separate these
* into even (e.g., black) and odd (e.g., white) spans.
* For barcodes, the black spans are 0, 2, etc. These
* distances are in pixel units. */
naedist = numaCreate(n / 2 + 1);
naodist = numaCreate(n / 2);
numaGetFValue(nas, 0, &val);
for (i = 1; i < n; i++) {
numaGetFValue(nas, i, &newval);
if (i % 2)
numaAddNumber(naedist, newval - val);
else
numaAddNumber(naodist, newval - val);
val = newval;
}
/* The mindist and maxdist of the spans are in pixel units. */
numaGetMin(naedist, &mindist, NULL);
numaGetMin(naodist, &dist, NULL);
mindist = L_MIN(dist, mindist);
numaGetMax(naedist, &maxdist, NULL);
numaGetMax(naodist, &dist, NULL);
maxdist = L_MAX(dist, maxdist);
L_INFO("mindist = %7.3f, maxdist = %7.3f\n", procName, mindist, maxdist);
if (pnaedist)
*pnaedist = naedist;
else
numaDestroy(&naedist);
if (pnaodist)
*pnaodist = naodist;
else
numaDestroy(&naodist);
if (pmindist) *pmindist = mindist;
if (pmaxdist) *pmaxdist = maxdist;
return 0;
}
/*!
* \brief numaLocatePeakRanges()
*
* \param[in] nas numa of histogram of crossing widths
* \param[in] minfirst min location of center of first peak
* \param[in] minsep min separation between peak range centers
* \param[in] maxmin max allowed value for min histo value between peaks
* \return nad ranges for each peak found, in pairs, or NULL on error
*
* <pre>
* Notes:
* (1) Units of %minsep are the index into nas.
* This puts useful constraints on peak-finding.
* (2) If maxmin == 0.0, the value of nas[i] must go to 0.0 (or less)
* between peaks.
* (3) All calculations are done in units of the index into nas.
* The resulting ranges are therefore integers.
* (4) The output nad gives pairs of range values for successive peaks.
* Any location [i] for which maxmin = nas[i] = 0.0 will NOT be
* included in a peak range. This works fine for histograms where
* if nas[i] == 0.0, it means that there are no samples at [i].
* (5) For barcodes, when this is used on a histogram of barcode
* widths, use maxmin = 0.0. This requires that there is at
* least one histogram bin corresponding to a width value between
* adjacent peak ranges that is unpopulated, making the separation
* of the histogram peaks unambiguous.
* </pre>
*/
static NUMA *
numaLocatePeakRanges(NUMA *nas,
l_float32 minfirst,
l_float32 minsep,
l_float32 maxmin)
{
l_int32 i, n, inpeak, left;
l_float32 center, prevcenter, val;
NUMA *nad;
PROCNAME("numaLocatePeakRanges");
if (!nas)
return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
n = numaGetCount(nas);
nad = numaCreate(0);
inpeak = FALSE;
prevcenter = minfirst - minsep - 1.0;
for (i = 0; i < n; i++) {
numaGetFValue(nas, i, &val);
if (inpeak == FALSE && val > maxmin) {
inpeak = TRUE;
left = i;
} else if (inpeak == TRUE && val <= maxmin) { /* end peak */
center = (left + i - 1.0) / 2.0;
if (center - prevcenter >= minsep) { /* save new peak */
inpeak = FALSE;
numaAddNumber(nad, left);
numaAddNumber(nad, i - 1);
prevcenter = center;
} else { /* attach to previous peak; revise the right edge */
numaSetValue(nad, numaGetCount(nad) - 1, i - 1);
}
}
}
if (inpeak == TRUE) { /* save the last peak */
numaAddNumber(nad, left);
numaAddNumber(nad, n - 1);
}
return nad;
}
/*!
* \brief numaGetPeakCentroids()
*
* \param[in] nahist numa of histogram of crossing widths
* \param[in] narange numa of ranges of x-values for the peaks in %nahist
* \return nad centroids for each peak found; max of 4, corresponding
* to 4 different barcode line widths, or NULL on error
*/
static NUMA *
numaGetPeakCentroids(NUMA *nahist,
NUMA *narange)
{
l_int32 i, j, nr, low, high;
l_float32 cent, sum, val;
NUMA *nad;
PROCNAME("numaGetPeakCentroids");
if (!nahist)
return (NUMA *)ERROR_PTR("nahist not defined", procName, NULL);
if (!narange)
return (NUMA *)ERROR_PTR("narange not defined", procName, NULL);
nr = numaGetCount(narange) / 2;
nad = numaCreate(4);
for (i = 0; i < nr; i++) {
numaGetIValue(narange, 2 * i, &low);
numaGetIValue(narange, 2 * i + 1, &high);
cent = 0.0;
sum = 0.0;
for (j = low; j <= high; j++) {
numaGetFValue(nahist, j, &val);
cent += j * val;
sum += val;
}
numaAddNumber(nad, cent / sum);
}
return nad;
}
/*!
* \brief numaGetPeakWidthLUT()
*
* \param[in] narange numa of x-val ranges for the histogram width peaks
* \param[in] nacent numa of centroids of each peak -- up to 4
* \return nalut lookup table from the width of a bar to one of the four
* integerized barcode units, or NULL on error
*
* <pre>
* Notes:
* (1) This generates the lookup table that maps from a sequence of widths
* (in some units) to the integerized barcode units (1, 2, 3, 4),
* which are the output integer widths between transitions.
* (2) The smallest width can be lost in float roundoff. To avoid
* losing it, we expand the peak range of the smallest width.
* </pre>
*/
static NUMA *
numaGetPeakWidthLUT(NUMA *narange,
NUMA *nacent)
{
l_int32 i, j, nc, low, high, imax;
l_int32 assign[4];
l_float32 *warray;
l_float32 max, rat21, rat32, rat42;
NUMA *nalut;
PROCNAME("numaGetPeakWidthLUT");
if (!narange)
return (NUMA *)ERROR_PTR("narange not defined", procName, NULL);
if (!nacent)
return (NUMA *)ERROR_PTR("nacent not defined", procName, NULL);
nc = numaGetCount(nacent); /* half the size of narange */
if (nc < 1 || nc > 4)
return (NUMA *)ERROR_PTR("nc must be 1, 2, 3, or 4", procName, NULL);
/* Check the peak centroids for consistency with bar widths.
* The third peak can correspond to a width of either 3 or 4.
* Use ratios 3/2 and 4/2 instead of 3/1 and 4/1 because the
* former are more stable and closer to the expected ratio. */
if (nc > 1) {
warray = numaGetFArray(nacent, L_NOCOPY);
if (warray[0] == 0)
return (NUMA *)ERROR_PTR("first peak has width 0.0",
procName, NULL);
rat21 = warray[1] / warray[0];
if (rat21 < 1.5 || rat21 > 2.6)
L_WARNING("width ratio 2/1 = %f\n", procName, rat21);
if (nc > 2) {
rat32 = warray[2] / warray[1];
if (rat32 < 1.3 || rat32 > 2.25)
L_WARNING("width ratio 3/2 = %f\n", procName, rat32);
}
if (nc == 4) {
rat42 = warray[3] / warray[1];
if (rat42 < 1.7 || rat42 > 2.3)
L_WARNING("width ratio 4/2 = %f\n", procName, rat42);
}
}
/* Set width assignments.
* The only possible ambiguity is with nc = 3 */
for (i = 0; i < 4; i++)
assign[i] = i + 1;
if (nc == 3) {
if (rat32 > 1.75)
assign[2] = 4;
}
/* Put widths into the LUT */
numaGetMax(narange, &max, NULL);
imax = (l_int32)max;
nalut = numaCreate(imax + 1);
numaSetCount(nalut, imax + 1); /* fill the array with zeroes */
for (i = 0; i < nc; i++) {
numaGetIValue(narange, 2 * i, &low);
if (i == 0) low--; /* catch smallest width */
numaGetIValue(narange, 2 * i + 1, &high);
for (j = low; j <= high; j++)
numaSetValue(nalut, j, assign[i]);
}
return nalut;
}
/*!
* \brief numaQuantizeCrossingsByWindow()
*
* \param[in] nas numa of crossing locations
* \param[in] ratio of max window size over min window size in search;
* typ. 2.0
* \param[out] pwidth [optional] best window width
* \param[out] pfirstloc [optional] center of window for first xing
* \param[out] pnac [optional] array of window crossings (0, 1, 2)
* \param[in] debugflag 1 to generate various plots of intermediate results
* \return nad sequence of widths, in unit sizes, or NULL on error
*
* <pre>
* Notes:
* (1) The minimum size of the window is set by the minimum
* distance between zero crossings.
* (2) The optional return signal %nac is a sequence of 0s, 1s,
* and perhaps a few 2s, giving the number of crossings in each window.
* On the occasion where there is a '2', it is interpreted as
* ending two runs: the previous one and another one that has length 1.
* </pre>
*/
NUMA *
numaQuantizeCrossingsByWindow(NUMA *nas,
l_float32 ratio,
l_float32 *pwidth,
l_float32 *pfirstloc,
NUMA **pnac,
l_int32 debugflag)
{
l_int32 i, nw, started, count, trans;
l_float32 minsize, minwidth, minshift, xfirst;
NUMA *nac, *nad;
PROCNAME("numaQuantizeCrossingsByWindow");
if (!nas)
return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
if (numaGetCount(nas) < 2)
return (NUMA *)ERROR_PTR("nas size < 2", procName, NULL);
/* Get the minsize, which is needed for the search for
* the window width (ultimately found as 'minwidth') */
numaGetCrossingDistances(nas, NULL, NULL, &minsize, NULL);
/* Compute the width and shift increments; start at minsize
* and go up to ratio * minsize */
numaEvalBestWidthAndShift(nas, 100, 10, minsize, ratio * minsize,
&minwidth, &minshift, NULL);
/* Refine width and shift calculation */
numaEvalBestWidthAndShift(nas, 100, 10, 0.98 * minwidth, 1.02 * minwidth,
&minwidth, &minshift, NULL);
L_INFO("best width = %7.3f, best shift = %7.3f\n",
procName, minwidth, minshift);
/* Get the crossing array (0,1,2) for the best window width and shift */
numaEvalSyncError(nas, 0, 0, minwidth, minshift, NULL, &nac);
if (pwidth) *pwidth = minwidth;
if (pfirstloc) {
numaGetFValue(nas, 0, &xfirst);
*pfirstloc = xfirst + minshift;
}
/* Get the array of bar widths, starting with a black bar */
nad = numaCreate(0);
nw = numaGetCount(nac); /* number of window measurements */
started = FALSE;
count = 0; /* unnecessary init */
for (i = 0; i < nw; i++) {
numaGetIValue(nac, i, &trans);
if (trans > 2)
L_WARNING("trans = %d > 2 !!!\n", procName, trans);
if (started) {
if (trans > 1) { /* i.e., when trans == 2 */
numaAddNumber(nad, count);
trans--;
count = 1;
}
if (trans == 1) {
numaAddNumber(nad, count);
count = 1;
} else {
count++;
}
}
if (!started && trans) {
started = TRUE;
if (trans == 2) /* a whole bar in this window */
numaAddNumber(nad, 1);
count = 1;
}
}
if (pnac)
*pnac = nac;
else
numaDestroy(&nac);
return nad;
}
/*!
* \brief numaEvalBestWidthAndShift()
*
* \param[in] nas numa of crossing locations
* \param[in] nwidth number of widths to consider
* \param[in] nshift number of shifts to consider for each width
* \param[in] minwidth smallest width to consider
* \param[in] maxwidth largest width to consider
* \param[out] pbestwidth best size of window
* \param[out] pbestshift best shift for the window
* \param[out] pbestscore [optional] average squared error of dist
* of crossing signal from the center of the window
* \return 0 if OK, 1 on error
*
* <pre>
* Notes:
* (1) This does a linear sweep of widths, evaluating at %nshift
* shifts for each width, finding the (width, shift) pair that
* gives the minimum score.
* </pre>
*/
static l_int32
numaEvalBestWidthAndShift(NUMA *nas,
l_int32 nwidth,
l_int32 nshift,
l_float32 minwidth,
l_float32 maxwidth,
l_float32 *pbestwidth,
l_float32 *pbestshift,
l_float32 *pbestscore)
{
l_int32 i, j;
l_float32 delwidth, delshift, width, shift, score;
l_float32 bestwidth, bestshift, bestscore;
PROCNAME("numaEvalBestWidthAndShift");
if (!nas)
return ERROR_INT("nas not defined", procName, 1);
if (!pbestwidth || !pbestshift)
return ERROR_INT("&bestwidth and &bestshift not defined", procName, 1);
bestwidth = 0.0f;
bestshift = 0.0f;
bestscore = 1.0;
delwidth = (maxwidth - minwidth) / (nwidth - 1.0);
for (i = 0; i < nwidth; i++) {
width = minwidth + delwidth * i;
delshift = width / (l_float32)(nshift);
for (j = 0; j < nshift; j++) {
shift = -0.5 * (width - delshift) + j * delshift;
numaEvalSyncError(nas, 0, 0, width, shift, &score, NULL);
if (score < bestscore) {
bestscore = score;
bestwidth = width;
bestshift = shift;
#if DEBUG_FREQUENCY
fprintf(stderr, "width = %7.3f, shift = %7.3f, score = %7.3f\n",
width, shift, score);
#endif /* DEBUG_FREQUENCY */
}
}
}
*pbestwidth = bestwidth;
*pbestshift = bestshift;
if (pbestscore)
*pbestscore = bestscore;
return 0;
}
/*!
* \brief numaEvalSyncError()
*
* \param[in] nas numa of crossing locations
* \param[in] ifirst first crossing to use
* \param[in] ilast last crossing to use; use 0 for all crossings
* \param[in] width size of window
* \param[in] shift of center of window w/rt first crossing
* \param[out] pscore [optional] average squared error of dist
* of crossing signal from the center of the window
* \param[out] pnad [optional] numa of 1s and 0s for crossings
* \return 0 if OK, 1 on error
*
* <pre>
* Notes:
* (1) The score is computed only on the part of the signal from the
* %ifirst to %ilast crossings. Use 0 for both of these to
* use all the crossings. The score is normalized for
* the number of crossings and with half-width of the window.
* (2) The optional return %nad is a sequence of 0s and 1s, where a '1'
* indicates a crossing in the window.
* </pre>
*/
static l_int32
numaEvalSyncError(NUMA *nas,
l_int32 ifirst,
l_int32 ilast,
l_float32 width,
l_float32 shift,
l_float32 *pscore,
NUMA **pnad)
{
l_int32 i, n, nc, nw, ival;
l_int32 iw; /* cell in which transition occurs */
l_float32 score, xfirst, xlast, xleft, xc, xwc;
NUMA *nad;
PROCNAME("numaEvalSyncError");
if (!nas)
return ERROR_INT("nas not defined", procName, 1);
if ((n = numaGetCount(nas)) < 2)
return ERROR_INT("nas size < 2", procName, 1);
if (ifirst < 0) ifirst = 0;
if (ilast <= 0) ilast = n - 1;
if (ifirst >= ilast)
return ERROR_INT("ifirst not < ilast", procName, 1);
nc = ilast - ifirst + 1;
/* Set up an array corresponding to the (shifted) windows,
* and fill in the crossings. */
score = 0.0;
numaGetFValue(nas, ifirst, &xfirst);
numaGetFValue(nas, ilast, &xlast);
nw = (l_int32) ((xlast - xfirst + 2.0 * width) / width);
nad = numaCreate(nw);
numaSetCount(nad, nw); /* init to all 0.0 */
xleft = xfirst - width / 2.0 + shift; /* left edge of first window */
for (i = ifirst; i <= ilast; i++) {
numaGetFValue(nas, i, &xc);
iw = (l_int32)((xc - xleft) / width);
xwc = xleft + (iw + 0.5) * width; /* center of cell iw */
score += (xwc - xc) * (xwc - xc);
numaGetIValue(nad, iw, &ival);
numaSetValue(nad, iw, ival + 1);
}
if (pscore)
*pscore = 4.0 * score / (width * width * (l_float32)nc);
if (pnad)
*pnad = nad;
else
numaDestroy(&nad);
return 0;
}