Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/thirdparty/leptonica/src/readbarcode.c @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mupdf-source/thirdparty/leptonica/src/readbarcode.c Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,1500 @@ +/*====================================================================* + - 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> + */ + +#ifdef HAVE_CONFIG_H +#include <config_auto.h> +#endif /* HAVE_CONFIG_H */ + +#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 */ + + /* 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 0 +#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; + + if (psaw) *psaw = NULL; + if (!pixs) + return (SARRAY *)ERROR_PTR("pixs not defined", __func__, NULL); + if (format != L_BF_ANY && !barcodeFormatIsSupported(format)) + return (SARRAY *)ERROR_PTR("unsupported format", __func__, NULL); + if (method != L_USE_WIDTHS && method != L_USE_WINDOWS) + return (SARRAY *)ERROR_PTR("invalid method", __func__, 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", __func__, 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; + + if (!pixs || pixGetDepth(pixs) != 8 || pixGetColormap(pixs)) + return (PIXA *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, 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", __func__, 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", __func__, 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("/tmp/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; + + if (psaw) *psaw = NULL; + if (!pixa) + return (SARRAY *)ERROR_PTR("pixa not defined", __func__, NULL); + if (format != L_BF_ANY && !barcodeFormatIsSupported(format)) + return (SARRAY *)ERROR_PTR("unsupported format", __func__, NULL); + if (method != L_USE_WIDTHS && method != L_USE_WINDOWS) + return (SARRAY *)ERROR_PTR("invalid method", __func__, 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", __func__, w, h); + pixDestroy(&pix1); + continue; + } + na = pixReadBarcodeWidths(pix1, method, debugflag); + pixDestroy(&pix1); + if (!na) { + ERROR_INT("valid barcode widths not returned", __func__, 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", __func__, 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", __func__, 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; + + if (!pixs) + return (NUMA *)ERROR_PTR("pixs not defined", __func__, NULL); + if (pixGetDepth(pixs) != 8) + return (NUMA *)ERROR_PTR("pixs not 8 bpp", __func__, NULL); + if (method != L_USE_WIDTHS && method != L_USE_WINDOWS) + return (NUMA *)ERROR_PTR("invalid method", __func__, 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", __func__, 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; + + if (!pixs) + return (BOXA *)ERROR_PTR("pixs not defined", __func__, 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; + + if (!pixs || pixGetDepth(pixs) != 1) + return (PIX *)ERROR_PTR("pixs not defined", __func__, 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 *box1, *box2; +BOXA *boxa1, *boxa2; +PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pixd; + + 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", __func__, NULL); + if (!pixb || pixGetDepth(pixb) != 1) + return (PIX *)ERROR_PTR("pixb undefined or not 1 bpp", __func__, NULL); + if (!box) + return (PIX *)ERROR_PTR("box not defined or 1 bpp", __func__, NULL); + + /* Clip out */ + deg2rad = 3.1415926535f / 180.f; + 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.0f, 45.0f, 2.5f, 0.01f); + pixFindSkewSweepAndSearchScore(pix4, &angle2, &conf2, &score2, + 1, 1, 0.0f, 45.0f, 2.5f, 0.01f); + 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", __func__, 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", __func__); + 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; + + if (pnaehist) *pnaehist = NULL; + if (pnaohist) *pnaohist = NULL; + if (!pixs || pixGetDepth(pixs) != 8) + return (NUMA *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, 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", __func__, 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 + * + * <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; + + if (pwidth) *pwidth = 0; + if (pnac) *pnac = NULL; + if (!pixs || pixGetDepth(pixs) != 8) + return (NUMA *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, 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", __func__, 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 + * + * <pre> + * Notes: + * (1) Require at least 10 crossings. + * </pre> + */ +NUMA * +pixExtractBarcodeCrossings(PIX *pixs, + l_float32 thresh, + l_int32 debugflag) +{ +l_int32 w; +l_float32 bestthresh; +GPLOT *gplot; +NUMA *nas, *nax, *nay, *nad; + + if (!pixs || pixGetDepth(pixs) != 8) + return (NUMA *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, NULL); + + /* Scan pixels horizontally and average results */ + if ((nas = pixAverageRasterScans(pixs, 50)) == NULL) + return (NUMA *)ERROR_PTR("nas not made", __func__, 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", __func__, 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; + + if (!pixs || pixGetDepth(pixs) != 8) + return (NUMA *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, 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, 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; + + if (pnaehist) *pnaehist = NULL; + if (pnaohist) *pnaohist = NULL; + if (!nas) + return (NUMA *)ERROR_PTR("nas not defined", __func__, NULL); + n = numaGetCount(nas); + if (n < 10) + return (NUMA *)ERROR_PTR("n < 10", __func__, NULL); + if (binfract <= 0.0) + return (NUMA *)ERROR_PTR("binfract <= 0.0", __func__, NULL); + + /* Get even and odd crossing distances, and determine the rank + * widths for rank 0.1 (minsize) and 0.9 (maxsize). */ + ret = numaGetCrossingDistances(nas, &naedist, &naodist, &minsize, &maxsize); + if (ret || minsize < 1.0 || maxsize / minsize > 8.0) { + L_ERROR("bad data, or minsize = %5.2f < 1.0 or max/min = %f > 4.0\n", + __func__, minsize, maxsize / minsize); + numaDestroy(&naedist); + numaDestroy(&naodist); + return 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", __func__); + 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; +NUMA *na1, *na2, *naedist, *naodist; + + 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", __func__, 1); + if ((n = numaGetCount(nas)) < 2) + return ERROR_INT("n < 2", __func__, 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 min and max rank distances of the spans are in pixel units. */ + na1 = numaCopy(naedist); + numaJoin(na1, naodist, 0, -1); /* use both bars and spaces */ + na2 = numaMakeHistogram(na1, 100, NULL, NULL); + numaHistogramGetValFromRank(na2, 0.1f, &mindist); + numaHistogramGetValFromRank(na2, 0.9f, &maxdist); + numaDestroy(&na1); + numaDestroy(&na2); + L_INFO("mindist = %7.3f, maxdist = %7.3f\n", __func__, 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; + + if (!nas) + return (NUMA *)ERROR_PTR("nas not defined", __func__, 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; + + if (!nahist) + return (NUMA *)ERROR_PTR("nahist not defined", __func__, NULL); + if (!narange) + return (NUMA *)ERROR_PTR("narange not defined", __func__, 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; + + if (!narange) + return (NUMA *)ERROR_PTR("narange not defined", __func__, NULL); + if (!nacent) + return (NUMA *)ERROR_PTR("nacent not defined", __func__, 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", __func__, 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", + __func__, NULL); + rat21 = warray[1] / warray[0]; + if (rat21 < 1.5 || rat21 > 2.6) + L_WARNING("width ratio 2/1 = %f\n", __func__, rat21); + if (nc > 2) { + rat32 = warray[2] / warray[1]; + if (rat32 < 1.3 || rat32 > 2.25) + L_WARNING("width ratio 3/2 = %f\n", __func__, rat32); + } + if (nc == 4) { + rat42 = warray[3] / warray[1]; + if (rat42 < 1.7 || rat42 > 2.3) + L_WARNING("width ratio 4/2 = %f\n", __func__, 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; + + if (!nas) + return (NUMA *)ERROR_PTR("nas not defined", __func__, NULL); + if (numaGetCount(nas) < 2) + return (NUMA *)ERROR_PTR("nas size < 2", __func__, 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", + __func__, 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", __func__, 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; + + if (!nas) + return ERROR_INT("nas not defined", __func__, 1); + if (!pbestwidth || !pbestshift) + return ERROR_INT("&bestwidth and &bestshift not defined", __func__, 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 + * + * <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; + + if (!nas) + return ERROR_INT("nas not defined", __func__, 1); + if ((n = numaGetCount(nas)) < 2) + return ERROR_INT("nas size < 2", __func__, 1); + if (ifirst < 0) ifirst = 0; + if (ilast <= 0) ilast = n - 1; + if (ifirst >= ilast) + return ERROR_INT("ifirst not < ilast", __func__, 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; +}
