Mercurial > hgrepos > Python2 > PyMuPDF
comparison 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 |
comparison
equal
deleted
inserted
replaced
| 1:1d09e1dec1d9 | 2:b50eed0cc0ef |
|---|---|
| 1 /*====================================================================* | |
| 2 - Copyright (C) 2001 Leptonica. All rights reserved. | |
| 3 - | |
| 4 - Redistribution and use in source and binary forms, with or without | |
| 5 - modification, are permitted provided that the following conditions | |
| 6 - are met: | |
| 7 - 1. Redistributions of source code must retain the above copyright | |
| 8 - notice, this list of conditions and the following disclaimer. | |
| 9 - 2. Redistributions in binary form must reproduce the above | |
| 10 - copyright notice, this list of conditions and the following | |
| 11 - disclaimer in the documentation and/or other materials | |
| 12 - provided with the distribution. | |
| 13 - | |
| 14 - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 15 - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 16 - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 17 - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY | |
| 18 - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |
| 19 - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
| 20 - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
| 21 - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY | |
| 22 - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
| 23 - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
| 24 - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 25 *====================================================================*/ | |
| 26 | |
| 27 | |
| 28 /*! | |
| 29 * \file readbarcode.c | |
| 30 * <pre> | |
| 31 * | |
| 32 * Basic operations to locate and identify the line widths | |
| 33 * in 1D barcodes. | |
| 34 * | |
| 35 * Top level | |
| 36 * SARRAY *pixProcessBarcodes() | |
| 37 * | |
| 38 * Next levels | |
| 39 * PIXA *pixExtractBarcodes() | |
| 40 * SARRAY *pixReadBarcodes() | |
| 41 * l_int32 pixReadBarcodeWidths() | |
| 42 * | |
| 43 * Location | |
| 44 * BOXA *pixLocateBarcodes() | |
| 45 * static PIX *pixGenerateBarcodeMask() | |
| 46 * | |
| 47 * Extraction and deskew | |
| 48 * PIXA *pixDeskewBarcodes() | |
| 49 * | |
| 50 * Process to get line widths | |
| 51 * NUMA *pixExtractBarcodeWidths1() | |
| 52 * NUMA *pixExtractBarcodeWidths2() | |
| 53 * NUMA *pixExtractBarcodeCrossings() | |
| 54 * | |
| 55 * Average adjacent rasters | |
| 56 * static NUMA *pixAverageRasterScans() | |
| 57 * | |
| 58 * Signal processing for barcode widths | |
| 59 * NUMA *numaQuantizeCrossingsByWidth() | |
| 60 * static l_int32 numaGetCrossingDistances() | |
| 61 * static NUMA *numaLocatePeakRanges() | |
| 62 * static NUMA *numaGetPeakCentroids() | |
| 63 * static NUMA *numaGetPeakWidthLUT() | |
| 64 * NUMA *numaQuantizeCrossingsByWindow() | |
| 65 * static l_int32 numaEvalBestWidthAndShift() | |
| 66 * static l_int32 numaEvalSyncError() | |
| 67 * | |
| 68 * | |
| 69 * NOTE CAREFULLY: This is "early beta" code. It has not been tuned | |
| 70 * to work robustly on a large database of barcode images. I'm putting | |
| 71 * it out so that people can play with it, find out how it breaks, and | |
| 72 * contribute decoders for other barcode formats. Both the functional | |
| 73 * interfaces and ABI will almost certainly change in the coming | |
| 74 * few months. The actual decoder, in bardecode.c, at present only | |
| 75 * works on the following codes: Code I2of5, Code 2of5, Code 39, Code 93 | |
| 76 * Codabar and UPCA. To add another barcode format, it is necessary | |
| 77 * to make changes in readbarcode.h and bardecode.c. | |
| 78 * The program prog/barcodetest shows how to run from the top level | |
| 79 * (image --> decoded data). | |
| 80 * </pre> | |
| 81 */ | |
| 82 | |
| 83 #ifdef HAVE_CONFIG_H | |
| 84 #include <config_auto.h> | |
| 85 #endif /* HAVE_CONFIG_H */ | |
| 86 | |
| 87 #include <string.h> | |
| 88 #include "allheaders.h" | |
| 89 #include "readbarcode.h" | |
| 90 | |
| 91 /* Parameters for pixGenerateBarcodeMask() */ | |
| 92 static const l_int32 MAX_SPACE_WIDTH = 19; /* was 15 */ | |
| 93 static const l_int32 MAX_NOISE_WIDTH = 50; /* smaller than barcode width */ | |
| 94 static const l_int32 MAX_NOISE_HEIGHT = 30; /* smaller than barcode height */ | |
| 95 | |
| 96 /* Minimum barcode image size */ | |
| 97 static const l_int32 MIN_BC_WIDTH = 50; | |
| 98 static const l_int32 MIN_BC_HEIGHT = 50; | |
| 99 | |
| 100 /* Static functions */ | |
| 101 static PIX *pixGenerateBarcodeMask(PIX *pixs, l_int32 maxspace, | |
| 102 l_int32 nwidth, l_int32 nheight); | |
| 103 static NUMA *pixAverageRasterScans(PIX *pixs, l_int32 nscans); | |
| 104 static l_int32 numaGetCrossingDistances(NUMA *nas, NUMA **pnaedist, | |
| 105 NUMA **pnaodist, l_float32 *pmindist, | |
| 106 l_float32 *pmaxdist); | |
| 107 static NUMA *numaLocatePeakRanges(NUMA *nas, l_float32 minfirst, | |
| 108 l_float32 minsep, l_float32 maxmin); | |
| 109 static NUMA *numaGetPeakCentroids(NUMA *nahist, NUMA *narange); | |
| 110 static NUMA *numaGetPeakWidthLUT(NUMA *narange, NUMA *nacent); | |
| 111 static l_int32 numaEvalBestWidthAndShift(NUMA *nas, l_int32 nwidth, | |
| 112 l_int32 nshift, l_float32 minwidth, | |
| 113 l_float32 maxwidth, | |
| 114 l_float32 *pbestwidth, | |
| 115 l_float32 *pbestshift, | |
| 116 l_float32 *pbestscore); | |
| 117 static l_int32 numaEvalSyncError(NUMA *nas, l_int32 ifirst, l_int32 ilast, | |
| 118 l_float32 width, l_float32 shift, | |
| 119 l_float32 *pscore, NUMA **pnad); | |
| 120 | |
| 121 | |
| 122 #ifndef NO_CONSOLE_IO | |
| 123 #define DEBUG_DESKEW 0 | |
| 124 #define DEBUG_WIDTHS 0 | |
| 125 #endif /* ~NO_CONSOLE_IO */ | |
| 126 | |
| 127 | |
| 128 /*------------------------------------------------------------------------* | |
| 129 * Top level * | |
| 130 *------------------------------------------------------------------------*/ | |
| 131 /*! | |
| 132 * \brief pixProcessBarcodes() | |
| 133 * | |
| 134 * \param[in] pixs any depth | |
| 135 * \param[in] format L_BF_ANY, L_BF_CODEI2OF5, L_BF_CODE93, ... | |
| 136 * \param[in] method L_USE_WIDTHS, L_USE_WINDOWS | |
| 137 * \param[out] psaw [optional] sarray of bar widths | |
| 138 * \param[in] debugflag use 1 to generate debug output | |
| 139 * \return sarray text of barcodes, or NULL if none found or on error | |
| 140 */ | |
| 141 SARRAY * | |
| 142 pixProcessBarcodes(PIX *pixs, | |
| 143 l_int32 format, | |
| 144 l_int32 method, | |
| 145 SARRAY **psaw, | |
| 146 l_int32 debugflag) | |
| 147 { | |
| 148 PIX *pixg; | |
| 149 PIXA *pixa; | |
| 150 SARRAY *sad; | |
| 151 | |
| 152 if (psaw) *psaw = NULL; | |
| 153 if (!pixs) | |
| 154 return (SARRAY *)ERROR_PTR("pixs not defined", __func__, NULL); | |
| 155 if (format != L_BF_ANY && !barcodeFormatIsSupported(format)) | |
| 156 return (SARRAY *)ERROR_PTR("unsupported format", __func__, NULL); | |
| 157 if (method != L_USE_WIDTHS && method != L_USE_WINDOWS) | |
| 158 return (SARRAY *)ERROR_PTR("invalid method", __func__, NULL); | |
| 159 | |
| 160 /* Get an 8 bpp image, no cmap */ | |
| 161 if (pixGetDepth(pixs) == 8 && !pixGetColormap(pixs)) | |
| 162 pixg = pixClone(pixs); | |
| 163 else | |
| 164 pixg = pixConvertTo8(pixs, 0); | |
| 165 | |
| 166 pixa = pixExtractBarcodes(pixg, debugflag); | |
| 167 pixDestroy(&pixg); | |
| 168 if (!pixa) | |
| 169 return (SARRAY *)ERROR_PTR("no barcode(s) found", __func__, NULL); | |
| 170 | |
| 171 sad = pixReadBarcodes(pixa, format, method, psaw, debugflag); | |
| 172 pixaDestroy(&pixa); | |
| 173 return sad; | |
| 174 } | |
| 175 | |
| 176 | |
| 177 /*! | |
| 178 * \brief pixExtractBarcodes() | |
| 179 * | |
| 180 * \param[in] pixs 8 bpp, no colormap | |
| 181 * \param[in] debugflag use 1 to generate debug output | |
| 182 * \return pixa deskewed and cropped barcodes, or NULL if none found | |
| 183 * or on error | |
| 184 */ | |
| 185 PIXA * | |
| 186 pixExtractBarcodes(PIX *pixs, | |
| 187 l_int32 debugflag) | |
| 188 { | |
| 189 l_int32 i, n; | |
| 190 l_float32 angle, conf; | |
| 191 BOX *box; | |
| 192 BOXA *boxa; | |
| 193 PIX *pix1, *pix2, *pix3; | |
| 194 PIXA *pixa; | |
| 195 | |
| 196 if (!pixs || pixGetDepth(pixs) != 8 || pixGetColormap(pixs)) | |
| 197 return (PIXA *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, NULL); | |
| 198 | |
| 199 /* Locate them; use small threshold for edges. */ | |
| 200 boxa = pixLocateBarcodes(pixs, 20, &pix2, &pix1); | |
| 201 n = boxaGetCount(boxa); | |
| 202 L_INFO("%d possible barcode(s) found\n", __func__, n); | |
| 203 if (n == 0) { | |
| 204 boxaDestroy(&boxa); | |
| 205 pixDestroy(&pix2); | |
| 206 pixDestroy(&pix1); | |
| 207 return NULL; | |
| 208 } | |
| 209 | |
| 210 if (debugflag) { | |
| 211 boxaWriteStderr(boxa); | |
| 212 pixDisplay(pix2, 100, 100); | |
| 213 pixDisplay(pix1, 800, 100); | |
| 214 } | |
| 215 pixDestroy(&pix1); | |
| 216 | |
| 217 /* Deskew each barcode individually */ | |
| 218 pixa = pixaCreate(n); | |
| 219 for (i = 0; i < n; i++) { | |
| 220 box = boxaGetBox(boxa, i, L_CLONE); | |
| 221 pix3 = pixDeskewBarcode(pixs, pix2, box, 15, 20, &angle, &conf); | |
| 222 if (!pix3) conf = 0.0; /* don't use */ | |
| 223 L_INFO("angle = %6.2f, conf = %6.2f\n", __func__, angle, conf); | |
| 224 if (conf > 5.0) { | |
| 225 pixaAddPix(pixa, pix3, L_INSERT); | |
| 226 pixaAddBox(pixa, box, L_INSERT); | |
| 227 } else { | |
| 228 pixDestroy(&pix3); | |
| 229 boxDestroy(&box); | |
| 230 } | |
| 231 } | |
| 232 pixDestroy(&pix2); | |
| 233 boxaDestroy(&boxa); | |
| 234 | |
| 235 #if DEBUG_DESKEW | |
| 236 pix3 = pixaDisplayTiledInRows(pixa, 8, 1000, 1.0, 0, 30, 2); | |
| 237 pixWrite("/tmp/lept/pix3.png", pix3, IFF_PNG); | |
| 238 pixDestroy(&pix3); | |
| 239 #endif /* DEBUG_DESKEW */ | |
| 240 | |
| 241 return pixa; | |
| 242 } | |
| 243 | |
| 244 | |
| 245 /*! | |
| 246 * \brief pixReadBarcodes() | |
| 247 * | |
| 248 * \param[in] pixa of 8 bpp deskewed and cropped barcodes | |
| 249 * \param[in] format L_BF_ANY, L_BF_CODEI2OF5, L_BF_CODE93, ... | |
| 250 * \param[in] method L_USE_WIDTHS, L_USE_WINDOWS; | |
| 251 * \param[out] psaw [optional] sarray of bar widths | |
| 252 * \param[in] debugflag use 1 to generate debug output | |
| 253 * \return sa sarray of widths, one string for each barcode found, | |
| 254 * or NULL on error | |
| 255 */ | |
| 256 SARRAY * | |
| 257 pixReadBarcodes(PIXA *pixa, | |
| 258 l_int32 format, | |
| 259 l_int32 method, | |
| 260 SARRAY **psaw, | |
| 261 l_int32 debugflag) | |
| 262 { | |
| 263 char *barstr, *data; | |
| 264 char emptystring[] = ""; | |
| 265 l_int32 w, h, i, j, n, nbars, ival; | |
| 266 NUMA *na; | |
| 267 PIX *pix1; | |
| 268 SARRAY *saw, *sad; | |
| 269 | |
| 270 if (psaw) *psaw = NULL; | |
| 271 if (!pixa) | |
| 272 return (SARRAY *)ERROR_PTR("pixa not defined", __func__, NULL); | |
| 273 if (format != L_BF_ANY && !barcodeFormatIsSupported(format)) | |
| 274 return (SARRAY *)ERROR_PTR("unsupported format", __func__, NULL); | |
| 275 if (method != L_USE_WIDTHS && method != L_USE_WINDOWS) | |
| 276 return (SARRAY *)ERROR_PTR("invalid method", __func__, NULL); | |
| 277 | |
| 278 n = pixaGetCount(pixa); | |
| 279 saw = sarrayCreate(n); | |
| 280 sad = sarrayCreate(n); | |
| 281 for (i = 0; i < n; i++) { | |
| 282 /* Extract the widths of the lines in each barcode */ | |
| 283 pix1 = pixaGetPix(pixa, i, L_CLONE); | |
| 284 pixGetDimensions(pix1, &w, &h, NULL); | |
| 285 if (w < MIN_BC_WIDTH || h < MIN_BC_HEIGHT) { | |
| 286 L_ERROR("pix is too small: w = %d, h = %d\n", __func__, w, h); | |
| 287 pixDestroy(&pix1); | |
| 288 continue; | |
| 289 } | |
| 290 na = pixReadBarcodeWidths(pix1, method, debugflag); | |
| 291 pixDestroy(&pix1); | |
| 292 if (!na) { | |
| 293 ERROR_INT("valid barcode widths not returned", __func__, 1); | |
| 294 continue; | |
| 295 } | |
| 296 | |
| 297 /* Save the widths as a string */ | |
| 298 nbars = numaGetCount(na); | |
| 299 barstr = (char *)LEPT_CALLOC(nbars + 1, sizeof(char)); | |
| 300 for (j = 0; j < nbars; j++) { | |
| 301 numaGetIValue(na, j, &ival); | |
| 302 barstr[j] = 0x30 + ival; | |
| 303 } | |
| 304 sarrayAddString(saw, barstr, L_INSERT); | |
| 305 numaDestroy(&na); | |
| 306 | |
| 307 /* Decode the width strings */ | |
| 308 data = barcodeDispatchDecoder(barstr, format, debugflag); | |
| 309 if (!data) { | |
| 310 ERROR_INT("barcode not decoded", __func__, 1); | |
| 311 sarrayAddString(sad, emptystring, L_COPY); | |
| 312 continue; | |
| 313 } | |
| 314 sarrayAddString(sad, data, L_INSERT); | |
| 315 } | |
| 316 | |
| 317 /* If nothing found, clean up */ | |
| 318 if (sarrayGetCount(saw) == 0) { | |
| 319 sarrayDestroy(&saw); | |
| 320 sarrayDestroy(&sad); | |
| 321 return (SARRAY *)ERROR_PTR("no valid barcode data", __func__, NULL); | |
| 322 } | |
| 323 | |
| 324 if (psaw) | |
| 325 *psaw = saw; | |
| 326 else | |
| 327 sarrayDestroy(&saw); | |
| 328 return sad; | |
| 329 } | |
| 330 | |
| 331 | |
| 332 /*! | |
| 333 * \brief pixReadBarcodeWidths() | |
| 334 * | |
| 335 * \param[in] pixs of 8 bpp deskewed and cropped barcode | |
| 336 * \param[in] method L_USE_WIDTHS, L_USE_WINDOWS; | |
| 337 * \param[in] debugflag use 1 to generate debug output | |
| 338 * \return na numa of widths (each in set {1,2,3,4}, or NULL on error | |
| 339 */ | |
| 340 NUMA * | |
| 341 pixReadBarcodeWidths(PIX *pixs, | |
| 342 l_int32 method, | |
| 343 l_int32 debugflag) | |
| 344 { | |
| 345 l_float32 winwidth; | |
| 346 NUMA *na; | |
| 347 | |
| 348 if (!pixs) | |
| 349 return (NUMA *)ERROR_PTR("pixs not defined", __func__, NULL); | |
| 350 if (pixGetDepth(pixs) != 8) | |
| 351 return (NUMA *)ERROR_PTR("pixs not 8 bpp", __func__, NULL); | |
| 352 if (method != L_USE_WIDTHS && method != L_USE_WINDOWS) | |
| 353 return (NUMA *)ERROR_PTR("invalid method", __func__, NULL); | |
| 354 | |
| 355 /* Extract the widths of the lines in each barcode */ | |
| 356 if (method == L_USE_WIDTHS) | |
| 357 na = pixExtractBarcodeWidths1(pixs, 120, 0.25, NULL, NULL, | |
| 358 debugflag); | |
| 359 else /* method == L_USE_WINDOWS */ | |
| 360 na = pixExtractBarcodeWidths2(pixs, 120, &winwidth, | |
| 361 NULL, debugflag); | |
| 362 #if DEBUG_WIDTHS | |
| 363 if (method == L_USE_WINDOWS) | |
| 364 lept_stderr("Window width for barcode: %7.3f\n", winwidth); | |
| 365 numaWriteStderr(na); | |
| 366 #endif /* DEBUG_WIDTHS */ | |
| 367 | |
| 368 if (!na) | |
| 369 return (NUMA *)ERROR_PTR("barcode widths invalid", __func__, NULL); | |
| 370 | |
| 371 return na; | |
| 372 } | |
| 373 | |
| 374 | |
| 375 /*------------------------------------------------------------------------* | |
| 376 * Locate barcode in image * | |
| 377 *------------------------------------------------------------------------*/ | |
| 378 /*! | |
| 379 * \brief pixLocateBarcodes() | |
| 380 * | |
| 381 * \param[in] pixs any depth | |
| 382 * \param[in] thresh for binarization of edge filter output; typ. 20 | |
| 383 * \param[out] ppixb [optional] binarized edge filtered input image | |
| 384 * \param[out] ppixm [optional] mask over barcodes | |
| 385 * \return boxa location of barcodes, or NULL if none found or on error | |
| 386 */ | |
| 387 BOXA * | |
| 388 pixLocateBarcodes(PIX *pixs, | |
| 389 l_int32 thresh, | |
| 390 PIX **ppixb, | |
| 391 PIX **ppixm) | |
| 392 { | |
| 393 BOXA *boxa; | |
| 394 PIX *pix8, *pixe, *pixb, *pixm; | |
| 395 | |
| 396 if (!pixs) | |
| 397 return (BOXA *)ERROR_PTR("pixs not defined", __func__, NULL); | |
| 398 | |
| 399 /* Get an 8 bpp image, no cmap */ | |
| 400 if (pixGetDepth(pixs) == 8 && !pixGetColormap(pixs)) | |
| 401 pix8 = pixClone(pixs); | |
| 402 else | |
| 403 pix8 = pixConvertTo8(pixs, 0); | |
| 404 | |
| 405 /* Get a 1 bpp image of the edges */ | |
| 406 pixe = pixSobelEdgeFilter(pix8, L_ALL_EDGES); | |
| 407 pixb = pixThresholdToBinary(pixe, thresh); | |
| 408 pixInvert(pixb, pixb); | |
| 409 pixDestroy(&pix8); | |
| 410 pixDestroy(&pixe); | |
| 411 | |
| 412 pixm = pixGenerateBarcodeMask(pixb, MAX_SPACE_WIDTH, MAX_NOISE_WIDTH, | |
| 413 MAX_NOISE_HEIGHT); | |
| 414 boxa = pixConnComp(pixm, NULL, 8); | |
| 415 | |
| 416 if (ppixb) | |
| 417 *ppixb = pixb; | |
| 418 else | |
| 419 pixDestroy(&pixb); | |
| 420 if (ppixm) | |
| 421 *ppixm = pixm; | |
| 422 else | |
| 423 pixDestroy(&pixm); | |
| 424 | |
| 425 return boxa; | |
| 426 } | |
| 427 | |
| 428 | |
| 429 /*! | |
| 430 * \brief pixGenerateBarcodeMask() | |
| 431 * | |
| 432 * \param[in] pixs 1 bpp | |
| 433 * \param[in] maxspace largest space in the barcode, in pixels | |
| 434 * \param[in] nwidth opening 'width' to remove noise | |
| 435 * \param[in] nheight opening 'height' to remove noise | |
| 436 * \return pixm mask over barcodes, or NULL if none found or on error | |
| 437 * | |
| 438 * <pre> | |
| 439 * Notes: | |
| 440 * (1) For noise removal, 'width' and 'height' are referred to the | |
| 441 * barcode orientation. | |
| 442 * (2) If there is skew, the mask will not cover the barcode corners. | |
| 443 * </pre> | |
| 444 */ | |
| 445 static PIX * | |
| 446 pixGenerateBarcodeMask(PIX *pixs, | |
| 447 l_int32 maxspace, | |
| 448 l_int32 nwidth, | |
| 449 l_int32 nheight) | |
| 450 { | |
| 451 PIX *pixt1, *pixt2, *pixd; | |
| 452 | |
| 453 if (!pixs || pixGetDepth(pixs) != 1) | |
| 454 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); | |
| 455 | |
| 456 /* Identify horizontal barcodes */ | |
| 457 pixt1 = pixCloseBrick(NULL, pixs, maxspace + 1, 1); | |
| 458 pixt2 = pixOpenBrick(NULL, pixs, maxspace + 1, 1); | |
| 459 pixXor(pixt2, pixt2, pixt1); | |
| 460 pixOpenBrick(pixt2, pixt2, nwidth, nheight); | |
| 461 pixDestroy(&pixt1); | |
| 462 | |
| 463 /* Identify vertical barcodes */ | |
| 464 pixt1 = pixCloseBrick(NULL, pixs, 1, maxspace + 1); | |
| 465 pixd = pixOpenBrick(NULL, pixs, 1, maxspace + 1); | |
| 466 pixXor(pixd, pixd, pixt1); | |
| 467 pixOpenBrick(pixd, pixd, nheight, nwidth); | |
| 468 pixDestroy(&pixt1); | |
| 469 | |
| 470 /* Combine to get all barcodes */ | |
| 471 pixOr(pixd, pixd, pixt2); | |
| 472 pixDestroy(&pixt2); | |
| 473 | |
| 474 return pixd; | |
| 475 } | |
| 476 | |
| 477 | |
| 478 /*------------------------------------------------------------------------* | |
| 479 * Extract and deskew barcode * | |
| 480 *------------------------------------------------------------------------*/ | |
| 481 /*! | |
| 482 * \brief pixDeskewBarcode() | |
| 483 * | |
| 484 * \param[in] pixs input image; 8 bpp | |
| 485 * \param[in] pixb binarized edge-filtered input image | |
| 486 * \param[in] box identified region containing barcode | |
| 487 * \param[in] margin of extra pixels around box to extract | |
| 488 * \param[in] threshold for binarization; ~20 | |
| 489 * \param[out] pangle [optional] in degrees, clockwise is positive | |
| 490 * \param[out] pconf [optional] confidence | |
| 491 * \return pixd deskewed barcode, or NULL on error | |
| 492 * | |
| 493 * <pre> | |
| 494 * Notes: | |
| 495 * (1) The (optional) angle returned is the angle in degrees (cw positive) | |
| 496 * necessary to rotate the image so that it is deskewed. | |
| 497 * </pre> | |
| 498 */ | |
| 499 PIX * | |
| 500 pixDeskewBarcode(PIX *pixs, | |
| 501 PIX *pixb, | |
| 502 BOX *box, | |
| 503 l_int32 margin, | |
| 504 l_int32 threshold, | |
| 505 l_float32 *pangle, | |
| 506 l_float32 *pconf) | |
| 507 { | |
| 508 l_int32 x, y, w, h, n; | |
| 509 l_float32 angle, angle1, angle2, conf, conf1, conf2, score1, score2, deg2rad; | |
| 510 BOX *box1, *box2; | |
| 511 BOXA *boxa1, *boxa2; | |
| 512 PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pixd; | |
| 513 | |
| 514 if (pangle) *pangle = 0.0; | |
| 515 if (pconf) *pconf = 0.0; | |
| 516 if (!pixs || pixGetDepth(pixs) != 8) | |
| 517 return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, NULL); | |
| 518 if (!pixb || pixGetDepth(pixb) != 1) | |
| 519 return (PIX *)ERROR_PTR("pixb undefined or not 1 bpp", __func__, NULL); | |
| 520 if (!box) | |
| 521 return (PIX *)ERROR_PTR("box not defined or 1 bpp", __func__, NULL); | |
| 522 | |
| 523 /* Clip out */ | |
| 524 deg2rad = 3.1415926535f / 180.f; | |
| 525 boxGetGeometry(box, &x, &y, &w, &h); | |
| 526 box2 = boxCreate(x - 25, y - 25, w + 51, h + 51); | |
| 527 pix1 = pixClipRectangle(pixb, box2, NULL); | |
| 528 pix2 = pixClipRectangle(pixs, box2, NULL); | |
| 529 boxDestroy(&box2); | |
| 530 | |
| 531 /* Deskew, looking at all possible orientations over 180 degrees */ | |
| 532 pix3 = pixRotateOrth(pix1, 1); /* look for vertical bar lines */ | |
| 533 pix4 = pixClone(pix1); /* look for horizontal bar lines */ | |
| 534 pixFindSkewSweepAndSearchScore(pix3, &angle1, &conf1, &score1, | |
| 535 1, 1, 0.0f, 45.0f, 2.5f, 0.01f); | |
| 536 pixFindSkewSweepAndSearchScore(pix4, &angle2, &conf2, &score2, | |
| 537 1, 1, 0.0f, 45.0f, 2.5f, 0.01f); | |
| 538 pixDestroy(&pix1); | |
| 539 pixDestroy(&pix3); | |
| 540 pixDestroy(&pix4); | |
| 541 | |
| 542 /* Because we're using the boundary pixels of the barcodes, | |
| 543 * the peak can be sharper (and the confidence ratio higher) | |
| 544 * from the signal across the top and bottom of the barcode. | |
| 545 * However, the max score, which is the magnitude of the signal | |
| 546 * at the optimum skew angle, will be smaller, so we use the | |
| 547 * max score as the primary indicator of orientation. */ | |
| 548 if (score1 >= score2) { | |
| 549 conf = conf1; | |
| 550 if (conf1 > 6.0 && L_ABS(angle1) > 0.1) { | |
| 551 angle = angle1; | |
| 552 pix5 = pixRotate(pix2, deg2rad * angle1, L_ROTATE_AREA_MAP, | |
| 553 L_BRING_IN_WHITE, 0, 0); | |
| 554 } else { | |
| 555 angle = 0.0; | |
| 556 pix5 = pixClone(pix2); | |
| 557 } | |
| 558 } else { /* score2 > score1 */ | |
| 559 conf = conf2; | |
| 560 pix6 = pixRotateOrth(pix2, 1); | |
| 561 if (conf2 > 6.0 && L_ABS(angle2) > 0.1) { | |
| 562 angle = 90.0 + angle2; | |
| 563 pix5 = pixRotate(pix6, deg2rad * angle2, L_ROTATE_AREA_MAP, | |
| 564 L_BRING_IN_WHITE, 0, 0); | |
| 565 } else { | |
| 566 angle = 90.0; | |
| 567 pix5 = pixClone(pix6); | |
| 568 } | |
| 569 pixDestroy(&pix6); | |
| 570 } | |
| 571 pixDestroy(&pix2); | |
| 572 | |
| 573 /* Extract barcode plus a margin around it */ | |
| 574 boxa1 = pixLocateBarcodes(pix5, threshold, 0, 0); | |
| 575 if ((n = boxaGetCount(boxa1)) != 1) { | |
| 576 L_WARNING("barcode mask in %d components\n", __func__, n); | |
| 577 boxa2 = boxaSort(boxa1, L_SORT_BY_AREA, L_SORT_DECREASING, NULL); | |
| 578 } else { | |
| 579 boxa2 = boxaCopy(boxa1, L_CLONE); | |
| 580 } | |
| 581 box1 = boxaGetBox(boxa2, 0, L_CLONE); | |
| 582 boxGetGeometry(box1, &x, &y, &w, &h); | |
| 583 box2 = boxCreate(x - margin, y - margin, w + 2 * margin, | |
| 584 h + 2 * margin); | |
| 585 pixd = pixClipRectangle(pix5, box2, NULL); | |
| 586 boxDestroy(&box1); | |
| 587 boxDestroy(&box2); | |
| 588 boxaDestroy(&boxa1); | |
| 589 boxaDestroy(&boxa2); | |
| 590 pixDestroy(&pix5); | |
| 591 | |
| 592 if (pangle) *pangle = angle; | |
| 593 if (pconf) *pconf = conf; | |
| 594 | |
| 595 if (!pixd) | |
| 596 L_ERROR("pixd not made\n", __func__); | |
| 597 return pixd; | |
| 598 } | |
| 599 | |
| 600 | |
| 601 /*------------------------------------------------------------------------* | |
| 602 * Process to get line widths * | |
| 603 *------------------------------------------------------------------------*/ | |
| 604 /*! | |
| 605 * \brief pixExtractBarcodeWidths1() | |
| 606 * | |
| 607 * \param[in] pixs input image; 8 bpp | |
| 608 * \param[in] thresh estimated pixel threshold for crossing | |
| 609 * white <--> black; typ. ~120 | |
| 610 * \param[in] binfract histo binsize as a fraction of minsize; e.g., 0.25 | |
| 611 * \param[out] pnaehist [optional] histogram of black widths; NULL ok | |
| 612 * \param[out] pnaohist [optional] histogram of white widths; NULL ok | |
| 613 * \param[in] debugflag use 1 to generate debug output | |
| 614 * \return nad numa of barcode widths in encoded integer units, | |
| 615 * or NULL on error | |
| 616 * | |
| 617 * <pre> | |
| 618 * Notes: | |
| 619 * (1) The widths are alternating black/white, starting with black | |
| 620 * and ending with black. | |
| 621 * (2) This method uses the widths of the bars directly, in terms | |
| 622 * of the (float) number of pixels between transitions. | |
| 623 * The histograms of these widths for black and white bars is | |
| 624 * generated and interpreted. | |
| 625 * </pre> | |
| 626 */ | |
| 627 NUMA * | |
| 628 pixExtractBarcodeWidths1(PIX *pixs, | |
| 629 l_float32 thresh, | |
| 630 l_float32 binfract, | |
| 631 NUMA **pnaehist, | |
| 632 NUMA **pnaohist, | |
| 633 l_int32 debugflag) | |
| 634 { | |
| 635 NUMA *nac, *nad; | |
| 636 | |
| 637 if (pnaehist) *pnaehist = NULL; | |
| 638 if (pnaohist) *pnaohist = NULL; | |
| 639 if (!pixs || pixGetDepth(pixs) != 8) | |
| 640 return (NUMA *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, NULL); | |
| 641 | |
| 642 /* Get the best estimate of the crossings, in pixel units */ | |
| 643 if ((nac = pixExtractBarcodeCrossings(pixs, thresh, debugflag)) == NULL) | |
| 644 return (NUMA *)ERROR_PTR("nac not made", __func__, NULL); | |
| 645 | |
| 646 /* Get the array of bar widths, starting with a black bar */ | |
| 647 nad = numaQuantizeCrossingsByWidth(nac, binfract, pnaehist, | |
| 648 pnaohist, debugflag); | |
| 649 | |
| 650 numaDestroy(&nac); | |
| 651 return nad; | |
| 652 } | |
| 653 | |
| 654 | |
| 655 /*! | |
| 656 * \brief pixExtractBarcodeWidths2() | |
| 657 * | |
| 658 * \param[in] pixs input image; 8 bpp | |
| 659 * \param[in] thresh estimated pixel threshold for crossing | |
| 660 * white <--> black; typ. ~120 | |
| 661 * \param[out] pwidth [optional] best decoding window width, in pixels | |
| 662 * \param[out] pnac [optional] number of transitions in each window | |
| 663 * \param[in] debugflag use 1 to generate debug output | |
| 664 * \return nad numa of barcode widths in encoded integer units, | |
| 665 * or NULL on error | |
| 666 * | |
| 667 * <pre> | |
| 668 * Notes: | |
| 669 * (1) The widths are alternating black/white, starting with black | |
| 670 * and ending with black. | |
| 671 * (2) The optional best decoding window width is the width of the window | |
| 672 * that is used to make a decision about whether a transition occurs. | |
| 673 * It is approximately the average width in pixels of the narrowest | |
| 674 * white and black bars (i.e., those corresponding to unit width). | |
| 675 * (3) The optional return signal %nac is a sequence of 0s, 1s, | |
| 676 * and perhaps a few 2s, giving the number of crossings in each window. | |
| 677 * On the occasion where there is a '2', it is interpreted as | |
| 678 * as ending two runs: the previous one and another one that has length 1. | |
| 679 * </pre> | |
| 680 */ | |
| 681 NUMA * | |
| 682 pixExtractBarcodeWidths2(PIX *pixs, | |
| 683 l_float32 thresh, | |
| 684 l_float32 *pwidth, | |
| 685 NUMA **pnac, | |
| 686 l_int32 debugflag) | |
| 687 { | |
| 688 NUMA *nacp, *nad; | |
| 689 | |
| 690 if (pwidth) *pwidth = 0; | |
| 691 if (pnac) *pnac = NULL; | |
| 692 if (!pixs || pixGetDepth(pixs) != 8) | |
| 693 return (NUMA *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, NULL); | |
| 694 | |
| 695 /* Get the best estimate of the crossings, in pixel units */ | |
| 696 if ((nacp = pixExtractBarcodeCrossings(pixs, thresh, debugflag)) == NULL) | |
| 697 return (NUMA *)ERROR_PTR("nacp not made", __func__, NULL); | |
| 698 | |
| 699 /* Quantize the crossings to get actual windowed data */ | |
| 700 nad = numaQuantizeCrossingsByWindow(nacp, 2.0, pwidth, NULL, | |
| 701 pnac, debugflag); | |
| 702 numaDestroy(&nacp); | |
| 703 return nad; | |
| 704 } | |
| 705 | |
| 706 | |
| 707 /*! | |
| 708 * \brief pixExtractBarcodeCrossings() | |
| 709 * | |
| 710 * \param[in] pixs input image; 8 bpp | |
| 711 * \param[in] thresh estimated pixel threshold for crossing | |
| 712 * white <--> black; typ. ~120 | |
| 713 * \param[in] debugflag use 1 to generate debug output | |
| 714 * \return numa of crossings, in pixel units, or NULL on error | |
| 715 * | |
| 716 * <pre> | |
| 717 * Notes: | |
| 718 * (1) Require at least 10 crossings. | |
| 719 * </pre> | |
| 720 */ | |
| 721 NUMA * | |
| 722 pixExtractBarcodeCrossings(PIX *pixs, | |
| 723 l_float32 thresh, | |
| 724 l_int32 debugflag) | |
| 725 { | |
| 726 l_int32 w; | |
| 727 l_float32 bestthresh; | |
| 728 GPLOT *gplot; | |
| 729 NUMA *nas, *nax, *nay, *nad; | |
| 730 | |
| 731 if (!pixs || pixGetDepth(pixs) != 8) | |
| 732 return (NUMA *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, NULL); | |
| 733 | |
| 734 /* Scan pixels horizontally and average results */ | |
| 735 if ((nas = pixAverageRasterScans(pixs, 50)) == NULL) | |
| 736 return (NUMA *)ERROR_PTR("nas not made", __func__, NULL); | |
| 737 | |
| 738 /* Interpolate to get 4x the number of values */ | |
| 739 w = pixGetWidth(pixs); | |
| 740 numaInterpolateEqxInterval(0.0, 1.0, nas, L_QUADRATIC_INTERP, 0.0, | |
| 741 (l_float32)(w - 1), 4 * w + 1, &nax, &nay); | |
| 742 | |
| 743 if (debugflag) { | |
| 744 lept_mkdir("lept/barcode"); | |
| 745 gplot = gplotCreate("/tmp/lept/barcode/signal", GPLOT_PNG, | |
| 746 "Pixel values", "dist in pixels", "value"); | |
| 747 gplotAddPlot(gplot, nax, nay, GPLOT_LINES, "plot 1"); | |
| 748 gplotMakeOutput(gplot); | |
| 749 gplotDestroy(&gplot); | |
| 750 } | |
| 751 | |
| 752 /* Locate the crossings. Run multiple times with different | |
| 753 * thresholds, and choose a threshold in the center of the | |
| 754 * run of thresholds that all give the maximum number of crossings. */ | |
| 755 numaSelectCrossingThreshold(nax, nay, thresh, &bestthresh); | |
| 756 | |
| 757 /* Get the crossings with the best threshold. */ | |
| 758 nad = numaCrossingsByThreshold(nax, nay, bestthresh); | |
| 759 numaDestroy(&nas); | |
| 760 numaDestroy(&nax); | |
| 761 numaDestroy(&nay); | |
| 762 | |
| 763 if (numaGetCount(nad) < 10) { | |
| 764 L_ERROR("Only %d crossings; failure\n", __func__, numaGetCount(nad)); | |
| 765 numaDestroy(&nad); | |
| 766 } | |
| 767 return nad; | |
| 768 } | |
| 769 | |
| 770 | |
| 771 /*------------------------------------------------------------------------* | |
| 772 * Average adjacent rasters * | |
| 773 *------------------------------------------------------------------------*/ | |
| 774 /*! | |
| 775 * \brief pixAverageRasterScans() | |
| 776 * | |
| 777 * \param[in] pixs input image; 8 bpp | |
| 778 * \param[in] nscans number of adjacent scans, about the center vertically | |
| 779 * \return numa of average pixel values across image, or NULL on error | |
| 780 */ | |
| 781 static NUMA * | |
| 782 pixAverageRasterScans(PIX *pixs, | |
| 783 l_int32 nscans) | |
| 784 { | |
| 785 l_int32 w, h, first, last, i, j, wpl, val; | |
| 786 l_uint32 *line, *data; | |
| 787 l_float32 *array; | |
| 788 NUMA *nad; | |
| 789 | |
| 790 if (!pixs || pixGetDepth(pixs) != 8) | |
| 791 return (NUMA *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, NULL); | |
| 792 | |
| 793 pixGetDimensions(pixs, &w, &h, NULL); | |
| 794 if (nscans > h) { | |
| 795 first = 0; | |
| 796 last = h - 1; | |
| 797 nscans = h; | |
| 798 } else { | |
| 799 first = (h - nscans) / 2; | |
| 800 last = first + nscans - 1; | |
| 801 } | |
| 802 | |
| 803 nad = numaCreate(w); | |
| 804 numaSetCount(nad, w); | |
| 805 array = numaGetFArray(nad, L_NOCOPY); | |
| 806 wpl = pixGetWpl(pixs); | |
| 807 data = pixGetData(pixs); | |
| 808 for (j = 0; j < w; j++) { | |
| 809 for (i = first; i <= last; i++) { | |
| 810 line = data + i * wpl; | |
| 811 val = GET_DATA_BYTE(line, j); | |
| 812 array[j] += val; | |
| 813 } | |
| 814 array[j] = array[j] / (l_float32)nscans; | |
| 815 } | |
| 816 | |
| 817 return nad; | |
| 818 } | |
| 819 | |
| 820 | |
| 821 /*------------------------------------------------------------------------* | |
| 822 * Signal processing for barcode widths * | |
| 823 *------------------------------------------------------------------------*/ | |
| 824 /*! | |
| 825 * \brief numaQuantizeCrossingsByWidth() | |
| 826 * | |
| 827 * \param[in] nas numa of crossing locations, in pixel units | |
| 828 * \param[in] binfract histo binsize as a fraction of minsize; e.g., 0.25 | |
| 829 * \param[out] pnaehist [optional] histo of even (black) bar widths | |
| 830 * \param[out] pnaohist [optional] histo of odd (white) bar widths | |
| 831 * \param[in] debugflag 1 to generate plots of histograms of bar widths | |
| 832 * \return nad sequence of widths, in unit sizes, or NULL on error | |
| 833 * | |
| 834 * <pre> | |
| 835 * Notes: | |
| 836 * (1) This first computes the histogram of black and white bar widths, | |
| 837 * binned in appropriate units. There should be well-defined | |
| 838 * peaks, each corresponding to a specific width. The sequence | |
| 839 * of barcode widths (namely, the integers from the set {1,2,3,4}) | |
| 840 * is returned. | |
| 841 * (2) The optional returned histograms are binned in width units | |
| 842 * that are inversely proportional to %binfract. For example, | |
| 843 * if %binfract = 0.25, there are 4.0 bins in the distance of | |
| 844 * the width of the narrowest bar. | |
| 845 * </pre> | |
| 846 */ | |
| 847 NUMA * | |
| 848 numaQuantizeCrossingsByWidth(NUMA *nas, | |
| 849 l_float32 binfract, | |
| 850 NUMA **pnaehist, | |
| 851 NUMA **pnaohist, | |
| 852 l_int32 debugflag) | |
| 853 { | |
| 854 l_int32 i, n, ret, ned, nod, iw, width; | |
| 855 l_float32 val, minsize, maxsize, factor; | |
| 856 GPLOT *gplot; | |
| 857 NUMA *naedist, *naodist, *naehist, *naohist, *naecent, *naocent; | |
| 858 NUMA *naerange, *naorange, *naelut, *naolut, *nad; | |
| 859 | |
| 860 if (pnaehist) *pnaehist = NULL; | |
| 861 if (pnaohist) *pnaohist = NULL; | |
| 862 if (!nas) | |
| 863 return (NUMA *)ERROR_PTR("nas not defined", __func__, NULL); | |
| 864 n = numaGetCount(nas); | |
| 865 if (n < 10) | |
| 866 return (NUMA *)ERROR_PTR("n < 10", __func__, NULL); | |
| 867 if (binfract <= 0.0) | |
| 868 return (NUMA *)ERROR_PTR("binfract <= 0.0", __func__, NULL); | |
| 869 | |
| 870 /* Get even and odd crossing distances, and determine the rank | |
| 871 * widths for rank 0.1 (minsize) and 0.9 (maxsize). */ | |
| 872 ret = numaGetCrossingDistances(nas, &naedist, &naodist, &minsize, &maxsize); | |
| 873 if (ret || minsize < 1.0 || maxsize / minsize > 8.0) { | |
| 874 L_ERROR("bad data, or minsize = %5.2f < 1.0 or max/min = %f > 4.0\n", | |
| 875 __func__, minsize, maxsize / minsize); | |
| 876 numaDestroy(&naedist); | |
| 877 numaDestroy(&naodist); | |
| 878 return NULL; | |
| 879 } | |
| 880 | |
| 881 /* Bin the spans in units of binfract * minsize. These | |
| 882 * units are convenient because they scale to make at least | |
| 883 * 1/binfract bins in the smallest span (width). We want this | |
| 884 * number to be large enough to clearly separate the | |
| 885 * widths, but small enough so that the histogram peaks | |
| 886 * have very few if any holes (zeroes) within them. */ | |
| 887 naehist = numaMakeHistogramClipped(naedist, binfract * minsize, | |
| 888 (1.25 / binfract) * maxsize); | |
| 889 naohist = numaMakeHistogramClipped(naodist, binfract * minsize, | |
| 890 (1.25 / binfract) * maxsize); | |
| 891 | |
| 892 if (debugflag) { | |
| 893 lept_mkdir("lept/barcode"); | |
| 894 gplot = gplotCreate("/tmp/lept/barcode/histw", GPLOT_PNG, | |
| 895 "Raw width histogram", "Width", "Number"); | |
| 896 gplotAddPlot(gplot, NULL, naehist, GPLOT_LINES, "plot black"); | |
| 897 gplotAddPlot(gplot, NULL, naohist, GPLOT_LINES, "plot white"); | |
| 898 gplotMakeOutput(gplot); | |
| 899 gplotDestroy(&gplot); | |
| 900 } | |
| 901 | |
| 902 /* Compute the peak ranges, still in units of binfract * minsize. */ | |
| 903 naerange = numaLocatePeakRanges(naehist, 1.0 / binfract, | |
| 904 1.0 / binfract, 0.0); | |
| 905 naorange = numaLocatePeakRanges(naohist, 1.0 / binfract, | |
| 906 1.0 / binfract, 0.0); | |
| 907 | |
| 908 /* Find the centroid values of each peak */ | |
| 909 naecent = numaGetPeakCentroids(naehist, naerange); | |
| 910 naocent = numaGetPeakCentroids(naohist, naorange); | |
| 911 | |
| 912 /* Generate the lookup tables that map from the bar width, in | |
| 913 * units of (binfract * minsize), to the integerized barcode | |
| 914 * units (1, 2, 3, 4), which are the output integer widths | |
| 915 * between transitions. */ | |
| 916 naelut = numaGetPeakWidthLUT(naerange, naecent); | |
| 917 naolut = numaGetPeakWidthLUT(naorange, naocent); | |
| 918 | |
| 919 /* Get the widths. Because the LUT accepts our funny units, | |
| 920 * we first must convert the pixel widths to these units, | |
| 921 * which is what 'factor' does. */ | |
| 922 nad = numaCreate(0); | |
| 923 ned = numaGetCount(naedist); | |
| 924 nod = numaGetCount(naodist); | |
| 925 if (nod != ned - 1) | |
| 926 L_WARNING("ned != nod + 1\n", __func__); | |
| 927 factor = 1.0 / (binfract * minsize); /* for converting units */ | |
| 928 for (i = 0; i < ned - 1; i++) { | |
| 929 numaGetFValue(naedist, i, &val); | |
| 930 width = (l_int32)(factor * val); | |
| 931 numaGetIValue(naelut, width, &iw); | |
| 932 numaAddNumber(nad, iw); | |
| 933 /* lept_stderr("even: val = %7.3f, width = %d, iw = %d\n", | |
| 934 val, width, iw); */ | |
| 935 numaGetFValue(naodist, i, &val); | |
| 936 width = (l_int32)(factor * val); | |
| 937 numaGetIValue(naolut, width, &iw); | |
| 938 numaAddNumber(nad, iw); | |
| 939 /* lept_stderr("odd: val = %7.3f, width = %d, iw = %d\n", | |
| 940 val, width, iw); */ | |
| 941 } | |
| 942 numaGetFValue(naedist, ned - 1, &val); | |
| 943 width = (l_int32)(factor * val); | |
| 944 numaGetIValue(naelut, width, &iw); | |
| 945 numaAddNumber(nad, iw); | |
| 946 | |
| 947 if (debugflag) { | |
| 948 lept_stderr(" ---- Black bar widths (pixels) ------ \n"); | |
| 949 numaWriteStderr(naedist); | |
| 950 lept_stderr(" ---- Histogram of black bar widths ------ \n"); | |
| 951 numaWriteStderr(naehist); | |
| 952 lept_stderr(" ---- Peak ranges in black bar histogram bins --- \n"); | |
| 953 numaWriteStderr(naerange); | |
| 954 lept_stderr(" ---- Peak black bar centroid width values ------ \n"); | |
| 955 numaWriteStderr(naecent); | |
| 956 lept_stderr(" ---- Black bar lookup table ------ \n"); | |
| 957 numaWriteStderr(naelut); | |
| 958 lept_stderr(" ---- White bar widths (pixels) ------ \n"); | |
| 959 numaWriteStderr(naodist); | |
| 960 lept_stderr(" ---- Histogram of white bar widths ------ \n"); | |
| 961 numaWriteStderr(naohist); | |
| 962 lept_stderr(" ---- Peak ranges in white bar histogram bins --- \n"); | |
| 963 numaWriteStderr(naorange); | |
| 964 lept_stderr(" ---- Peak white bar centroid width values ------ \n"); | |
| 965 numaWriteStderr(naocent); | |
| 966 lept_stderr(" ---- White bar lookup table ------ \n"); | |
| 967 numaWriteStderr(naolut); | |
| 968 } | |
| 969 | |
| 970 numaDestroy(&naedist); | |
| 971 numaDestroy(&naodist); | |
| 972 numaDestroy(&naerange); | |
| 973 numaDestroy(&naorange); | |
| 974 numaDestroy(&naecent); | |
| 975 numaDestroy(&naocent); | |
| 976 numaDestroy(&naelut); | |
| 977 numaDestroy(&naolut); | |
| 978 if (pnaehist) | |
| 979 *pnaehist = naehist; | |
| 980 else | |
| 981 numaDestroy(&naehist); | |
| 982 if (pnaohist) | |
| 983 *pnaohist = naohist; | |
| 984 else | |
| 985 numaDestroy(&naohist); | |
| 986 return nad; | |
| 987 } | |
| 988 | |
| 989 | |
| 990 /*! | |
| 991 * \brief numaGetCrossingDistances() | |
| 992 * | |
| 993 * \param[in] nas numa of crossing locations | |
| 994 * \param[out] pnaedist [optional] even distances between crossings | |
| 995 * \param[out] pnaodist [optional] odd distances between crossings | |
| 996 * \param[out] pmindist [optional] min distance between crossings | |
| 997 * \param[out] pmaxdist [optional] max distance between crossings | |
| 998 * \return 0 if OK, 1 on error | |
| 999 */ | |
| 1000 static l_int32 | |
| 1001 numaGetCrossingDistances(NUMA *nas, | |
| 1002 NUMA **pnaedist, | |
| 1003 NUMA **pnaodist, | |
| 1004 l_float32 *pmindist, | |
| 1005 l_float32 *pmaxdist) | |
| 1006 { | |
| 1007 l_int32 i, n; | |
| 1008 l_float32 val, newval, mindist, maxdist; | |
| 1009 NUMA *na1, *na2, *naedist, *naodist; | |
| 1010 | |
| 1011 if (pnaedist) *pnaedist = NULL; | |
| 1012 if (pnaodist) *pnaodist = NULL; | |
| 1013 if (pmindist) *pmindist = 0.0; | |
| 1014 if (pmaxdist) *pmaxdist = 0.0; | |
| 1015 if (!nas) | |
| 1016 return ERROR_INT("nas not defined", __func__, 1); | |
| 1017 if ((n = numaGetCount(nas)) < 2) | |
| 1018 return ERROR_INT("n < 2", __func__, 1); | |
| 1019 | |
| 1020 /* Get numas of distances between crossings. Separate these | |
| 1021 * into even (e.g., black) and odd (e.g., white) spans. | |
| 1022 * For barcodes, the black spans are 0, 2, etc. These | |
| 1023 * distances are in pixel units. */ | |
| 1024 naedist = numaCreate(n / 2 + 1); | |
| 1025 naodist = numaCreate(n / 2); | |
| 1026 numaGetFValue(nas, 0, &val); | |
| 1027 for (i = 1; i < n; i++) { | |
| 1028 numaGetFValue(nas, i, &newval); | |
| 1029 if (i % 2) | |
| 1030 numaAddNumber(naedist, newval - val); | |
| 1031 else | |
| 1032 numaAddNumber(naodist, newval - val); | |
| 1033 val = newval; | |
| 1034 } | |
| 1035 | |
| 1036 /* The min and max rank distances of the spans are in pixel units. */ | |
| 1037 na1 = numaCopy(naedist); | |
| 1038 numaJoin(na1, naodist, 0, -1); /* use both bars and spaces */ | |
| 1039 na2 = numaMakeHistogram(na1, 100, NULL, NULL); | |
| 1040 numaHistogramGetValFromRank(na2, 0.1f, &mindist); | |
| 1041 numaHistogramGetValFromRank(na2, 0.9f, &maxdist); | |
| 1042 numaDestroy(&na1); | |
| 1043 numaDestroy(&na2); | |
| 1044 L_INFO("mindist = %7.3f, maxdist = %7.3f\n", __func__, mindist, maxdist); | |
| 1045 | |
| 1046 if (pnaedist) | |
| 1047 *pnaedist = naedist; | |
| 1048 else | |
| 1049 numaDestroy(&naedist); | |
| 1050 if (pnaodist) | |
| 1051 *pnaodist = naodist; | |
| 1052 else | |
| 1053 numaDestroy(&naodist); | |
| 1054 if (pmindist) *pmindist = mindist; | |
| 1055 if (pmaxdist) *pmaxdist = maxdist; | |
| 1056 return 0; | |
| 1057 } | |
| 1058 | |
| 1059 | |
| 1060 /*! | |
| 1061 * \brief numaLocatePeakRanges() | |
| 1062 * | |
| 1063 * \param[in] nas numa of histogram of crossing widths | |
| 1064 * \param[in] minfirst min location of center of first peak | |
| 1065 * \param[in] minsep min separation between peak range centers | |
| 1066 * \param[in] maxmin max allowed value for min histo value between peaks | |
| 1067 * \return nad ranges for each peak found, in pairs, or NULL on error | |
| 1068 * | |
| 1069 * <pre> | |
| 1070 * Notes: | |
| 1071 * (1) Units of %minsep are the index into nas. | |
| 1072 * This puts useful constraints on peak-finding. | |
| 1073 * (2) If maxmin == 0.0, the value of nas[i] must go to 0.0 (or less) | |
| 1074 * between peaks. | |
| 1075 * (3) All calculations are done in units of the index into nas. | |
| 1076 * The resulting ranges are therefore integers. | |
| 1077 * (4) The output nad gives pairs of range values for successive peaks. | |
| 1078 * Any location [i] for which maxmin = nas[i] = 0.0 will NOT be | |
| 1079 * included in a peak range. This works fine for histograms where | |
| 1080 * if nas[i] == 0.0, it means that there are no samples at [i]. | |
| 1081 * (5) For barcodes, when this is used on a histogram of barcode | |
| 1082 * widths, use maxmin = 0.0. This requires that there is at | |
| 1083 * least one histogram bin corresponding to a width value between | |
| 1084 * adjacent peak ranges that is unpopulated, making the separation | |
| 1085 * of the histogram peaks unambiguous. | |
| 1086 * </pre> | |
| 1087 */ | |
| 1088 static NUMA * | |
| 1089 numaLocatePeakRanges(NUMA *nas, | |
| 1090 l_float32 minfirst, | |
| 1091 l_float32 minsep, | |
| 1092 l_float32 maxmin) | |
| 1093 { | |
| 1094 l_int32 i, n, inpeak, left; | |
| 1095 l_float32 center, prevcenter, val; | |
| 1096 NUMA *nad; | |
| 1097 | |
| 1098 if (!nas) | |
| 1099 return (NUMA *)ERROR_PTR("nas not defined", __func__, NULL); | |
| 1100 n = numaGetCount(nas); | |
| 1101 nad = numaCreate(0); | |
| 1102 | |
| 1103 inpeak = FALSE; | |
| 1104 prevcenter = minfirst - minsep - 1.0; | |
| 1105 for (i = 0; i < n; i++) { | |
| 1106 numaGetFValue(nas, i, &val); | |
| 1107 if (inpeak == FALSE && val > maxmin) { | |
| 1108 inpeak = TRUE; | |
| 1109 left = i; | |
| 1110 } else if (inpeak == TRUE && val <= maxmin) { /* end peak */ | |
| 1111 center = (left + i - 1.0) / 2.0; | |
| 1112 if (center - prevcenter >= minsep) { /* save new peak */ | |
| 1113 inpeak = FALSE; | |
| 1114 numaAddNumber(nad, left); | |
| 1115 numaAddNumber(nad, i - 1); | |
| 1116 prevcenter = center; | |
| 1117 } else { /* attach to previous peak; revise the right edge */ | |
| 1118 numaSetValue(nad, numaGetCount(nad) - 1, i - 1); | |
| 1119 } | |
| 1120 } | |
| 1121 } | |
| 1122 if (inpeak == TRUE) { /* save the last peak */ | |
| 1123 numaAddNumber(nad, left); | |
| 1124 numaAddNumber(nad, n - 1); | |
| 1125 } | |
| 1126 | |
| 1127 return nad; | |
| 1128 } | |
| 1129 | |
| 1130 | |
| 1131 /*! | |
| 1132 * \brief numaGetPeakCentroids() | |
| 1133 * | |
| 1134 * \param[in] nahist numa of histogram of crossing widths | |
| 1135 * \param[in] narange numa of ranges of x-values for the peaks in %nahist | |
| 1136 * \return nad centroids for each peak found; max of 4, corresponding | |
| 1137 * to 4 different barcode line widths, or NULL on error | |
| 1138 */ | |
| 1139 static NUMA * | |
| 1140 numaGetPeakCentroids(NUMA *nahist, | |
| 1141 NUMA *narange) | |
| 1142 { | |
| 1143 l_int32 i, j, nr, low, high; | |
| 1144 l_float32 cent, sum, val; | |
| 1145 NUMA *nad; | |
| 1146 | |
| 1147 if (!nahist) | |
| 1148 return (NUMA *)ERROR_PTR("nahist not defined", __func__, NULL); | |
| 1149 if (!narange) | |
| 1150 return (NUMA *)ERROR_PTR("narange not defined", __func__, NULL); | |
| 1151 nr = numaGetCount(narange) / 2; | |
| 1152 | |
| 1153 nad = numaCreate(4); | |
| 1154 for (i = 0; i < nr; i++) { | |
| 1155 numaGetIValue(narange, 2 * i, &low); | |
| 1156 numaGetIValue(narange, 2 * i + 1, &high); | |
| 1157 cent = 0.0; | |
| 1158 sum = 0.0; | |
| 1159 for (j = low; j <= high; j++) { | |
| 1160 numaGetFValue(nahist, j, &val); | |
| 1161 cent += j * val; | |
| 1162 sum += val; | |
| 1163 } | |
| 1164 numaAddNumber(nad, cent / sum); | |
| 1165 } | |
| 1166 | |
| 1167 return nad; | |
| 1168 } | |
| 1169 | |
| 1170 | |
| 1171 /*! | |
| 1172 * \brief numaGetPeakWidthLUT() | |
| 1173 * | |
| 1174 * \param[in] narange numa of x-val ranges for the histogram width peaks | |
| 1175 * \param[in] nacent numa of centroids of each peak -- up to 4 | |
| 1176 * \return nalut lookup table from the width of a bar to one of the four | |
| 1177 * integerized barcode units, or NULL on error | |
| 1178 * | |
| 1179 * <pre> | |
| 1180 * Notes: | |
| 1181 * (1) This generates the lookup table that maps from a sequence of widths | |
| 1182 * (in some units) to the integerized barcode units (1, 2, 3, 4), | |
| 1183 * which are the output integer widths between transitions. | |
| 1184 * (2) The smallest width can be lost in float roundoff. To avoid | |
| 1185 * losing it, we expand the peak range of the smallest width. | |
| 1186 * </pre> | |
| 1187 */ | |
| 1188 static NUMA * | |
| 1189 numaGetPeakWidthLUT(NUMA *narange, | |
| 1190 NUMA *nacent) | |
| 1191 { | |
| 1192 l_int32 i, j, nc, low, high, imax; | |
| 1193 l_int32 assign[4]; | |
| 1194 l_float32 *warray; | |
| 1195 l_float32 max, rat21, rat32, rat42; | |
| 1196 NUMA *nalut; | |
| 1197 | |
| 1198 if (!narange) | |
| 1199 return (NUMA *)ERROR_PTR("narange not defined", __func__, NULL); | |
| 1200 if (!nacent) | |
| 1201 return (NUMA *)ERROR_PTR("nacent not defined", __func__, NULL); | |
| 1202 nc = numaGetCount(nacent); /* half the size of narange */ | |
| 1203 if (nc < 1 || nc > 4) | |
| 1204 return (NUMA *)ERROR_PTR("nc must be 1, 2, 3, or 4", __func__, NULL); | |
| 1205 | |
| 1206 /* Check the peak centroids for consistency with bar widths. | |
| 1207 * The third peak can correspond to a width of either 3 or 4. | |
| 1208 * Use ratios 3/2 and 4/2 instead of 3/1 and 4/1 because the | |
| 1209 * former are more stable and closer to the expected ratio. */ | |
| 1210 if (nc > 1) { | |
| 1211 warray = numaGetFArray(nacent, L_NOCOPY); | |
| 1212 if (warray[0] == 0) | |
| 1213 return (NUMA *)ERROR_PTR("first peak has width 0.0", | |
| 1214 __func__, NULL); | |
| 1215 rat21 = warray[1] / warray[0]; | |
| 1216 if (rat21 < 1.5 || rat21 > 2.6) | |
| 1217 L_WARNING("width ratio 2/1 = %f\n", __func__, rat21); | |
| 1218 if (nc > 2) { | |
| 1219 rat32 = warray[2] / warray[1]; | |
| 1220 if (rat32 < 1.3 || rat32 > 2.25) | |
| 1221 L_WARNING("width ratio 3/2 = %f\n", __func__, rat32); | |
| 1222 } | |
| 1223 if (nc == 4) { | |
| 1224 rat42 = warray[3] / warray[1]; | |
| 1225 if (rat42 < 1.7 || rat42 > 2.3) | |
| 1226 L_WARNING("width ratio 4/2 = %f\n", __func__, rat42); | |
| 1227 } | |
| 1228 } | |
| 1229 | |
| 1230 /* Set width assignments. | |
| 1231 * The only possible ambiguity is with nc = 3 */ | |
| 1232 for (i = 0; i < 4; i++) | |
| 1233 assign[i] = i + 1; | |
| 1234 if (nc == 3) { | |
| 1235 if (rat32 > 1.75) | |
| 1236 assign[2] = 4; | |
| 1237 } | |
| 1238 | |
| 1239 /* Put widths into the LUT */ | |
| 1240 numaGetMax(narange, &max, NULL); | |
| 1241 imax = (l_int32)max; | |
| 1242 nalut = numaCreate(imax + 1); | |
| 1243 numaSetCount(nalut, imax + 1); /* fill the array with zeroes */ | |
| 1244 for (i = 0; i < nc; i++) { | |
| 1245 numaGetIValue(narange, 2 * i, &low); | |
| 1246 if (i == 0) low--; /* catch smallest width */ | |
| 1247 numaGetIValue(narange, 2 * i + 1, &high); | |
| 1248 for (j = low; j <= high; j++) | |
| 1249 numaSetValue(nalut, j, assign[i]); | |
| 1250 } | |
| 1251 | |
| 1252 return nalut; | |
| 1253 } | |
| 1254 | |
| 1255 | |
| 1256 /*! | |
| 1257 * \brief numaQuantizeCrossingsByWindow() | |
| 1258 * | |
| 1259 * \param[in] nas numa of crossing locations | |
| 1260 * \param[in] ratio of max window size over min window size in search; | |
| 1261 * typ. 2.0 | |
| 1262 * \param[out] pwidth [optional] best window width | |
| 1263 * \param[out] pfirstloc [optional] center of window for first xing | |
| 1264 * \param[out] pnac [optional] array of window crossings (0, 1, 2) | |
| 1265 * \param[in] debugflag 1 to generate various plots of intermediate results | |
| 1266 * \return nad sequence of widths, in unit sizes, or NULL on error | |
| 1267 * | |
| 1268 * <pre> | |
| 1269 * Notes: | |
| 1270 * (1) The minimum size of the window is set by the minimum | |
| 1271 * distance between zero crossings. | |
| 1272 * (2) The optional return signal %nac is a sequence of 0s, 1s, | |
| 1273 * and perhaps a few 2s, giving the number of crossings in each window. | |
| 1274 * On the occasion where there is a '2', it is interpreted as | |
| 1275 * ending two runs: the previous one and another one that has length 1. | |
| 1276 * </pre> | |
| 1277 */ | |
| 1278 NUMA * | |
| 1279 numaQuantizeCrossingsByWindow(NUMA *nas, | |
| 1280 l_float32 ratio, | |
| 1281 l_float32 *pwidth, | |
| 1282 l_float32 *pfirstloc, | |
| 1283 NUMA **pnac, | |
| 1284 l_int32 debugflag) | |
| 1285 { | |
| 1286 l_int32 i, nw, started, count, trans; | |
| 1287 l_float32 minsize, minwidth, minshift, xfirst; | |
| 1288 NUMA *nac, *nad; | |
| 1289 | |
| 1290 if (!nas) | |
| 1291 return (NUMA *)ERROR_PTR("nas not defined", __func__, NULL); | |
| 1292 if (numaGetCount(nas) < 2) | |
| 1293 return (NUMA *)ERROR_PTR("nas size < 2", __func__, NULL); | |
| 1294 | |
| 1295 /* Get the minsize, which is needed for the search for | |
| 1296 * the window width (ultimately found as 'minwidth') */ | |
| 1297 numaGetCrossingDistances(nas, NULL, NULL, &minsize, NULL); | |
| 1298 | |
| 1299 /* Compute the width and shift increments; start at minsize | |
| 1300 * and go up to ratio * minsize */ | |
| 1301 numaEvalBestWidthAndShift(nas, 100, 10, minsize, ratio * minsize, | |
| 1302 &minwidth, &minshift, NULL); | |
| 1303 | |
| 1304 /* Refine width and shift calculation */ | |
| 1305 numaEvalBestWidthAndShift(nas, 100, 10, 0.98 * minwidth, 1.02 * minwidth, | |
| 1306 &minwidth, &minshift, NULL); | |
| 1307 | |
| 1308 L_INFO("best width = %7.3f, best shift = %7.3f\n", | |
| 1309 __func__, minwidth, minshift); | |
| 1310 | |
| 1311 /* Get the crossing array (0,1,2) for the best window width and shift */ | |
| 1312 numaEvalSyncError(nas, 0, 0, minwidth, minshift, NULL, &nac); | |
| 1313 if (pwidth) *pwidth = minwidth; | |
| 1314 if (pfirstloc) { | |
| 1315 numaGetFValue(nas, 0, &xfirst); | |
| 1316 *pfirstloc = xfirst + minshift; | |
| 1317 } | |
| 1318 | |
| 1319 /* Get the array of bar widths, starting with a black bar */ | |
| 1320 nad = numaCreate(0); | |
| 1321 nw = numaGetCount(nac); /* number of window measurements */ | |
| 1322 started = FALSE; | |
| 1323 count = 0; /* unnecessary init */ | |
| 1324 for (i = 0; i < nw; i++) { | |
| 1325 numaGetIValue(nac, i, &trans); | |
| 1326 if (trans > 2) | |
| 1327 L_WARNING("trans = %d > 2 !!!\n", __func__, trans); | |
| 1328 if (started) { | |
| 1329 if (trans > 1) { /* i.e., when trans == 2 */ | |
| 1330 numaAddNumber(nad, count); | |
| 1331 trans--; | |
| 1332 count = 1; | |
| 1333 } | |
| 1334 if (trans == 1) { | |
| 1335 numaAddNumber(nad, count); | |
| 1336 count = 1; | |
| 1337 } else { | |
| 1338 count++; | |
| 1339 } | |
| 1340 } | |
| 1341 if (!started && trans) { | |
| 1342 started = TRUE; | |
| 1343 if (trans == 2) /* a whole bar in this window */ | |
| 1344 numaAddNumber(nad, 1); | |
| 1345 count = 1; | |
| 1346 } | |
| 1347 } | |
| 1348 | |
| 1349 if (pnac) | |
| 1350 *pnac = nac; | |
| 1351 else | |
| 1352 numaDestroy(&nac); | |
| 1353 return nad; | |
| 1354 } | |
| 1355 | |
| 1356 | |
| 1357 /*! | |
| 1358 * \brief numaEvalBestWidthAndShift() | |
| 1359 * | |
| 1360 * \param[in] nas numa of crossing locations | |
| 1361 * \param[in] nwidth number of widths to consider | |
| 1362 * \param[in] nshift number of shifts to consider for each width | |
| 1363 * \param[in] minwidth smallest width to consider | |
| 1364 * \param[in] maxwidth largest width to consider | |
| 1365 * \param[out] pbestwidth best size of window | |
| 1366 * \param[out] pbestshift best shift for the window | |
| 1367 * \param[out] pbestscore [optional] average squared error of dist | |
| 1368 * of crossing signal from the center of the window | |
| 1369 * \return 0 if OK, 1 on error | |
| 1370 * | |
| 1371 * <pre> | |
| 1372 * Notes: | |
| 1373 * (1) This does a linear sweep of widths, evaluating at %nshift | |
| 1374 * shifts for each width, finding the (width, shift) pair that | |
| 1375 * gives the minimum score. | |
| 1376 * </pre> | |
| 1377 */ | |
| 1378 static l_int32 | |
| 1379 numaEvalBestWidthAndShift(NUMA *nas, | |
| 1380 l_int32 nwidth, | |
| 1381 l_int32 nshift, | |
| 1382 l_float32 minwidth, | |
| 1383 l_float32 maxwidth, | |
| 1384 l_float32 *pbestwidth, | |
| 1385 l_float32 *pbestshift, | |
| 1386 l_float32 *pbestscore) | |
| 1387 { | |
| 1388 l_int32 i, j; | |
| 1389 l_float32 delwidth, delshift, width, shift, score; | |
| 1390 l_float32 bestwidth, bestshift, bestscore; | |
| 1391 | |
| 1392 if (!nas) | |
| 1393 return ERROR_INT("nas not defined", __func__, 1); | |
| 1394 if (!pbestwidth || !pbestshift) | |
| 1395 return ERROR_INT("&bestwidth and &bestshift not defined", __func__, 1); | |
| 1396 | |
| 1397 bestwidth = 0.0f; | |
| 1398 bestshift = 0.0f; | |
| 1399 bestscore = 1.0; | |
| 1400 delwidth = (maxwidth - minwidth) / (nwidth - 1.0); | |
| 1401 for (i = 0; i < nwidth; i++) { | |
| 1402 width = minwidth + delwidth * i; | |
| 1403 delshift = width / (l_float32)(nshift); | |
| 1404 for (j = 0; j < nshift; j++) { | |
| 1405 shift = -0.5 * (width - delshift) + j * delshift; | |
| 1406 numaEvalSyncError(nas, 0, 0, width, shift, &score, NULL); | |
| 1407 if (score < bestscore) { | |
| 1408 bestscore = score; | |
| 1409 bestwidth = width; | |
| 1410 bestshift = shift; | |
| 1411 #if DEBUG_FREQUENCY | |
| 1412 lept_stderr("width = %7.3f, shift = %7.3f, score = %7.3f\n", | |
| 1413 width, shift, score); | |
| 1414 #endif /* DEBUG_FREQUENCY */ | |
| 1415 } | |
| 1416 } | |
| 1417 } | |
| 1418 | |
| 1419 *pbestwidth = bestwidth; | |
| 1420 *pbestshift = bestshift; | |
| 1421 if (pbestscore) | |
| 1422 *pbestscore = bestscore; | |
| 1423 return 0; | |
| 1424 } | |
| 1425 | |
| 1426 | |
| 1427 /*! | |
| 1428 * \brief numaEvalSyncError() | |
| 1429 * | |
| 1430 * \param[in] nas numa of crossing locations | |
| 1431 * \param[in] ifirst first crossing to use | |
| 1432 * \param[in] ilast last crossing to use; use 0 for all crossings | |
| 1433 * \param[in] width size of window | |
| 1434 * \param[in] shift of center of window w/rt first crossing | |
| 1435 * \param[out] pscore [optional] average squared error of dist | |
| 1436 * of crossing signal from the center of the window | |
| 1437 * \param[out] pnad [optional] numa of 1s and 0s for crossings | |
| 1438 * \return 0 if OK, 1 on error | |
| 1439 * | |
| 1440 * <pre> | |
| 1441 * Notes: | |
| 1442 * (1) The score is computed only on the part of the signal from the | |
| 1443 * %ifirst to %ilast crossings. Use 0 for both of these to | |
| 1444 * use all the crossings. The score is normalized for | |
| 1445 * the number of crossings and with half-width of the window. | |
| 1446 * (2) The optional return %nad is a sequence of 0s and 1s, where a '1' | |
| 1447 * indicates a crossing in the window. | |
| 1448 * </pre> | |
| 1449 */ | |
| 1450 static l_int32 | |
| 1451 numaEvalSyncError(NUMA *nas, | |
| 1452 l_int32 ifirst, | |
| 1453 l_int32 ilast, | |
| 1454 l_float32 width, | |
| 1455 l_float32 shift, | |
| 1456 l_float32 *pscore, | |
| 1457 NUMA **pnad) | |
| 1458 { | |
| 1459 l_int32 i, n, nc, nw, ival; | |
| 1460 l_int32 iw; /* cell in which transition occurs */ | |
| 1461 l_float32 score, xfirst, xlast, xleft, xc, xwc; | |
| 1462 NUMA *nad; | |
| 1463 | |
| 1464 if (!nas) | |
| 1465 return ERROR_INT("nas not defined", __func__, 1); | |
| 1466 if ((n = numaGetCount(nas)) < 2) | |
| 1467 return ERROR_INT("nas size < 2", __func__, 1); | |
| 1468 if (ifirst < 0) ifirst = 0; | |
| 1469 if (ilast <= 0) ilast = n - 1; | |
| 1470 if (ifirst >= ilast) | |
| 1471 return ERROR_INT("ifirst not < ilast", __func__, 1); | |
| 1472 nc = ilast - ifirst + 1; | |
| 1473 | |
| 1474 /* Set up an array corresponding to the (shifted) windows, | |
| 1475 * and fill in the crossings. */ | |
| 1476 score = 0.0; | |
| 1477 numaGetFValue(nas, ifirst, &xfirst); | |
| 1478 numaGetFValue(nas, ilast, &xlast); | |
| 1479 nw = (l_int32) ((xlast - xfirst + 2.0 * width) / width); | |
| 1480 nad = numaCreate(nw); | |
| 1481 numaSetCount(nad, nw); /* init to all 0.0 */ | |
| 1482 xleft = xfirst - width / 2.0 + shift; /* left edge of first window */ | |
| 1483 for (i = ifirst; i <= ilast; i++) { | |
| 1484 numaGetFValue(nas, i, &xc); | |
| 1485 iw = (l_int32)((xc - xleft) / width); | |
| 1486 xwc = xleft + (iw + 0.5) * width; /* center of cell iw */ | |
| 1487 score += (xwc - xc) * (xwc - xc); | |
| 1488 numaGetIValue(nad, iw, &ival); | |
| 1489 numaSetValue(nad, iw, ival + 1); | |
| 1490 } | |
| 1491 | |
| 1492 if (pscore) | |
| 1493 *pscore = 4.0 * score / (width * width * (l_float32)nc); | |
| 1494 if (pnad) | |
| 1495 *pnad = nad; | |
| 1496 else | |
| 1497 numaDestroy(&nad); | |
| 1498 | |
| 1499 return 0; | |
| 1500 } |
