Leptonica 1.68
C Image Processing Library

regutils.c

Go to the documentation of this file.
00001 /*====================================================================*
00002  -  Copyright (C) 2001 Leptonica.  All rights reserved.
00003  -  This software is distributed in the hope that it will be
00004  -  useful, but with NO WARRANTY OF ANY KIND.
00005  -  No author or distributor accepts responsibility to anyone for the
00006  -  consequences of using this software, or for whether it serves any
00007  -  particular purpose or works at all, unless he or she says so in
00008  -  writing.  Everyone is granted permission to copy, modify and
00009  -  redistribute this source code, for commercial or non-commercial
00010  -  purposes, with the following restrictions: (1) the origin of this
00011  -  source code must not be misrepresented; (2) modified versions must
00012  -  be plainly marked as such; and (3) this notice may not be removed
00013  -  or altered from any source or modified source distribution.
00014  *====================================================================*/
00015 
00016 
00017 /*
00018  *  regutils.c
00019  *
00020  *       Regression test utilities
00021  *           l_int32    regTestSetup()
00022  *           l_int32    regTestCleanup()
00023  *           l_int32    regTestComparePix()
00024  *           l_int32    regTestCompareSimilarPix()
00025  *           l_int32    regTestCheckFile()
00026  *           l_int32    regTestCompareFiles()
00027  *           l_int32    regTestWritePixAndCheck()
00028  *
00029  *       Static function
00030  *           char      *getRootNameFromArgv0()
00031  */
00032 
00033 #include <string.h>
00034 #include "allheaders.h"
00035 
00036 
00037 extern l_int32 NumImageFileFormatExtensions;
00038 extern const char *ImageFileFormatExtensions[];
00039 
00040 static char *getRootNameFromArgv0(const char *argv0);
00041 
00042 
00043 /*--------------------------------------------------------------------*
00044  *                      Regression test utilities                     *
00045  *--------------------------------------------------------------------*/
00046 /*!
00047  *  regTestSetup()
00048  *
00049  *      Input:  argc (from invocation; can be either 1 or 2)
00050  *              argv (to regtest: @argv[1] is one of these:
00051  *                    "generate", "compare", "display")
00052  *              &rp (<return> all regression params)
00053  *      Return: 0 if OK, 1 on error
00054  *
00055  *  Notes:
00056  *      (1) Call this function with the args to the reg test.
00057  *          There are three cases:
00058  *          Case 1:
00059  *              The second arg is "generate".  This will cause
00060  *              generation of new golden files for the reg test.
00061  *              The results of the reg test are not recorded, and
00062  *              the display field is set to FALSE, preventing image display.
00063  *          Case 2:
00064  *              The second arg is "compare".  This is the mode in which
00065  *              you run a regression test (or a set of them), looking
00066  *              for failures and logging the results to a file.
00067  *              The output, which includes logging of all reg test
00068  *              failures plus a SUCCESS or FAILURE summary for each test,
00069  *              is appended to the file "/tmp/reg_results.txt.  For this
00070  *              case, as in Case 1, the display field in rp is set to FALSE.
00071  *          Case 3:
00072  *              There is either only arg, or the  second arg is "display".
00073  *              The test will run and files will be written.  Comparisons
00074  *              with golden files will not be carried out, so the only
00075  *              notion of success or failure is with tests that do not
00076  *              involve golden files.  The display field in rp is TRUE,
00077  *              and this is used by pixDisplayWithTitle().
00078  *      (2) See regutils.h for examples of usage.
00079  */
00080 l_int32
00081 regTestSetup(l_int32        argc,
00082              char         **argv,
00083              L_REGPARAMS  **prp)
00084 {
00085 char         *testname, *vers;
00086 char          errormsg[64];
00087 L_REGPARAMS  *rp;
00088 
00089     PROCNAME("regTestSetup");
00090 
00091     if (argc != 1 && argc != 2) {
00092         snprintf(errormsg, sizeof(errormsg),
00093             "Syntax: %s [generate | compare | [display]]", argv[0]);
00094         return ERROR_INT(errormsg, procName, 1);
00095     }
00096 
00097     if ((testname = getRootNameFromArgv0(argv[0])) == NULL)
00098         return ERROR_INT("invalid root", procName, 1);
00099 
00100     if ((rp = (L_REGPARAMS *)CALLOC(1, sizeof(L_REGPARAMS))) == NULL)
00101         return ERROR_INT("rp not made", procName, 1);
00102     *prp = rp;
00103     rp->testname = testname;
00104     rp->index = -1;  /* increment before each test */
00105 
00106         /* Initialize to true.  A failure in any test is registered
00107          * as a failure of the regression test. */
00108     rp->success = TRUE;
00109 
00110         /* Only open a stream to a temp file for the 'compare' case */
00111     if (argc == 1 || !strcmp(argv[1], "display")) {
00112         rp->mode = L_REG_DISPLAY;
00113         rp->display = TRUE;
00114     }
00115     else if (!strcmp(argv[1], "compare")) {
00116         rp->mode = L_REG_COMPARE;
00117         rp->tempfile = genTempFilename("/tmp", "regtest_output.txt", 0, 1);
00118         rp->fp = fopenWriteStream(rp->tempfile, "wb");
00119         if (rp->fp == NULL) {
00120             rp->success = FALSE;
00121             return ERROR_INT("stream not opened for tempfile", procName, 1);
00122         }
00123     }
00124     else if (!strcmp(argv[1], "generate")) {
00125         rp->mode = L_REG_GENERATE;
00126         lept_mkdir("golden");
00127     }
00128     else {
00129         FREE(rp);
00130         snprintf(errormsg, sizeof(errormsg),
00131             "Syntax: %s [generate | compare | [display]]", argv[0]);
00132         return ERROR_INT(errormsg, procName, 1);
00133     }
00134 
00135         /* Print out test name and both the leptonica and
00136          * image libarary versions */
00137     fprintf(stderr, "\n################   %s_reg   ###############\n",
00138             rp->testname);
00139     vers = getLeptonicaVersion();
00140     fprintf(stderr, "%s\n", vers);
00141     FREE(vers);
00142     vers = getImagelibVersions();
00143     fprintf(stderr, "%s\n", vers);
00144     FREE(vers);
00145 
00146     rp->tstart = startTimerNested();
00147     return 0;
00148 }
00149 
00150 
00151 /*!
00152  *  regTestCleanup()
00153  *
00154  *      Input:  rp (regression test parameters)
00155  *      Return: 0 if OK, 1 on error
00156  *
00157  *  Notes:
00158  *      (1) This copies anything written to the temporary file to the
00159  *          output file /tmp/reg_results.txt.
00160  */
00161 l_int32
00162 regTestCleanup(L_REGPARAMS  *rp)
00163 {
00164 char    result[512];
00165 char   *results_file;  /* success/failure output in 'compare' mode */
00166 char   *text, *message;
00167 size_t  nbytes;
00168 
00169     PROCNAME("regTestCleanup");
00170 
00171     if (!rp)
00172         return ERROR_INT("rp not defined", procName, 1);
00173 
00174     fprintf(stderr, "Time: %7.3f sec\n", stopTimerNested(rp->tstart));
00175     fprintf(stderr, "################################################\n");
00176 
00177         /* If generating golden files or running in display mode, release rp */
00178     if (!rp->fp) {
00179         FREE(rp->testname);
00180         FREE(rp->tempfile);
00181         FREE(rp);
00182         return 0;
00183     }
00184 
00185         /* Compare mode: read back data from temp file */
00186     fclose(rp->fp);
00187     text = (char *)l_binaryRead(rp->tempfile, &nbytes);
00188     FREE(rp->tempfile);
00189     if (!text) {
00190         rp->success = FALSE;
00191         FREE(rp);
00192         return ERROR_INT("text not returned", procName, 1);
00193     }
00194 
00195         /* Prepare result message */
00196     if (rp->success)
00197         snprintf(result, sizeof(result), "SUCCESS: %s_reg\n", rp->testname);
00198     else
00199         snprintf(result, sizeof(result), "FAILURE: %s_reg\n", rp->testname);
00200     message = stringJoin(text, result);
00201     FREE(text);
00202     results_file = genPathname("/tmp", "reg_results.txt");
00203     fileAppendString(results_file, message);
00204     FREE(results_file);
00205     FREE(message);
00206 
00207     FREE(rp->testname);
00208     FREE(rp);
00209     return 0;
00210 }
00211 
00212 
00213 /*!
00214  *  regTestComparePix()
00215  *
00216  *      Input:  rp (regtest parameters)
00217  *              pix1, pix2 (to be tested for equality)
00218  *      Return: 0 if OK, 1 on error (a failure in comparison is not an error)
00219  *
00220  *  Notes:
00221  *      (1) This function compares two pix for equality.  If not in compare
00222  *          mode, on failure it writes to stderr.
00223  */
00224 l_int32
00225 regTestComparePix(L_REGPARAMS  *rp,
00226                   PIX          *pix1,
00227                   PIX          *pix2)
00228 {
00229 l_int32  same;
00230 
00231     PROCNAME("regTestComparePix");
00232 
00233     if (!rp)
00234         return ERROR_INT("rp not defined", procName, 1);
00235     if (!pix1 || !pix2) {
00236         rp->success = FALSE;
00237         return ERROR_INT("pix1 and pix2 not both defined", procName, 1);
00238     }
00239 
00240     rp->index++;
00241     pixEqual(pix1, pix2, &same);
00242 
00243         /* Record on failure */
00244     if (!same) {
00245         if (rp->fp) {
00246             fprintf(rp->fp, "Failure in %s_reg: pix comparison for index %d\n",
00247                     rp->testname, rp->index);
00248         }
00249         fprintf(stderr, "Failure in %s_reg: pix comparison for index %d\n",
00250                 rp->testname, rp->index);
00251         rp->success = FALSE;
00252     }
00253     return 0;
00254 }
00255 
00256 
00257 /*!
00258  *  regTestCompareSimilarPix()
00259  *
00260  *      Input:  rp (regtest parameters)
00261  *              pix1, pix2 (to be tested for equality)
00262  *              mindiff (minimum pixel difference to be counted; > 0)
00263  *              maxfract (maximum fraction of pixels allowed to have
00264  *                        diff greater than or equal to mindiff)
00265  *              printstats (use 1 to print normalized histogram to stderr)
00266  *      Return: 0 if OK, 1 on error (a failure in similarity comparison
00267  *              is not an error)
00268  *
00269  *  Notes:
00270  *      (1) This function compares two pix for equality.  If not in compare
00271  *          mode, on failure it writes to stderr.
00272  *      (2) To identify two images as 'similar', select @maxfract to be
00273  *          the upper bound for what you expect.  Typical values might
00274  *          be @mindiff = 15 and @maxfract = 0.01.
00275  *      (3) Normally, use @printstats = 0.  In debugging mode, to see
00276  *          the relation between @mindiff and the minimum value of
00277  *          @maxfract for success, set this to 1.
00278  */
00279 l_int32
00280 regTestCompareSimilarPix(L_REGPARAMS  *rp,
00281                          PIX          *pix1,
00282                          PIX          *pix2,
00283                          l_int32       mindiff,
00284                          l_float32     maxfract,
00285                          l_int32       printstats)
00286 {
00287 l_int32  w, h, factor, similar;
00288 
00289     PROCNAME("regTestCompareSimilarPix");
00290 
00291     if (!rp)
00292         return ERROR_INT("rp not defined", procName, 1);
00293     if (!pix1 || !pix2) {
00294         rp->success = FALSE;
00295         return ERROR_INT("pix1 and pix2 not both defined", procName, 1);
00296     }
00297 
00298     rp->index++;
00299     pixGetDimensions(pix1, &w, &h, NULL);
00300     factor = L_MAX(w, h) / 400;
00301     factor = L_MAX(1, L_MIN(factor, 4));   /* between 1 and 4 */
00302     pixTestForSimilarity(pix1, pix2, factor, mindiff, maxfract, 0.0,
00303                          &similar, printstats);
00304 
00305         /* Record on failure */
00306     if (!similar) {
00307         if (rp->fp) {
00308             fprintf(rp->fp,
00309                     "Failure in %s_reg: pix similarity comp for index %d\n",
00310                     rp->testname, rp->index);
00311         }
00312         fprintf(stderr, "Failure in %s_reg: pix similarity comp for index %d\n",
00313                 rp->testname, rp->index);
00314         rp->success = FALSE;
00315     }
00316     return 0;
00317 }
00318 
00319 
00320 /*!
00321  *  regTestCheckFile()
00322  *
00323  *      Input:  rp (regtest parameters)
00324  *              localname (name of output file from reg test)
00325  *      Return: 0 if OK, 1 on error (a failure in comparison is not an error)
00326  *
00327  *  Notes:
00328  *      (1) This function does one of three things, depending on the mode:
00329  *           * "generate": makes a "golden" file as a copy @localname.
00330  *           * "compare": compares @localname contents with the golden file
00331  *           * "display": makes the @localname file but does no comparison
00332  *      (2) The canonical format of the golden filenames is:
00333  *            /tmp/golden/<root of main name>_golden.<index>.<ext of localname>
00334  *          e.g.,
00335  *             /tmp/golden/maze_golden.0.png
00336  *          It is important to add an extension to the local name, because
00337  *          the extension is added to the name of the golden file.
00338  */
00339 l_int32
00340 regTestCheckFile(L_REGPARAMS  *rp,
00341                  const char   *localname)
00342 {
00343 char    *ext;
00344 char     namebuf[256];
00345 l_int32  ret, same;
00346 
00347     PROCNAME("regTestCheckFile");
00348 
00349     if (!rp)
00350         return ERROR_INT("rp not defined", procName, 1);
00351     if (!localname) {
00352         rp->success = FALSE;
00353         return ERROR_INT("local name not defined", procName, 1);
00354     }
00355     if (rp->mode != L_REG_GENERATE && rp->mode != L_REG_COMPARE &&
00356         rp->mode != L_REG_DISPLAY) {
00357         rp->success = FALSE;
00358         return ERROR_INT("invalid mode", procName, 1);
00359     }
00360     rp->index++;
00361 
00362     if (rp->mode == L_REG_DISPLAY) return 0;
00363 
00364         /* Generate the golden file name; used in 'generate' and 'compare' */
00365     splitPathAtExtension(localname, NULL, &ext);
00366     snprintf(namebuf, sizeof(namebuf), "/tmp/golden/%s_golden.%d%s",
00367              rp->testname, rp->index, ext);
00368     FREE(ext);
00369 
00370     if (rp->mode == L_REG_GENERATE) {
00371             /* Save the file as a golden file */
00372 /*        fprintf(stderr, "%d: %s\n", rp->index, namebuf);  */
00373         ret = fileCopy(localname, namebuf);
00374         if (!ret)
00375             fprintf(stderr, "Copy: %s to %s\n", localname, namebuf);
00376         return ret;
00377     }
00378 
00379         /* Compare mode: test and record on failure */
00380     filesAreIdentical(localname, namebuf, &same);
00381     if (!same) {
00382         fprintf(rp->fp, "Failure in %s_reg, index %d: comparing %s with %s\n",
00383                 rp->testname, rp->index, localname, namebuf);
00384         fprintf(stderr, "Failure in %s_reg, index %d: comparing %s with %s\n",
00385                 rp->testname, rp->index, localname, namebuf);
00386         rp->success = FALSE;
00387     }
00388 
00389     return 0;
00390 }
00391 
00392 
00393 /*!
00394  *  regTestCompareFiles()
00395  *
00396  *      Input:  rp (regtest parameters)
00397  *              index1 (of one output file from reg test)
00398  *              index2 (of another output file from reg test)
00399  *      Return: 0 if OK, 1 on error (a failure in comparison is not an error)
00400  *
00401  *  Notes:
00402  *      (1) This only does something in "compare" mode.
00403  *      (2) The canonical format of the golden filenames is:
00404  *            /tmp/golden/<root of main name>_golden.<index>.<ext of localname>
00405  *          e.g.,
00406  *            /tmp/golden/maze_golden.0.png
00407  */
00408 l_int32
00409 regTestCompareFiles(L_REGPARAMS  *rp,
00410                     l_int32       index1,
00411                     l_int32       index2)
00412 {
00413 char    *name1, *name2;
00414 char     namebuf[256];
00415 l_int32  same;
00416 SARRAY  *sa;
00417 
00418     PROCNAME("regTestCompareFiles");
00419 
00420     if (!rp)
00421         return ERROR_INT("rp not defined", procName, 1);
00422     if (index1 < 0 || index2 < 0) {
00423         rp->success = FALSE;
00424         return ERROR_INT("index1 and/or index2 is negative", procName, 1);
00425     }
00426     if (index1 == index2) {
00427         rp->success = FALSE;
00428         return ERROR_INT("index1 must differ from index2", procName, 1);
00429     }
00430 
00431     rp->index++;
00432     if (rp->mode != L_REG_COMPARE) return 0;
00433 
00434         /* Generate the golden file names */
00435     snprintf(namebuf, sizeof(namebuf), "%s_golden.%d.", rp->testname, index1);
00436     sa = getSortedPathnamesInDirectory("/tmp/golden", namebuf, 0, 0);
00437     if (sarrayGetCount(sa) != 1) {
00438         sarrayDestroy(&sa);
00439         rp->success = FALSE;
00440         L_ERROR_STRING("golden file %s not found", procName, namebuf);
00441         return 1;
00442     }
00443     name1 = sarrayGetString(sa, 0, L_COPY);
00444     sarrayDestroy(&sa);
00445 
00446     snprintf(namebuf, sizeof(namebuf), "%s_golden.%d.", rp->testname, index2);
00447     sa = getSortedPathnamesInDirectory("/tmp/golden", namebuf, 0, 0);
00448     if (sarrayGetCount(sa) != 1) {
00449         sarrayDestroy(&sa);
00450         rp->success = FALSE;
00451         FREE(name1);
00452         L_ERROR_STRING("golden file %s not found", procName, namebuf);
00453         return 1;
00454     }
00455     name2 = sarrayGetString(sa, 0, L_COPY);
00456     sarrayDestroy(&sa);
00457 
00458         /* Test and record on failure */
00459     filesAreIdentical(name1, name2, &same);
00460     if (!same) {
00461         fprintf(rp->fp,
00462                 "Failure in %s_reg, index %d: comparing %s with %s\n",
00463                 rp->testname, rp->index, name1, name2);
00464         fprintf(stderr,
00465                 "Failure in %s_reg, index %d: comparing %s with %s\n",
00466                 rp->testname, rp->index, name1, name2);
00467         rp->success = FALSE;
00468     }
00469 
00470     FREE(name1);
00471     FREE(name2);
00472     return 0;
00473 }
00474 
00475 
00476 /*!
00477  *  regTestWritePixAndCheck()
00478  *
00479  *      Input:  rp (regtest parameters)
00480  *              pix (to be written)
00481  *              format (of output pix)
00482  *      Return: 0 if OK, 1 on error (a failure in comparison is not an error)
00483  *
00484  *  Notes:
00485  *      (1) This function makes it easy to write the pix in a numbered
00486  *          sequence of files, and either to:
00487  *             (a) write the golden file ("generate" arg to regression test)
00488  *             (b) make a local file and "compare" with the golden file
00489  *             (c) make a local file and "display" the results
00490  *      (3) The canonical format of the local filename is:
00491  *            /tmp/<root of main name>.<count>.<format extension string>
00492  *          e.g., for scale_reg,
00493  *            /tmp/scale.0.png
00494  */
00495 l_int32
00496 regTestWritePixAndCheck(L_REGPARAMS  *rp,
00497                         PIX          *pix,
00498                         l_int32       format)
00499 {
00500 char   namebuf[256];
00501 
00502     PROCNAME("regTestWritePixAndCheck");
00503 
00504     if (!rp)
00505         return ERROR_INT("rp not defined", procName, 1);
00506     if (!pix) {
00507         rp->success = FALSE;
00508         return ERROR_INT("pix not defined", procName, 1);
00509     }
00510     if (format < 0 || format >= NumImageFileFormatExtensions) {
00511         rp->success = FALSE;
00512         return ERROR_INT("invalid format", procName, 1);
00513     }
00514 
00515         /* Generate the local file name */
00516     snprintf(namebuf, sizeof(namebuf), "/tmp/%s.%d.%s", rp->testname,
00517              rp->index + 1, ImageFileFormatExtensions[format]);
00518 
00519         /* Write the local file */
00520     pixWrite(namebuf, pix, format);
00521 
00522         /* Either write the golden file ("generate") or check the
00523            local file against an existing golden file ("compare") */
00524     regTestCheckFile(rp, namebuf);
00525 
00526     return 0;
00527 }
00528 
00529 
00530 /*!
00531  *  getRootNameFromArgv0()
00532  *
00533  *      Input:  argv0
00534  *      Return: root name (without the '_reg'), or null on error
00535  *
00536  *  Notes:
00537  *      (1) For example, from psioseg_reg, we want to extract
00538  *          just 'psioseg' as the root.
00539  *      (2) In unix with autotools, the executable is not X,
00540  *          but ./.libs/lt-X.   So in addition to stripping out the
00541  *          last 4 characters of the tail, we have to check for
00542  *          the '-' and strip out the "lt-" prefix if we find it.
00543  */
00544 static char *
00545 getRootNameFromArgv0(const char  *argv0)
00546 {
00547 l_int32  len;
00548 char    *root;
00549 
00550     PROCNAME("getRootNameFromArgv0");
00551 
00552     splitPathAtDirectory(argv0, NULL, &root);
00553     if ((len = strlen(root)) <= 4) {
00554         FREE(root);
00555         return (char *)ERROR_PTR("invalid argv0; too small", procName, NULL);
00556     }
00557 
00558 #ifndef _WIN32
00559     {
00560         char    *newroot;
00561         l_int32  loc;
00562         if (stringFindSubstr(root, "-", &loc)) {
00563             newroot = stringNew(root + loc + 1);  /* strip out "lt-" */
00564             FREE(root);
00565             root = newroot;
00566             len = strlen(root);
00567         }
00568     }
00569 #else
00570     if (strstr(root, ".exe") != NULL)
00571         len -= 4;
00572 #endif  /* ! _WIN32 */
00573 
00574     root[len - 4] = '\0';  /* remove the suffix */
00575     return root;
00576 }
00577 
00578 
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Defines