/*====================================================================* - 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 *
 *
 *      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).
 * 
*/ #ifdef HAVE_CONFIG_H #include #endif /* HAVE_CONFIG_H */ #include #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 */ /* Minimum barcode image size */ static const l_int32 MIN_BC_WIDTH = 50; static const l_int32 MIN_BC_HEIGHT = 50; /* 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); pixa = pixExtractBarcodes(pixg, debugflag); pixDestroy(&pixg); if (!pixa) return (SARRAY *)ERROR_PTR("no barcode(s) found", procName, NULL); sad = pixReadBarcodes(pixa, format, method, psaw, debugflag); 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 *pix1, *pix2, *pix3; 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, &pix2, &pix1); n = boxaGetCount(boxa); L_INFO("%d possible barcode(s) found\n", procName, n); if (n == 0) { boxaDestroy(&boxa); pixDestroy(&pix2); pixDestroy(&pix1); return NULL; } if (debugflag) { boxaWriteStderr(boxa); pixDisplay(pix2, 100, 100); pixDisplay(pix1, 800, 100); } pixDestroy(&pix1); /* Deskew each barcode individually */ pixa = pixaCreate(n); for (i = 0; i < n; i++) { box = boxaGetBox(boxa, i, L_CLONE); pix3 = pixDeskewBarcode(pixs, pix2, box, 15, 20, &angle, &conf); if (!pix3) conf = 0.0; /* don't use */ L_INFO("angle = %6.2f, conf = %6.2f\n", procName, angle, conf); if (conf > 5.0) { pixaAddPix(pixa, pix3, L_INSERT); pixaAddBox(pixa, box, L_INSERT); } else { pixDestroy(&pix3); boxDestroy(&box); } } pixDestroy(&pix2); boxaDestroy(&boxa); #if DEBUG_DESKEW pix3 = pixaDisplayTiledInRows(pixa, 8, 1000, 1.0, 0, 30, 2); pixWrite("lept/pix3.png", pix3, IFF_PNG); pixDestroy(&pix3); #endif /* DEBUG_DESKEW */ 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 w, h, i, j, n, nbars, ival; NUMA *na; PIX *pix1; 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 */ pix1 = pixaGetPix(pixa, i, L_CLONE); pixGetDimensions(pix1, &w, &h, NULL); if (w < MIN_BC_WIDTH || h < MIN_BC_HEIGHT) { L_ERROR("pix is too small: w = %d, h = %d\n", procName, w, h); pixDestroy(&pix1); continue; } na = pixReadBarcodeWidths(pix1, method, debugflag); pixDestroy(&pix1); 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) lept_stderr("Window width for barcode: %7.3f\n", winwidth); numaWriteStderr(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 * *
 * 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.
 * 
*/ 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 * *
 * Notes:
 *     (1) The (optional) angle returned is the angle in degrees (cw positive)
 *         necessary to rotate the image so that it is deskewed.
 * 
*/ 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 *box1, *box2; BOXA *boxa1, *boxa2; PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pixd; PROCNAME("pixDeskewBarcode"); if (pangle) *pangle = 0.0; if (pconf) *pconf = 0.0; 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); box2 = boxCreate(x - 25, y - 25, w + 51, h + 51); pix1 = pixClipRectangle(pixb, box2, NULL); pix2 = pixClipRectangle(pixs, box2, NULL); boxDestroy(&box2); /* Deskew, looking at all possible orientations over 180 degrees */ pix3 = pixRotateOrth(pix1, 1); /* look for vertical bar lines */ pix4 = pixClone(pix1); /* look for horizontal bar lines */ pixFindSkewSweepAndSearchScore(pix3, &angle1, &conf1, &score1, 1, 1, 0.0, 45.0, 2.5, 0.01); pixFindSkewSweepAndSearchScore(pix4, &angle2, &conf2, &score2, 1, 1, 0.0, 45.0, 2.5, 0.01); pixDestroy(&pix1); pixDestroy(&pix3); pixDestroy(&pix4); /* 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; pix5 = pixRotate(pix2, deg2rad * angle1, L_ROTATE_AREA_MAP, L_BRING_IN_WHITE, 0, 0); } else { angle = 0.0; pix5 = pixClone(pix2); } } else { /* score2 > score1 */ conf = conf2; pix6 = pixRotateOrth(pix2, 1); if (conf2 > 6.0 && L_ABS(angle2) > 0.1) { angle = 90.0 + angle2; pix5 = pixRotate(pix6, deg2rad * angle2, L_ROTATE_AREA_MAP, L_BRING_IN_WHITE, 0, 0); } else { angle = 90.0; pix5 = pixClone(pix6); } pixDestroy(&pix6); } pixDestroy(&pix2); /* Extract barcode plus a margin around it */ boxa1 = pixLocateBarcodes(pix5, threshold, 0, 0); if ((n = boxaGetCount(boxa1)) != 1) { L_WARNING("barcode mask in %d components\n", procName, n); boxa2 = boxaSort(boxa1, L_SORT_BY_AREA, L_SORT_DECREASING, NULL); } else { boxa2 = boxaCopy(boxa1, L_CLONE); } box1 = boxaGetBox(boxa2, 0, L_CLONE); boxGetGeometry(box1, &x, &y, &w, &h); box2 = boxCreate(x - margin, y - margin, w + 2 * margin, h + 2 * margin); pixd = pixClipRectangle(pix5, box2, NULL); boxDestroy(&box1); boxDestroy(&box2); boxaDestroy(&boxa1); boxaDestroy(&boxa2); pixDestroy(&pix5); if (pangle) *pangle = angle; if (pconf) *pconf = conf; if (!pixd) L_ERROR("pixd not made\n", procName); 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 * *
 * 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.
 * 
*/ NUMA * pixExtractBarcodeWidths1(PIX *pixs, l_float32 thresh, l_float32 binfract, NUMA **pnaehist, NUMA **pnaohist, l_int32 debugflag) { NUMA *nac, *nad; PROCNAME("pixExtractBarcodeWidths1"); if (pnaehist) *pnaehist = NULL; if (pnaohist) *pnaehist = NULL; 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 */ if ((nac = pixExtractBarcodeCrossings(pixs, thresh, debugflag)) == NULL) return (NUMA *)ERROR_PTR("nac not made", procName, NULL); /* 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 * *
 * 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.
 * 
*/ NUMA * pixExtractBarcodeWidths2(PIX *pixs, l_float32 thresh, l_float32 *pwidth, NUMA **pnac, l_int32 debugflag) { l_int32 width; NUMA *nac, *nacp, *nad; PROCNAME("pixExtractBarcodeWidths2"); if (pwidth) *pwidth = 0; if (pnac) *pnac = NULL; 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 */ if ((nacp = pixExtractBarcodeCrossings(pixs, thresh, debugflag)) == NULL) return (NUMA *)ERROR_PTR("nacp not made", procName, NULL); /* 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 * *
 * Notes:
 *      (1) Require at least 10 crossings.
 * 
*/ NUMA * pixExtractBarcodeCrossings(PIX *pixs, l_float32 thresh, l_int32 debugflag) { l_int32 w; l_float32 bestthresh; GPLOT *gplot; 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 */ if ((nas = pixAverageRasterScans(pixs, 50)) == NULL) return (NUMA *)ERROR_PTR("nas not made", procName, NULL); /* 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 = 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); if (numaGetCount(nad) < 10) { L_ERROR("Only %d crossings; failure\n", procName, numaGetCount(nad)); numaDestroy(&nad); } 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 * *
 * 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.
 * 
*/ NUMA * numaQuantizeCrossingsByWidth(NUMA *nas, l_float32 binfract, NUMA **pnaehist, NUMA **pnaohist, l_int32 debugflag) { l_int32 i, n, ret, 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 (pnaehist) *pnaehist = NULL; if (pnaohist) *pnaohist = NULL; if (!nas) return (NUMA *)ERROR_PTR("nas not defined", procName, NULL); n = numaGetCount(nas); if (n < 10) return (NUMA *)ERROR_PTR("n < 10", procName, NULL); if (binfract <= 0.0) return (NUMA *)ERROR_PTR("binfract <= 0.0", procName, NULL); /* Get even and odd crossing distances */ ret = numaGetCrossingDistances(nas, &naedist, &naodist, &minsize, &maxsize); if (ret) return (NUMA *)ERROR_PTR("crossing data not found", procName, NULL); /* 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); /* lept_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); /* lept_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) { lept_stderr(" ---- Black bar widths (pixels) ------ \n"); numaWriteStderr(naedist); lept_stderr(" ---- Histogram of black bar widths ------ \n"); numaWriteStderr(naehist); lept_stderr(" ---- Peak ranges in black bar histogram bins --- \n"); numaWriteStderr(naerange); lept_stderr(" ---- Peak black bar centroid width values ------ \n"); numaWriteStderr(naecent); lept_stderr(" ---- Black bar lookup table ------ \n"); numaWriteStderr(naelut); lept_stderr(" ---- White bar widths (pixels) ------ \n"); numaWriteStderr(naodist); lept_stderr(" ---- Histogram of white bar widths ------ \n"); numaWriteStderr(naohist); lept_stderr(" ---- Peak ranges in white bar histogram bins --- \n"); numaWriteStderr(naorange); lept_stderr(" ---- Peak white bar centroid width values ------ \n"); numaWriteStderr(naocent); lept_stderr(" ---- White bar lookup table ------ \n"); numaWriteStderr(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 * *
 * 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.
 * 
*/ 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 * *
 * 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.
 * 
*/ 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 * *
 * 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.
 * 
*/ 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 * *
 * 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.
 * 
*/ 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 lept_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 * *
 * 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.
 * 
*/ 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; }