From f312b538ff9eb2f88b2a6bf96862b660534fb484 Mon Sep 17 00:00:00 2001 From: Dan Bloomberg Date: Tue, 4 Dec 2018 15:42:08 -0800 Subject: [PATCH] Add new function for determining if box sizes are essentially the same. * These boxes can represent page image regions and have outliers. * If the test comes back true, page size reconciliation can be attempted. * Add test to boxa3_reg. --- prog/boxa3_reg.c | 41 +++++++++----- src/allheaders.h | 3 +- src/boxfunc5.c | 145 +++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 164 insertions(+), 25 deletions(-) diff --git a/prog/boxa3_reg.c b/prog/boxa3_reg.c index 792593fc7..8f972e28d 100644 --- a/prog/boxa3_reg.c +++ b/prog/boxa3_reg.c @@ -38,6 +38,9 @@ static const char *boxafiles[3] = {"boxap1.ba", "boxap2.ba", "boxap3.ba"}; void static TestBoxa(L_REGPARAMS *rp, l_int32 index); +static l_float32 varp[3] = {0.0165, 0.0432, 0.0716}; +static l_float32 varm[3] = {0.0088, 0.0213, 0.0357}; +static l_int32 same[3] = {1, -1, -1}; static l_float32 devwidth[3] = {0.0864, 0.0895, 0.1174}; static l_float32 devheight[3] = {0.0048, 0.0294, 0.0023}; @@ -62,9 +65,9 @@ TestBoxa(L_REGPARAMS *rp, l_int32 index) { l_uint8 *data; -l_int32 w, h, medw, medh; +l_int32 w, h, medw, medh, isame; size_t size; -l_float32 scalefact, devw, devh, ratiowh; +l_float32 scalefact, devw, devh, ratiowh, fvarp, fvarm; BOXA *boxa1, *boxa2, *boxa3; PIX *pix1; @@ -75,10 +78,10 @@ PIX *pix1; scalefact = 100.0 / (l_float32)w; boxa2 = boxaTransform(boxa1, 0, 0, scalefact, scalefact); boxaWriteMem(&data, &size, boxa2); - regTestWriteDataAndCheck(rp, data, size, "ba"); /* 0, 10, 20 */ + regTestWriteDataAndCheck(rp, data, size, "ba"); /* 0, 13, 26 */ lept_free(data); pix1 = boxaDisplayTiled(boxa2, NULL, 0, -1, 2200, 2, 1.0, 0, 3, 2); - regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 1, 11, 21 */ + regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 1, 14, 27 */ pixDisplayWithTitle(pix1, 0, 0, NULL, rp->display); pixDestroy(&pix1); @@ -88,10 +91,20 @@ PIX *pix1; if (rp->display) fprintf(stderr, "median width = %d, median height = %d\n", medw, medh); - /* Check for deviations from median by pairs */ - boxaEvalSizeConsistency(boxa2, &devw, &devh, 0); - regTestCompareValues(rp, devwidth[index], devw, 0.001); /* 2, 12, 22 */ - regTestCompareValues(rp, devheight[index], devh, 0.001); /* 3, 13, 23 */ + /* Check for deviations from median by pairs: method 1 */ + boxaSizeConsistency1(boxa2, L_CHECK_HEIGHT, 0.0, 0.0, + &fvarp, &fvarm, &isame); + regTestCompareValues(rp, varp[index], fvarp, 0.003); /* 2, 15, 28 */ + regTestCompareValues(rp, varm[index], fvarm, 0.003); /* 3, 16, 29 */ + regTestCompareValues(rp, same[index], isame, 0); /* 4, 17, 30 */ + if (rp->display) + fprintf(stderr, "fvarp = %7.4f, fvarm = %7.4f, same = %d\n", + fvarp, fvarm, isame); + + /* Check for deviations from median by pairs: method 2 */ + boxaSizeConsistency2(boxa2, &devw, &devh, 0); + regTestCompareValues(rp, devwidth[index], devw, 0.001); /* 5, 18, 31 */ + regTestCompareValues(rp, devheight[index], devh, 0.001); /* 6, 19, 32 */ if (rp->display) fprintf(stderr, "dev width = %7.4f, dev height = %7.4f\n", devw, devh); @@ -99,10 +112,10 @@ PIX *pix1; boxa3 = boxaReconcileSizeByMedian(boxa2, L_CHECK_WIDTH, 0.05, 1.03, NULL, NULL, &ratiowh); boxaWriteMem(&data, &size, boxa3); - regTestWriteDataAndCheck(rp, data, size, "ba"); /* 4, 14, 24 */ + regTestWriteDataAndCheck(rp, data, size, "ba"); /* 7, 20, 33 */ lept_free(data); pix1 = boxaDisplayTiled(boxa3, NULL, 0, -1, 2200, 2, 1.0, 0, 3, 2); - regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 5, 15, 25 */ + regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 8, 21, 34 */ pixDisplayWithTitle(pix1, 500, 0, NULL, rp->display); if (rp->display) fprintf(stderr, "ratio median width/height = %6.3f\n", ratiowh); @@ -113,10 +126,10 @@ PIX *pix1; boxa3 = boxaReconcileSizeByMedian(boxa2, L_CHECK_HEIGHT, 0.05, 1.03, NULL, NULL, NULL); boxaWriteMem(&data, &size, boxa3); - regTestWriteDataAndCheck(rp, data, size, "ba"); /* 6, 16, 26 */ + regTestWriteDataAndCheck(rp, data, size, "ba"); /* 9, 22, 35 */ lept_free(data); pix1 = boxaDisplayTiled(boxa3, NULL, 0, -1, 2200, 2, 1.0, 0, 3, 2); - regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 7, 17, 27 */ + regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 10, 23, 36 */ pixDisplayWithTitle(pix1, 1000, 0, NULL, rp->display); boxaDestroy(&boxa3); pixDestroy(&pix1); @@ -125,10 +138,10 @@ PIX *pix1; boxa3 = boxaReconcileSizeByMedian(boxa2, L_CHECK_BOTH, 0.05, 1.03, NULL, NULL, NULL); boxaWriteMem(&data, &size, boxa3); - regTestWriteDataAndCheck(rp, data, size, "ba"); /* 8, 18, 28 */ + regTestWriteDataAndCheck(rp, data, size, "ba"); /* 11, 24, 37 */ lept_free(data); pix1 = boxaDisplayTiled(boxa3, NULL, 0, -1, 2200, 2, 1.0, 0, 3, 2); - regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 9, 19, 29 */ + regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 12, 25, 38 */ pixDisplayWithTitle(pix1, 1500, 0, NULL, rp->display); boxaDestroy(&boxa3); pixDestroy(&pix1); diff --git a/src/allheaders.h b/src/allheaders.h index 7c8f37a4d..a931d67e7 100644 --- a/src/allheaders.h +++ b/src/allheaders.h @@ -369,7 +369,8 @@ LEPT_DLL extern BOXA * boxaModifyWithBoxa ( BOXA *boxas, BOXA *boxam, l_int32 su LEPT_DLL extern BOXA * boxaConstrainSize ( BOXA *boxas, l_int32 width, l_int32 widthflag, l_int32 height, l_int32 heightflag ); LEPT_DLL extern BOXA * boxaReconcileEvenOddHeight ( BOXA *boxas, l_int32 sides, l_int32 delh, l_int32 op, l_float32 factor, l_int32 start ); LEPT_DLL extern BOXA * boxaReconcilePairWidth ( BOXA *boxas, l_int32 delw, l_int32 op, l_float32 factor, NUMA *na ); -LEPT_DLL extern l_ok boxaEvalSizeConsistency ( BOXA *boxas, l_float32 *pfdevw, l_float32 *pfdevh, l_int32 debug ); +LEPT_DLL extern l_ok boxaSizeConsistency1 ( BOXA *boxas, l_int32 type, l_float32 threshp, l_float32 threshm, l_float32 *pfvarp, l_float32 *pfvarm, l_int32 *psame ); +LEPT_DLL extern l_ok boxaSizeConsistency2 ( BOXA *boxas, l_float32 *pfdevw, l_float32 *pfdevh, l_int32 debug ); LEPT_DLL extern BOXA * boxaReconcileSizeByMedian ( BOXA *boxas, l_int32 type, l_float32 fract, l_float32 factor, NUMA **pnadelw, NUMA **pnadelh, l_float32 *pratiowh ); LEPT_DLL extern l_ok boxaPlotSides ( BOXA *boxa, const char *plotname, NUMA **pnal, NUMA **pnat, NUMA **pnar, NUMA **pnab, PIX **ppixd ); LEPT_DLL extern l_ok boxaPlotSizes ( BOXA *boxa, const char *plotname, NUMA **pnaw, NUMA **pnah, PIX **ppixd ); diff --git a/src/boxfunc5.c b/src/boxfunc5.c index 8d3a45910..62fb6a084 100644 --- a/src/boxfunc5.c +++ b/src/boxfunc5.c @@ -38,7 +38,8 @@ * BOXA *boxaReconcileEvenOddHeight() * static l_int32 boxaTestEvenOddHeight() * BOXA *boxaReconcilePairWidth() - * l_int32 boxaEvalSizeConsistency() + * l_int32 boxaSizeConsistency1() + * l_int32 boxaSizeConsistency2() * BOXA *boxaReconcileSizeByMedian() * l_int32 boxaPlotSides() [for debugging] * l_int32 boxaPlotSizes() [for debugging] @@ -1084,7 +1085,128 @@ BOXA *boxae, *boxao, *boxad; /*! - * \brief boxaEvalSizeConsistency() + * \brief boxaSizeConsistency1() + * + * \param[in] boxas of size >= 10 + * \param[in] type L_CHECK_WIDTH, L_CHECK_HEIGHT + * \param[in] threshp threshold for pairwise fractional variation + * \param[in] threshm threshold for fractional variation from median + * \param[out] pfvarp [optional] average fractional pairwise variation + * \param[out] pfvarm [optional] average fractional median variation + * \param[out] psame decision for uniformity of page size (1, 0, -1) + * + *
+ * Notes:
+ *      (1) This evaluates a boxa for particular types of dimensional
+ *          variation.  Select either width or height variation.  Then
+ *          it returns two numbers: one is based on pairwise (even/odd)
+ *          variation; the other is based on the average variation
+ *          from the boxa median.
+ *      (2) For the pairwise variation, get the fraction of the absolute
+ *          difference in dimension of each pair of boxes, and take
+ *          the average value.  The median variation is simply the
+ *          the average of the fractional deviation from the median
+ *          of all the boxes.
+ *      (3) Use 0 for default values of %threshp and %threshm.  They are
+ *            threshp:  0.02
+ *            threshm:  0.015
+ *      (4) The intended application is that the boxes are a sequence of
+ *          page regions in a book scan, and we calculate two numbers
+ *          that can give an indication if the pages are approximately
+ *          the same size.  The pairwise variation should be small if
+ *          the boxes are correctly calculated.  If there are a
+ *          significant number of random or systematic outliers, the
+ *          pairwise variation will be large, and no decision will be made
+ *          (i.e., return same == -1).  Here are the possible outcomes:
+ *            Pairwise Var    Median Var    Decision
+ *            ------------    ----------    --------
+ *            small           small         same size  (1)
+ *            small           large         different size  (0)
+ *            large           small/large   unknown   (-1)
+ * 
+ */ +l_ok +boxaSizeConsistency1(BOXA *boxas, + l_int32 type, + l_float32 threshp, + l_float32 threshm, + l_float32 *pfvarp, + l_float32 *pfvarm, + l_int32 *psame) +{ +l_int32 i, n, bw1, bh1, bw2, bh2, npairs; +l_float32 ave, fdiff, sumdiff, med, fvarp, fvarm; +NUMA *na1; + + PROCNAME("boxaSizeConsistency1"); + + if (pfvarp) *pfvarp = 0.0; + if (pfvarm) *pfvarm = 0.0; + if (!psame) + return ERROR_INT("&same not defined", procName, 1); + *psame = -1; + if (!boxas) + return ERROR_INT("boxas not defined", procName, 1); + if (boxaGetValidCount(boxas) < 6) + return ERROR_INT("need a least 6 valid boxes", procName, 1); + if (type != L_CHECK_WIDTH && type != L_CHECK_HEIGHT) + return ERROR_INT("invalid type", procName, 1); + if (threshp < 0.0 || threshp >= 0.5) + return ERROR_INT("invalid threshp", procName, 1); + if (threshm < 0.0 || threshm >= 0.5) + return ERROR_INT("invalid threshm", procName, 1); + if (threshp == 0.0) threshp = 0.02; + if (threshm == 0.0) threshm = 0.015; + + /* Evaluate pairwise variation */ + n = boxaGetCount(boxas); + na1 = numaCreate(0); + for (i = 0, npairs = 0, sumdiff = 0; i < n - 1; i += 2) { + boxaGetBoxGeometry(boxas, i, NULL, NULL, &bw1, &bh1); + boxaGetBoxGeometry(boxas, i + 1, NULL, NULL, &bw2, &bh2); + if (bw1 == 0 || bh1 == 0 || bw2 == 0 || bh2 == 0) + continue; + npairs++; + if (type == L_CHECK_WIDTH) { + ave = (bw1 + bw2) / 2.0; + fdiff = L_ABS(bw1 - bw2) / ave; + numaAddNumber(na1, bw1); + numaAddNumber(na1, bw2); + } else { /* type == L_CHECK_HEIGHT) */ + ave = (bh1 + bh2) / 2.0; + fdiff = L_ABS(bh1 - bh2) / ave; + numaAddNumber(na1, bh1); + numaAddNumber(na1, bh2); + } + sumdiff += fdiff; + } + fvarp = sumdiff / npairs; + if (pfvarp) *pfvarp = fvarp; + + /* Evaluate the average abs fractional deviation from the median */ + numaGetMedian(na1, &med); + if (med == 0.0) { + L_WARNING("median value is 0\n", procName); + } else { + numaGetMeanDevFromMedian(na1, med, &fvarm); + fvarm /= med; + if (pfvarm) *pfvarm = fvarm; + } + numaDestroy(&na1); + + /* Make decision */ + if (fvarp < threshp && fvarm < threshm) + *psame = 1; + else if (fvarp < threshp && fvarm > threshm) + *psame = 0; + else + *psame = -1; /* unknown */ + return 0; +} + + +/*! + * \brief boxaSizeConsistency2() * * \param[in] boxas of size >= 10 * \param[out] pfdevw average fractional deviation from median width @@ -1100,12 +1222,15 @@ BOXA *boxae, *boxao, *boxad; * about whether the pages should be approximately the same size. * The determination should be robust to outliers, both random * and (for many cases) systematic. - * (2) Adjacent even and odd boxes are expected to be the same size. + * (2) This differs from boxaSizeConsistency1() in that it attempts + * to correct for box dimensional errors before doing the + * evaluation. For this reason, it may be less robust. + * (3) Adjacent even and odd boxes are expected to be the same size. * Take them pairwise, and assume the minimum height, hmin, * is correct. Then for (the usual case) wmin/hmin > 0.5, assume * the minimum width is correct. If wmin/hmin <= 0.5, assume * the maximum width is correct. - * (3) After correcting each pair so that they are the same size, + * (4) After correcting each pair so that they are the same size, * compute the average fractional deviation, from median width and * height. A deviation of width or height by more than about * 0.02 is evidence that the boxes may be from a non-homogeneous @@ -1113,10 +1238,10 @@ BOXA *boxae, *boxao, *boxad; * */ l_ok -boxaEvalSizeConsistency(BOXA *boxas, - l_float32 *pfdevw, - l_float32 *pfdevh, - l_int32 debug) +boxaSizeConsistency2(BOXA *boxas, + l_float32 *pfdevw, + l_float32 *pfdevh, + l_int32 debug) { l_int32 i, n, bw1, bh1, bw2, bh2, npairs; l_float32 medw, medh, devw, devh, minw, maxw, minh, w; @@ -1126,7 +1251,7 @@ NUMA *naw, *nah; PIX *pix1, *pix2, *pix3; PIXA *pixa; - PROCNAME("boxaEvalSizeConsistency"); + PROCNAME("boxaSizeConsistency2"); if (pfdevw) *pfdevw = 0.0; if (pfdevh) *pfdevh = 0.0; @@ -1208,7 +1333,7 @@ PIXA *pixa; * \param[in] boxas containing at least 6 valid boxes * \param[in] type L_CHECK_WIDTH, L_CHECK_HEIGHT, L_CHECK_BOTH * \param[in] fract threshold fraction of size variation from median; - * in range (0 ... 1); typ. about 0.1. + * in range (0 ... 1); typ. about 0.05. * \param[in] factor expansion for fixed box beyond median width; * should be near 1.0. * \param[out] pnadelw [optional] diff from median width for boxes