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

1173 lines
42 KiB
C

/*====================================================================*
- Copyright (C) 2001 Leptonica. All rights reserved.
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions
- are met:
- 1. Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
- 2. Redistributions in binary form must reproduce the above
- copyright notice, this list of conditions and the following
- disclaimer in the documentation and/or other materials
- provided with the distribution.
-
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
- CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
- OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*====================================================================*/
/*!
* \file dewarp4.c
* <pre>
*
* Single page dewarper
*
* Reference model (book-level, dewarpa) operations and debugging output
*
* Top-level single page dewarper
* l_int32 dewarpSinglePage()
* l_int32 dewarpSinglePageInit()
* l_int32 dewarpSinglePageRun()
*
* Operations on dewarpa
* l_int32 dewarpaListPages()
* l_int32 dewarpaSetValidModels()
* l_int32 dewarpaInsertRefModels()
* l_int32 dewarpaStripRefModels()
* l_int32 dewarpaRestoreModels()
*
* Dewarp debugging output
* l_int32 dewarpaInfo()
* l_int32 dewarpaModelStats()
* static l_int32 dewarpaTestForValidModel()
* l_int32 dewarpaShowArrays()
* l_int32 dewarpDebug()
* l_int32 dewarpShowResults()
* </pre>
*/
#include <math.h>
#include "allheaders.h"
static l_int32 dewarpaTestForValidModel(L_DEWARPA *dewa, L_DEWARP *dew,
l_int32 notests);
#ifndef NO_CONSOLE_IO
#define DEBUG_INVALID_MODELS 0 /* set this to 1 for debugging */
#endif /* !NO_CONSOLE_IO */
/* Special parameter value */
static const l_int32 GrayInValue = 200;
/*----------------------------------------------------------------------*
* Top-level single page dewarper *
*----------------------------------------------------------------------*/
/*!
* \brief dewarpSinglePage()
*
* \param[in] pixs with text, any depth
* \param[in] thresh for global thresh to 1 bpp; ignore otherwise
* \param[in] adaptive 1 for adaptive thresh; 0 for global threshold
* \param[in] useboth 1 for both horiz and vert; 0 for vertical only
* \param[in] check_columns 1 to skip horizontal if multiple columns;
* 0 otherwise; default is to skip
* \param[out] ppixd dewarped result
* \param[out] pdewa [optional] dewa with single page; NULL to skip
* \param[in] debug 1 for debugging output, 0 otherwise
* \return 0 if OK, 1 on error list of page numbers, or NULL on error
*
* <pre>
* Notes:
* (1) Dewarps pixs and returns the result in &pixd.
* (2) This uses default values for all model parameters.
* (3) If pixs is 1 bpp, the parameters %adaptive and %thresh are ignored.
* (4) If it can't build a model, returns a copy of pixs in &pixd.
* </pre>
*/
l_ok
dewarpSinglePage(PIX *pixs,
l_int32 thresh,
l_int32 adaptive,
l_int32 useboth,
l_int32 check_columns,
PIX **ppixd,
L_DEWARPA **pdewa,
l_int32 debug)
{
L_DEWARPA *dewa;
PIX *pixb;
PROCNAME("dewarpSinglePage");
if (!ppixd)
return ERROR_INT("&pixd not defined", procName, 1);
*ppixd = NULL;
if (pdewa) *pdewa = NULL;
if (!pixs)
return ERROR_INT("pixs not defined", procName, 1);
dewarpSinglePageInit(pixs, thresh, adaptive, useboth,
check_columns, &pixb, &dewa);
if (!pixb) {
dewarpaDestroy(&dewa);
return ERROR_INT("pixb not made", procName, 1);
}
dewarpSinglePageRun(pixs, pixb, dewa, ppixd, debug);
if (pdewa)
*pdewa = dewa;
else
dewarpaDestroy(&dewa);
pixDestroy(&pixb);
return 0;
}
/*!
* \brief dewarpSinglePageInit()
*
* \param[in] pixs with text, any depth
* \param[in] thresh for global thresh to 1 bpp; ignore otherwise
* \param[in] adaptive 1 for adaptive thresh; 0 for global threshold
* \param[in] useboth 1 for both horiz and vert; 0 for vertical only
* \param[in] check_columns 1 to skip horizontal if multiple columns;
* 0 otherwise; default is to skip
* \param[out] ppixb 1 bpp debug image
* \param[out] pdewa initialized dewa
* \return 0 if OK, 1 on error list of page numbers, or NULL on error
*
* <pre>
* Notes:
* (1) This binarizes the input pixs if necessary, returning the
* binarized image. It also initializes the dewa to default values
* for the model parameters.
* (2) If pixs is 1 bpp, the parameters %adaptive and %thresh are ignored.
* (3) To change the model parameters, call dewarpaSetCurvatures()
* before running dewarpSinglePageRun(). For example:
* dewarpSinglePageInit(pixs, 0, 1, 1, 1, &pixb, &dewa);
* dewarpaSetCurvatures(dewa, 250, -1, -1, 80, 70, 150);
* dewarpSinglePageRun(pixs, pixb, dewa, &pixd, 0);
* dewarpaDestroy(&dewa);
* pixDestroy(&pixb);
* </pre>
*/
l_ok
dewarpSinglePageInit(PIX *pixs,
l_int32 thresh,
l_int32 adaptive,
l_int32 useboth,
l_int32 check_columns,
PIX **ppixb,
L_DEWARPA **pdewa)
{
PIX *pix1;
PROCNAME("dewarpSinglePageInit");
if (ppixb) *ppixb = NULL;
if (pdewa) *pdewa = NULL;
if (!ppixb || !pdewa)
return ERROR_INT("&pixb and &dewa not both defined", procName, 1);
if (!pixs)
return ERROR_INT("pixs not defined", procName, 1);
*pdewa = dewarpaCreate(1, 0, 1, 0, -1);
dewarpaUseBothArrays(*pdewa, useboth);
dewarpaSetCheckColumns(*pdewa, check_columns);
/* Generate a binary image, if necessary */
if (pixGetDepth(pixs) > 1) {
pix1 = pixConvertTo8(pixs, 0);
if (adaptive)
*ppixb = pixAdaptThresholdToBinary(pix1, NULL, 1.0);
else
*ppixb = pixThresholdToBinary(pix1, thresh);
pixDestroy(&pix1);
} else {
*ppixb = pixClone(pixs);
}
return 0;
}
/*!
* \brief dewarpSinglePageRun()
*
* \param[in] pixs any depth
* \param[in] pixb 1 bpp
* \param[in] dewa initialized
* \param[out] ppixd dewarped result
* \param[in] debug 1 for debugging output, 0 otherwise
* \return 0 if OK, 1 on error list of page numbers, or NULL on error
*
* <pre>
* Notes:
* (1) Dewarps pixs and returns the result in &pixd.
* (2) The 1 bpp version %pixb and %dewa are conveniently generated by
* dewarpSinglePageInit().
* (3) Non-default model parameters must be set before calling this.
* (4) If a model cannot be built, this returns a copy of pixs in &pixd.
* </pre>
*/
l_ok
dewarpSinglePageRun(PIX *pixs,
PIX *pixb,
L_DEWARPA *dewa,
PIX **ppixd,
l_int32 debug)
{
const char *debugfile;
l_int32 vsuccess, ret;
L_DEWARP *dew;
PROCNAME("dewarpSinglePageRun");
if (!ppixd)
return ERROR_INT("&pixd not defined", procName, 1);
*ppixd = NULL;
if (!pixs)
return ERROR_INT("pixs not defined", procName, 1);
if (!pixb)
return ERROR_INT("pixb not defined", procName, 1);
if (!dewa)
return ERROR_INT("dewa not defined", procName, 1);
if (debug)
lept_mkdir("lept/dewarp");
/* Generate the page model */
dew = dewarpCreate(pixb, 0);
dewarpaInsertDewarp(dewa, dew);
debugfile = (debug) ? "/tmp/lept/dewarp/singlepage_model.pdf" : NULL;
dewarpBuildPageModel(dew, debugfile);
dewarpaModelStatus(dewa, 0, &vsuccess, NULL);
if (vsuccess == 0) {
L_ERROR("failure to build model for vertical disparity\n", procName);
*ppixd = pixCopy(NULL, pixs);
return 0;
}
/* Apply the page model */
debugfile = (debug) ? "/tmp/lept/dewarp/singlepage_apply.pdf" : NULL;
ret = dewarpaApplyDisparity(dewa, 0, pixs, 255, 0, 0, ppixd, debugfile);
if (ret)
L_ERROR("invalid model; failure to apply disparity\n", procName);
return 0;
}
/*----------------------------------------------------------------------*
* Operations on dewarpa *
*----------------------------------------------------------------------*/
/*!
* \brief dewarpaListPages()
*
* \param[in] dewa populated with dewarp structs for pages
* \return 0 if OK, 1 on error list of page numbers, or NULL on error
*
* <pre>
* Notes:
* (1) This generates two numas, stored in the dewarpa, that give:
* (a) the page number for each dew that has a page model.
* (b) the page number for each dew that has either a page
* model or a reference model.
* It can be called at any time.
* (2) It is called by the dewarpa serializer before writing.
* </pre>
*/
l_ok
dewarpaListPages(L_DEWARPA *dewa)
{
l_int32 i;
L_DEWARP *dew;
NUMA *namodels, *napages;
PROCNAME("dewarpaListPages");
if (!dewa)
return ERROR_INT("dewa not defined", procName, 1);
numaDestroy(&dewa->namodels);
numaDestroy(&dewa->napages);
namodels = numaCreate(dewa->maxpage + 1);
napages = numaCreate(dewa->maxpage + 1);
dewa->namodels = namodels;
dewa->napages = napages;
for (i = 0; i <= dewa->maxpage; i++) {
if ((dew = dewarpaGetDewarp(dewa, i)) != NULL) {
if (dew->hasref == 0)
numaAddNumber(namodels, dew->pageno);
numaAddNumber(napages, dew->pageno);
}
}
return 0;
}
/*!
* \brief dewarpaSetValidModels()
*
* \param[in] dewa
* \param[in] notests
* \param[in] debug 1 to output information on invalid page models
* \return 0 if OK, 1 on error
*
* <pre>
* Notes:
* (1) A valid model must meet the rendering requirements, which
* include whether or not a vertical disparity model exists
* and conditions on curvatures for vertical and horizontal
* disparity models.
* (2) If %notests == 1, this ignores the curvature constraints
* and assumes that all successfully built models are valid.
* (3) This function does not need to be called by the application.
* It is called by dewarpaInsertRefModels(), which
* will destroy all invalid dewarps. Consequently, to inspect
* an invalid dewarp model, it must be done before calling
* dewarpaInsertRefModels().
* </pre>
*/
l_ok
dewarpaSetValidModels(L_DEWARPA *dewa,
l_int32 notests,
l_int32 debug)
{
l_int32 i, n, maxcurv, diffcurv, diffedge;
L_DEWARP *dew;
PROCNAME("dewarpaSetValidModels");
if (!dewa)
return ERROR_INT("dewa not defined", procName, 1);
n = dewa->maxpage + 1;
for (i = 0; i < n; i++) {
if ((dew = dewarpaGetDewarp(dewa, i)) == NULL)
continue;
if (debug) {
if (dew->hasref == 1) {
L_INFO("page %d: has only a ref model\n", procName, i);
} else if (dew->vsuccess == 0) {
L_INFO("page %d: no model successfully built\n",
procName, i);
} else if (!notests) {
maxcurv = L_MAX(L_ABS(dew->mincurv), L_ABS(dew->maxcurv));
diffcurv = dew->maxcurv - dew->mincurv;
if (dewa->useboth && !dew->hsuccess)
L_INFO("page %d: useboth, but no horiz disparity\n",
procName, i);
if (maxcurv > dewa->max_linecurv)
L_INFO("page %d: max curvature %d > max_linecurv\n",
procName, i, diffcurv);
if (diffcurv < dewa->min_diff_linecurv)
L_INFO("page %d: diff curv %d < min_diff_linecurv\n",
procName, i, diffcurv);
if (diffcurv > dewa->max_diff_linecurv)
L_INFO("page %d: abs diff curv %d > max_diff_linecurv\n",
procName, i, diffcurv);
if (dew->hsuccess) {
if (L_ABS(dew->leftslope) > dewa->max_edgeslope)
L_INFO("page %d: abs left slope %d > max_edgeslope\n",
procName, i, dew->leftslope);
if (L_ABS(dew->rightslope) > dewa->max_edgeslope)
L_INFO("page %d: abs right slope %d > max_edgeslope\n",
procName, i, dew->rightslope);
diffedge = L_ABS(dew->leftcurv - dew->rightcurv);
if (L_ABS(dew->leftcurv) > dewa->max_edgecurv)
L_INFO("page %d: left curvature %d > max_edgecurv\n",
procName, i, dew->leftcurv);
if (L_ABS(dew->rightcurv) > dewa->max_edgecurv)
L_INFO("page %d: right curvature %d > max_edgecurv\n",
procName, i, dew->rightcurv);
if (diffedge > dewa->max_diff_edgecurv)
L_INFO("page %d: abs diff left-right curv %d > "
"max_diff_edgecurv\n", procName, i, diffedge);
}
}
}
dewarpaTestForValidModel(dewa, dew, notests);
}
return 0;
}
/*!
* \brief dewarpaInsertRefModels()
*
* \param[in] dewa
* \param[in] notests if 1, ignore curvature constraints on model
* \param[in] debug 1 to output information on invalid page models
* \return 0 if OK, 1 on error
*
* <pre>
* Notes:
* (1) This destroys all dewarp models that are invalid, and then
* inserts reference models where possible.
* (2) If %notests == 1, this ignores the curvature constraints
* and assumes that all successfully built models are valid.
* (3) If useboth == 0, it uses the closest valid model within the
* distance and parity constraints. If useboth == 1, it tries
* to use the closest allowed hvalid model; if it doesn't find
* an hvalid model, it uses the closest valid model.
* (4) For all pages without a model, this clears out any existing
* invalid and reference dewarps, finds the nearest valid model
* with the same parity, and inserts an empty dewarp with the
* reference page.
* (5) Then if it is requested to use both vertical and horizontal
* disparity arrays (useboth == 1), it tries to replace any
* hvalid == 0 model or reference with an hvalid == 1 reference.
* (6) The distance constraint is that any reference model must
* be within maxdist. Note that with the parity constraint,
* no reference models will be used if maxdist < 2.
* (7) This function must be called, even if reference models will
* not be used. It should be called after building models on all
* available pages, and after setting the rendering parameters.
* (8) If the dewa has been serialized, this function is called by
* dewarpaRead() when it is read back. It is also called
* any time the rendering parameters are changed.
* (9) Note: if this has been called with useboth == 1, and useboth
* is reset to 0, you should first call dewarpaRestoreModels()
* to bring real models from the cache back to the primary array.
* </pre>
*/
l_ok
dewarpaInsertRefModels(L_DEWARPA *dewa,
l_int32 notests,
l_int32 debug)
{
l_int32 i, j, n, val, min, distdown, distup;
L_DEWARP *dew;
NUMA *na, *nah;
PROCNAME("dewarpaInsertRefModels");
if (!dewa)
return ERROR_INT("dewa not defined", procName, 1);
if (dewa->maxdist < 2)
L_INFO("maxdist < 2; no ref models can be used\n", procName);
/* Make an indicator numa for pages with valid models. */
dewarpaSetValidModels(dewa, notests, debug);
n = dewa->maxpage + 1;
na = numaMakeConstant(0, n);
for (i = 0; i < n; i++) {
dew = dewarpaGetDewarp(dewa, i);
if (dew && dew->vvalid)
numaReplaceNumber(na, i, 1);
}
/* Remove all existing ref models and restore models from cache */
dewarpaRestoreModels(dewa);
/* Move invalid models to the cache, and insert reference dewarps
* for pages that need to borrow a model.
* First, try to find a valid model for each page. */
for (i = 0; i < n; i++) {
numaGetIValue(na, i, &val);
if (val == 1) continue; /* already has a valid model */
if ((dew = dewa->dewarp[i]) != NULL) { /* exists but is not valid; */
dewa->dewarpcache[i] = dew; /* move it to the cache */
dewa->dewarp[i] = NULL;
}
if (dewa->maxdist < 2) continue; /* can't use a ref model */
/* Look back for nearest model */
distdown = distup = dewa->maxdist + 1;
for (j = i - 2; j >= 0 && distdown > dewa->maxdist; j -= 2) {
numaGetIValue(na, j, &val);
if (val == 1) distdown = i - j;
}
/* Look ahead for nearest model */
for (j = i + 2; j < n && distup > dewa->maxdist; j += 2) {
numaGetIValue(na, j, &val);
if (val == 1) distup = j - i;
}
min = L_MIN(distdown, distup);
if (min > dewa->maxdist) continue; /* no valid model in range */
if (distdown <= distup)
dewarpaInsertDewarp(dewa, dewarpCreateRef(i, i - distdown));
else
dewarpaInsertDewarp(dewa, dewarpCreateRef(i, i + distup));
}
numaDestroy(&na);
/* If a valid model will do, we're finished. */
if (dewa->useboth == 0) {
dewa->modelsready = 1; /* validated */
return 0;
}
/* The request is useboth == 1. Now try to find an hvalid model */
nah = numaMakeConstant(0, n);
for (i = 0; i < n; i++) {
dew = dewarpaGetDewarp(dewa, i);
if (dew && dew->hvalid)
numaReplaceNumber(nah, i, 1);
}
for (i = 0; i < n; i++) {
numaGetIValue(nah, i, &val);
if (val == 1) continue; /* already has a hvalid model */
if (dewa->maxdist < 2) continue; /* can't use a ref model */
distdown = distup = 100000;
for (j = i - 2; j >= 0; j -= 2) { /* look back for nearest model */
numaGetIValue(nah, j, &val);
if (val == 1) {
distdown = i - j;
break;
}
}
for (j = i + 2; j < n; j += 2) { /* look ahead for nearest model */
numaGetIValue(nah, j, &val);
if (val == 1) {
distup = j - i;
break;
}
}
min = L_MIN(distdown, distup);
if (min > dewa->maxdist) continue; /* no hvalid model within range */
/* We can replace the existing valid model with an hvalid model.
* If it's not a reference, save it in the cache. */
if ((dew = dewarpaGetDewarp(dewa, i)) == NULL) {
L_ERROR("dew is null for page %d!\n", procName, i);
} else {
if (dew->hasref == 0) { /* not a ref model */
dewa->dewarpcache[i] = dew; /* move it to the cache */
dewa->dewarp[i] = NULL; /* must null the ptr */
}
}
if (distdown <= distup) /* insert the hvalid ref model */
dewarpaInsertDewarp(dewa, dewarpCreateRef(i, i - distdown));
else
dewarpaInsertDewarp(dewa, dewarpCreateRef(i, i + distup));
}
numaDestroy(&nah);
dewa->modelsready = 1; /* validated */
return 0;
}
/*!
* \brief dewarpaStripRefModels()
*
* \param[in] dewa populated with dewarp structs for pages
* \return 0 if OK, 1 on error
*
* <pre>
* Notes:
* (1) This examines each dew in a dewarpa, and removes
* all that don't have their own page model (i.e., all
* that have "references" to nearby pages with valid models).
* These references were generated by dewarpaInsertRefModels(dewa).
* </pre>
*/
l_ok
dewarpaStripRefModels(L_DEWARPA *dewa)
{
l_int32 i;
L_DEWARP *dew;
PROCNAME("dewarpaStripRefModels");
if (!dewa)
return ERROR_INT("dewa not defined", procName, 1);
for (i = 0; i <= dewa->maxpage; i++) {
if ((dew = dewarpaGetDewarp(dewa, i)) != NULL) {
if (dew->hasref)
dewarpDestroy(&dewa->dewarp[i]);
}
}
dewa->modelsready = 0;
/* Regenerate the page lists */
dewarpaListPages(dewa);
return 0;
}
/*!
* \brief dewarpaRestoreModels()
*
* \param[in] dewa populated with dewarp structs for pages
* \return 0 if OK, 1 on error
*
* <pre>
* Notes:
* (1) This puts all real models (and only real models) in the
* primary dewarpa array. First remove all dewarps that are
* only references to other page models. Then move all models
* that had been cached back into the primary dewarp array.
* (2) After this is done, we still need to recompute and insert
* the reference models before dewa->modelsready is true.
* </pre>
*/
l_ok
dewarpaRestoreModels(L_DEWARPA *dewa)
{
l_int32 i;
L_DEWARP *dew;
PROCNAME("dewarpaRestoreModels");
if (!dewa)
return ERROR_INT("dewa not defined", procName, 1);
/* Strip out ref models. Then only real models will be in the
* primary dewarp array. */
dewarpaStripRefModels(dewa);
/* The cache holds only real models, which are not necessarily valid. */
for (i = 0; i <= dewa->maxpage; i++) {
if ((dew = dewa->dewarpcache[i]) != NULL) {
if (dewa->dewarp[i]) {
L_ERROR("dew in both cache and main array!: page %d\n",
procName, i);
} else {
dewa->dewarp[i] = dew;
dewa->dewarpcache[i] = NULL;
}
}
}
dewa->modelsready = 0; /* new ref models not yet inserted */
/* Regenerate the page lists */
dewarpaListPages(dewa);
return 0;
}
/*----------------------------------------------------------------------*
* Dewarp debugging output *
*----------------------------------------------------------------------*/
/*!
* \brief dewarpaInfo()
*
* \param[in] fp
* \param[in] dewa
* \return 0 if OK, 1 on error
*/
l_ok
dewarpaInfo(FILE *fp,
L_DEWARPA *dewa)
{
l_int32 i, n, pageno, nnone, nvsuccess, nvvalid, nhsuccess, nhvalid, nref;
L_DEWARP *dew;
PROCNAME("dewarpaInfo");
if (!fp)
return ERROR_INT("dewa not defined", procName, 1);
if (!dewa)
return ERROR_INT("dewa not defined", procName, 1);
fprintf(fp, "\nDewarpaInfo: %p\n", dewa);
fprintf(fp, "nalloc = %d, maxpage = %d\n", dewa->nalloc, dewa->maxpage);
fprintf(fp, "sampling = %d, redfactor = %d, minlines = %d\n",
dewa->sampling, dewa->redfactor, dewa->minlines);
fprintf(fp, "maxdist = %d, useboth = %d\n",
dewa->maxdist, dewa->useboth);
dewarpaModelStats(dewa, &nnone, &nvsuccess, &nvvalid,
&nhsuccess, &nhvalid, &nref);
n = numaGetCount(dewa->napages);
fprintf(stderr, "Total number of pages with a dew = %d\n", n);
fprintf(stderr, "Number of pages without any models = %d\n", nnone);
fprintf(stderr, "Number of pages with a vert model = %d\n", nvsuccess);
fprintf(stderr, "Number of pages with a valid vert model = %d\n", nvvalid);
fprintf(stderr, "Number of pages with both models = %d\n", nhsuccess);
fprintf(stderr, "Number of pages with both models valid = %d\n", nhvalid);
fprintf(stderr, "Number of pages with a ref model = %d\n", nref);
for (i = 0; i < n; i++) {
numaGetIValue(dewa->napages, i, &pageno);
if ((dew = dewarpaGetDewarp(dewa, pageno)) == NULL)
continue;
fprintf(stderr, "Page: %d\n", dew->pageno);
fprintf(stderr, " hasref = %d, refpage = %d\n",
dew->hasref, dew->refpage);
fprintf(stderr, " nlines = %d\n", dew->nlines);
fprintf(stderr, " w = %d, h = %d, nx = %d, ny = %d\n",
dew->w, dew->h, dew->nx, dew->ny);
if (dew->sampvdispar)
fprintf(stderr, " Vertical disparity builds:\n"
" (min,max,abs-diff) line curvature = (%d,%d,%d)\n",
dew->mincurv, dew->maxcurv, dew->maxcurv - dew->mincurv);
if (dew->samphdispar)
fprintf(stderr, " Horizontal disparity builds:\n"
" left edge slope = %d, right edge slope = %d\n"
" (left,right,abs-diff) edge curvature = (%d,%d,%d)\n",
dew->leftslope, dew->rightslope, dew->leftcurv,
dew->rightcurv, L_ABS(dew->leftcurv - dew->rightcurv));
}
return 0;
}
/*!
* \brief dewarpaModelStats()
*
* \param[in] dewa
* \param[out] pnnone [optional] number without any model
* \param[out] pnvsuccess [optional] number with a vert model
* \param[out] pnvvalid [optional] number with a valid vert model
* \param[out] pnhsuccess [optional] number with both models
* \param[out] pnhvalid [optional] number with both models valid
* \param[out] pnref [optional] number with a reference model
* \return 0 if OK, 1 on error
*
* <pre>
* Notes:
* (1) A page without a model has no dew. It most likely failed to
* generate a vertical model, and has not been assigned a ref
* model from a neighboring page with a valid vertical model.
* (2) A page has vsuccess == 1 if there is at least a model of the
* vertical disparity. The model may be invalid, in which case
* dewarpaInsertRefModels() will stash it in the cache and
* attempt to replace it by a valid ref model.
* (3) A vvvalid model is a vertical disparity model whose parameters
* satisfy the constraints given in dewarpaSetValidModels().
* (4) A page has hsuccess == 1 if both the vertical and horizontal
* disparity arrays have been constructed.
* (5) An hvalid model has vertical and horizontal disparity
* models whose parameters satisfy the constraints given
* in dewarpaSetValidModels().
* (6) A page has a ref model if it failed to generate a valid
* model but was assigned a vvalid or hvalid model on another
* page (within maxdist) by dewarpaInsertRefModel().
* (7) This calls dewarpaTestForValidModel(); it ignores the vvalid
* and hvalid fields.
* </pre>
*/
l_ok
dewarpaModelStats(L_DEWARPA *dewa,
l_int32 *pnnone,
l_int32 *pnvsuccess,
l_int32 *pnvvalid,
l_int32 *pnhsuccess,
l_int32 *pnhvalid,
l_int32 *pnref)
{
l_int32 i, n, pageno, nnone, nvsuccess, nvvalid, nhsuccess, nhvalid, nref;
L_DEWARP *dew;
PROCNAME("dewarpaModelStats");
if (!dewa)
return ERROR_INT("dewa not defined", procName, 1);
dewarpaListPages(dewa);
n = numaGetCount(dewa->napages);
nnone = nref = nvsuccess = nvvalid = nhsuccess = nhvalid = 0;
for (i = 0; i < n; i++) {
numaGetIValue(dewa->napages, i, &pageno);
dew = dewarpaGetDewarp(dewa, pageno);
if (!dew) {
nnone++;
continue;
}
if (dew->hasref == 1)
nref++;
if (dew->vsuccess == 1)
nvsuccess++;
if (dew->hsuccess == 1)
nhsuccess++;
dewarpaTestForValidModel(dewa, dew, 0);
if (dew->vvalid == 1)
nvvalid++;
if (dew->hvalid == 1)
nhvalid++;
}
if (pnnone) *pnnone = nnone;
if (pnref) *pnref = nref;
if (pnvsuccess) *pnvsuccess = nvsuccess;
if (pnvvalid) *pnvvalid = nvvalid;
if (pnhsuccess) *pnhsuccess = nhsuccess;
if (pnhvalid) *pnhvalid = nhvalid;
return 0;
}
/*!
* \brief dewarpaTestForValidModel()
*
* \param[in] dewa
* \param[in] dew
* \param[in] notests
* \return 0 if OK, 1 on error
*
* <pre>
* Notes:
* (1) Computes validity of vertical (vvalid) model and both
* vertical and horizontal (hvalid) models.
* (2) If %notests == 1, this ignores the curvature constraints
* and assumes that all successfully built models are valid.
* (3) This is just about the models, not the rendering process,
* so the value of useboth is not considered here.
* </pre>
*/
static l_int32
dewarpaTestForValidModel(L_DEWARPA *dewa,
L_DEWARP *dew,
l_int32 notests)
{
l_int32 maxcurv, diffcurv, diffedge;
PROCNAME("dewarpaTestForValidModel");
if (!dewa || !dew)
return ERROR_INT("dewa and dew not both defined", procName, 1);
if (notests) {
dew->vvalid = dew->vsuccess;
dew->hvalid = dew->hsuccess;
return 0;
}
/* No actual model was built */
if (dew->vsuccess == 0) return 0;
/* Was previously found not to have a valid model */
if (dew->hasref == 1) return 0;
/* vsuccess == 1; a vertical (line) model exists.
* First test that the vertical curvatures are within allowed
* bounds. Note that all curvatures are signed.*/
maxcurv = L_MAX(L_ABS(dew->mincurv), L_ABS(dew->maxcurv));
diffcurv = dew->maxcurv - dew->mincurv;
if (maxcurv <= dewa->max_linecurv &&
diffcurv >= dewa->min_diff_linecurv &&
diffcurv <= dewa->max_diff_linecurv) {
dew->vvalid = 1;
} else {
L_INFO("invalid vert model for page %d:\n", procName, dew->pageno);
#if DEBUG_INVALID_MODELS
fprintf(stderr, " max line curv = %d, max allowed = %d\n",
maxcurv, dewa->max_linecurv);
fprintf(stderr, " diff line curv = %d, max allowed = %d\n",
diffcurv, dewa->max_diff_linecurv);
#endif /* DEBUG_INVALID_MODELS */
}
/* If a horizontal (edge) model exists, test for validity. */
if (dew->hsuccess) {
diffedge = L_ABS(dew->leftcurv - dew->rightcurv);
if (L_ABS(dew->leftslope) <= dewa->max_edgeslope &&
L_ABS(dew->rightslope) <= dewa->max_edgeslope &&
L_ABS(dew->leftcurv) <= dewa->max_edgecurv &&
L_ABS(dew->rightcurv) <= dewa->max_edgecurv &&
diffedge <= dewa->max_diff_edgecurv) {
dew->hvalid = 1;
} else {
L_INFO("invalid horiz model for page %d:\n", procName, dew->pageno);
#if DEBUG_INVALID_MODELS
fprintf(stderr, " left edge slope = %d, max allowed = %d\n",
dew->leftslope, dewa->max_edgeslope);
fprintf(stderr, " right edge slope = %d, max allowed = %d\n",
dew->rightslope, dewa->max_edgeslope);
fprintf(stderr, " left edge curv = %d, max allowed = %d\n",
dew->leftcurv, dewa->max_edgecurv);
fprintf(stderr, " right edge curv = %d, max allowed = %d\n",
dew->rightcurv, dewa->max_edgecurv);
fprintf(stderr, " diff edge curv = %d, max allowed = %d\n",
diffedge, dewa->max_diff_edgecurv);
#endif /* DEBUG_INVALID_MODELS */
}
}
return 0;
}
/*!
* \brief dewarpaShowArrays()
*
* \param[in] dewa
* \param[in] scalefact on contour images; typ. 0.5
* \param[in] first first page model to render
* \param[in] last last page model to render; use 0 to go to end
* \return 0 if OK, 1 on error
*
* <pre>
* Notes:
* (1) Generates a pdf of contour plots of the disparity arrays.
* (2) This only shows actual models; not ref models
* </pre>
*/
l_ok
dewarpaShowArrays(L_DEWARPA *dewa,
l_float32 scalefact,
l_int32 first,
l_int32 last)
{
char buf[256];
l_int32 i, svd, shd;
L_BMF *bmf;
L_DEWARP *dew;
PIX *pixv, *pixvs, *pixh, *pixhs, *pixt, *pixd;
PIXA *pixa;
PROCNAME("dewarpaShowArrays");
if (!dewa)
return ERROR_INT("dew not defined", procName, 1);
if (first < 0 || first > dewa->maxpage)
return ERROR_INT("first out of bounds", procName, 1);
if (last <= 0 || last > dewa->maxpage) last = dewa->maxpage;
if (last < first)
return ERROR_INT("last < first", procName, 1);
lept_rmdir("lept/dewarp1"); /* temp directory for contour plots */
lept_mkdir("lept/dewarp1");
if ((bmf = bmfCreate(NULL, 8)) == NULL)
L_ERROR("bmf not made; page info not displayed", procName);
fprintf(stderr, "Generating contour plots\n");
for (i = first; i <= last; i++) {
if (i && ((i % 10) == 0))
fprintf(stderr, " .. %d", i);
dew = dewarpaGetDewarp(dewa, i);
if (!dew) continue;
if (dew->hasref == 1) continue;
svd = shd = 0;
if (dew->sampvdispar) svd = 1;
if (dew->samphdispar) shd = 1;
if (!svd) {
L_ERROR("sampvdispar not made for page %d!\n", procName, i);
continue;
}
/* Generate contour plots at reduced resolution */
dewarpPopulateFullRes(dew, NULL, 0, 0);
pixv = fpixRenderContours(dew->fullvdispar, 3.0, 0.15);
pixvs = pixScaleBySampling(pixv, scalefact, scalefact);
pixDestroy(&pixv);
if (shd) {
pixh = fpixRenderContours(dew->fullhdispar, 3.0, 0.15);
pixhs = pixScaleBySampling(pixh, scalefact, scalefact);
pixDestroy(&pixh);
}
dewarpMinimize(dew);
/* Save side-by-side */
pixa = pixaCreate(2);
pixaAddPix(pixa, pixvs, L_INSERT);
if (shd)
pixaAddPix(pixa, pixhs, L_INSERT);
pixt = pixaDisplayTiledInRows(pixa, 32, 1500, 1.0, 0, 30, 2);
snprintf(buf, sizeof(buf), "Page %d", i);
pixd = pixAddSingleTextblock(pixt, bmf, buf, 0x0000ff00,
L_ADD_BELOW, NULL);
snprintf(buf, sizeof(buf), "/tmp/lept/dewarp1/arrays_%04d.png", i);
pixWriteDebug(buf, pixd, IFF_PNG);
pixaDestroy(&pixa);
pixDestroy(&pixt);
pixDestroy(&pixd);
}
bmfDestroy(&bmf);
fprintf(stderr, "\n");
fprintf(stderr, "Generating pdf of contour plots\n");
convertFilesToPdf("/tmp/lept/dewarp1", "arrays_", 90, 1.0, L_FLATE_ENCODE,
0, "Disparity arrays", "/tmp/lept/disparity_arrays.pdf");
fprintf(stderr, "Output written to: /tmp/lept/disparity_arrays.pdf\n");
return 0;
}
/*!
* \brief dewarpDebug()
*
* \param[in] dew
* \param[in] subdirs one or more subdirectories of /tmp; e.g., "dew1"
* \param[in] index to help label output images; e.g., the page number
* \return 0 if OK, 1 on error
*
* <pre>
* Notes:
* (1) Prints dewarp fields and generates disparity array contour images.
* The contour images are written to file:
* /tmp/[subdirs]/pixv_[index].png
* </pre>
*/
l_ok
dewarpDebug(L_DEWARP *dew,
const char *subdirs,
l_int32 index)
{
char fname[256];
char *outdir;
l_int32 svd, shd;
PIX *pixv, *pixh;
PROCNAME("dewarpDebug");
if (!dew)
return ERROR_INT("dew not defined", procName, 1);
if (!subdirs)
return ERROR_INT("subdirs not defined", procName, 1);
fprintf(stderr, "pageno = %d, hasref = %d, refpage = %d\n",
dew->pageno, dew->hasref, dew->refpage);
fprintf(stderr, "sampling = %d, redfactor = %d, minlines = %d\n",
dew->sampling, dew->redfactor, dew->minlines);
svd = shd = 0;
if (!dew->hasref) {
if (dew->sampvdispar) svd = 1;
if (dew->samphdispar) shd = 1;
fprintf(stderr, "sampv = %d, samph = %d\n", svd, shd);
fprintf(stderr, "w = %d, h = %d\n", dew->w, dew->h);
fprintf(stderr, "nx = %d, ny = %d\n", dew->nx, dew->ny);
fprintf(stderr, "nlines = %d\n", dew->nlines);
if (svd) {
fprintf(stderr, "(min,max,abs-diff) line curvature = (%d,%d,%d)\n",
dew->mincurv, dew->maxcurv, dew->maxcurv - dew->mincurv);
}
if (shd) {
fprintf(stderr, "(left edge slope = %d, right edge slope = %d\n",
dew->leftslope, dew->rightslope);
fprintf(stderr, "(left,right,abs-diff) edge curvature = "
"(%d,%d,%d)\n", dew->leftcurv, dew->rightcurv,
L_ABS(dew->leftcurv - dew->rightcurv));
}
}
if (!svd && !shd) {
fprintf(stderr, "No disparity arrays\n");
return 0;
}
dewarpPopulateFullRes(dew, NULL, 0, 0);
lept_mkdir(subdirs);
outdir = pathJoin("/tmp", subdirs);
if (svd) {
pixv = fpixRenderContours(dew->fullvdispar, 3.0, 0.15);
snprintf(fname, sizeof(fname), "%s/pixv_%d.png", outdir, index);
pixWriteDebug(fname, pixv, IFF_PNG);
pixDestroy(&pixv);
}
if (shd) {
pixh = fpixRenderContours(dew->fullhdispar, 3.0, 0.15);
snprintf(fname, sizeof(fname), "%s/pixh_%d.png", outdir, index);
pixWriteDebug(fname, pixh, IFF_PNG);
pixDestroy(&pixh);
}
LEPT_FREE(outdir);
return 0;
}
/*!
* \brief dewarpShowResults()
*
* \param[in] dewa
* \param[in] sa of indexed input images
* \param[in] boxa crop boxes for input images; can be null
* \param[in] firstpage
* \param[in] lastpage
* \param[in] pdfout filename
* \return 0 if OK, 1 on error
*
* <pre>
* Notes:
* (1) This generates a pdf of image pairs (before, after) for
* the designated set of input pages.
* (2) If the boxa exists, its elements are aligned with numbers
* in the filenames in %sa. It is used to crop the input images.
* It is assumed that the dewa was generated from the cropped
* images. No undercropping is applied before rendering.
* </pre>
*/
l_ok
dewarpShowResults(L_DEWARPA *dewa,
SARRAY *sa,
BOXA *boxa,
l_int32 firstpage,
l_int32 lastpage,
const char *pdfout)
{
char bufstr[256];
l_int32 i, modelpage;
L_BMF *bmf;
BOX *box;
L_DEWARP *dew;
PIX *pixs, *pixc, *pixd, *pixt1, *pixt2;
PIXA *pixa;
PROCNAME("dewarpShowResults");
if (!dewa)
return ERROR_INT("dewa not defined", procName, 1);
if (!sa)
return ERROR_INT("sa not defined", procName, 1);
if (!pdfout)
return ERROR_INT("pdfout not defined", procName, 1);
if (firstpage > lastpage)
return ERROR_INT("invalid first/last page numbers", procName, 1);
lept_rmdir("lept/dewarp_pdfout");
lept_mkdir("lept/dewarp_pdfout");
bmf = bmfCreate(NULL, 6);
fprintf(stderr, "Dewarping and generating s/by/s view\n");
for (i = firstpage; i <= lastpage; i++) {
if (i && (i % 10 == 0)) fprintf(stderr, ".. %d ", i);
pixs = pixReadIndexed(sa, i);
if (boxa) {
box = boxaGetBox(boxa, i, L_CLONE);
pixc = pixClipRectangle(pixs, box, NULL);
boxDestroy(&box);
}
else
pixc = pixClone(pixs);
dew = dewarpaGetDewarp(dewa, i);
pixd = NULL;
if (dew) {
dewarpaApplyDisparity(dewa, dew->pageno, pixc,
GrayInValue, 0, 0, &pixd, NULL);
dewarpMinimize(dew);
}
pixa = pixaCreate(2);
pixaAddPix(pixa, pixc, L_INSERT);
if (pixd)
pixaAddPix(pixa, pixd, L_INSERT);
pixt1 = pixaDisplayTiledAndScaled(pixa, 32, 500, 2, 0, 35, 2);
if (dew) {
modelpage = (dew->hasref) ? dew->refpage : dew->pageno;
snprintf(bufstr, sizeof(bufstr), "Page %d; using %d\n",
i, modelpage);
}
else
snprintf(bufstr, sizeof(bufstr), "Page %d; no dewarp\n", i);
pixt2 = pixAddSingleTextblock(pixt1, bmf, bufstr, 0x0000ff00,
L_ADD_BELOW, 0);
snprintf(bufstr, sizeof(bufstr), "/tmp/lept/dewarp_pdfout/%05d", i);
pixWriteDebug(bufstr, pixt2, IFF_JFIF_JPEG);
pixaDestroy(&pixa);
pixDestroy(&pixs);
pixDestroy(&pixt1);
pixDestroy(&pixt2);
}
fprintf(stderr, "\n");
fprintf(stderr, "Generating pdf of result\n");
convertFilesToPdf("/tmp/lept/dewarp_pdfout", NULL, 100, 1.0, L_JPEG_ENCODE,
0, "Dewarp sequence", pdfout);
fprintf(stderr, "Output written to: %s\n", pdfout);
bmfDestroy(&bmf);
return 0;
}