Leptonica 1.68
C Image Processing Library
|
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