ultimatepp/bazaar/PixRaster/lib/regutils.c
micio a74e1468c9 PixRaster : updated Leptonica library to version 1.65
git-svn-id: svn://ultimatepp.org/upp/trunk@2598 f0d560ea-af0d-0410-9eb7-867de7ffcac7
2010-08-07 23:38:03 +00:00

563 lines
20 KiB
C

/*====================================================================*
- Copyright (C) 2001 Leptonica. All rights reserved.
- This software is distributed in the hope that it will be
- useful, but with NO WARRANTY OF ANY KIND.
- No author or distributor accepts responsibility to anyone for the
- consequences of using this software, or for whether it serves any
- particular purpose or works at all, unless he or she says so in
- writing. Everyone is granted permission to copy, modify and
- redistribute this source code, for commercial or non-commercial
- purposes, with the following restrictions: (1) the origin of this
- source code must not be misrepresented; (2) modified versions must
- be plainly marked as such; and (3) this notice may not be removed
- or altered from any source or modified source distribution.
*====================================================================*/
/*
* regutils.c
*
* Regression test utilities
* l_int32 regTestSetup()
* l_int32 regTestCleanup()
* l_int32 regTestComparePix()
* l_int32 regTestCompareSimilarPix()
* l_int32 regTestCheckFile()
* l_int32 regTestCompareFiles()
* l_int32 regTestWritePixAndCheck()
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "allheaders.h"
extern l_int32 NumImageFileFormatExtensions;
extern const char *ImageFileFormatExtensions[];
/*--------------------------------------------------------------------*
* Regression test utilities *
*--------------------------------------------------------------------*/
/*!
* regTestSetup()
*
* Input: argc (to regtest: either 1 or 2)
* argv (to regtest: if @argc == 2, @argv[1] is either
* "generate" or a log file name)
* &fp (<return> stream for writing to a temporary file,
* or null for the "generate" case)
* &display (<return> TRUE or FALSE for image display)
* &success (<return> initialize to TRUE)
* &rp (<optional return> all regression params for either
* passthrough to subroutines or for automated
* "write and check")
* Return: 0 if OK, 1 on error
*
* Notes:
* (1) Call this function with the args to the reg test.
* There are exactly three cases:
* Case 1:
* The second arg is "generate". This will cause
* generation of new golden files for the reg test.
* The results of the reg test are not recorded. Also,
* the returned @display == FALSE (prevents image display).
* Case 2:
* The second arg is the name of the output log file
* into which all reg test failures are put, along with
* a SUCCESS or FAILURE summary for each test. This is the
* mode in which you run a set of reg tests, looking for
* failures, and logging the results to a file. For this
* case, as in (b), the returned @display == FALSE.
* Case 3:
* There is no second arg to the reg test. The results
* (failures, summary) will go to stderr. The returned
* @display == TRUE; this is used with pixDisplayWithTitle().
* (2) If you wish to use regTestWritePixAndCheck(), you must
* let regTestSetup() make and return an @rp struct. It is also
* convenient to pass @rp into subroutines of the reg test.
* (3) See regutils.h for examples of usage.
*/
l_int32
regTestSetup(l_int32 argc,
char **argv,
FILE **pfp,
l_int32 *pdisplay,
l_int32 *psuccess,
L_REGPARAMS **prp)
{
char *tempname;
char errormsg[64];
L_REGPARAMS *rp;
PROCNAME("regTestSetup");
if (argc != 1 && argc != 2) {
snprintf(errormsg, sizeof(errormsg),
"Syntax: %s [generate | <logfile>]", argv[0]);
return ERROR_INT(errormsg, procName, 1);
}
if (prp) *prp = NULL;
if (!psuccess)
return ERROR_INT("&success not defined", procName, 1);
*psuccess = TRUE;
if (!pdisplay)
return ERROR_INT("&display not defined", procName, 1);
if (!pfp)
return ERROR_INT("&fp defined", procName, 1);
*pfp = NULL; /* default for "generate case */
*pdisplay = (argc == 1) ? TRUE : FALSE;
if (argc == 1 || strcmp(argv[1], "generate")) {
tempname = genTempFilename("/tmp", "junk_regtest_output", ".txt");
*pfp = fopen(tempname, "wb");
FREE(tempname);
if (*pfp == NULL)
return ERROR_INT("stream not opened", procName, 1);
}
if (prp) {
if ((rp = (L_REGPARAMS *)CALLOC(1, sizeof(L_REGPARAMS))) == NULL)
return ERROR_INT("rp not made", procName, 1);
*prp = rp;
rp->fp = *pfp;
rp->argv = argv;
rp->display = *pdisplay;
rp->success = *psuccess;
}
return 0;
}
/*!
* regTestCleanup()
*
* Input: argc (to regtest: either 1 or 2)
* argv (to regtest: if @argc == 2, @argv[1] is either
* "generate" or a log file name)
* fp (stream that was used writing to a temporary file;
* null for the "generate" case)
* success (overall for this reg test)
* rp (regression test params; can be null)
* Return: 0 if OK, 1 on error
*
* Notes:
* (1) This outputs anything written to the temporary file and
* closes the stream to that file.
* (2) If a rp struct is made in regTestSetup(), it must be
* passed in here for destruction.
*/
l_int32
regTestCleanup(l_int32 argc,
char **argv,
FILE *fp,
l_int32 success,
L_REGPARAMS *rp)
{
char result[128];
char *tempname, *text, *message;
l_int32 nbytes;
PROCNAME("regTestCleanup");
if (!fp) { /* for generating golden files; release rp if it exists */
if (rp) FREE(rp);
return 0;
}
fclose(fp);
/* Read back data from temp file */
tempname = genTempFilename("/tmp", "junk_regtest_output", ".txt");
text = (char *)arrayRead(tempname, &nbytes);
FREE(tempname);
if (!text) {
if (rp) FREE(rp);
return ERROR_INT("text not returned", procName, 1);
}
/* Prepare result message */
if (rp) /* if either is 0, success == FALSE */
success = rp->success && success;
if (success)
snprintf(result, sizeof(result), "SUCCESS: %s\n", argv[0]);
else
snprintf(result, sizeof(result), "FAILURE: %s\n", argv[0]);
message = stringJoin(text, result);
FREE(text);
if (argc == 1)
fprintf(stderr, message);
else
fileAppendString(argv[1], message);
FREE(message);
if (rp) FREE(rp);
return 0;
}
/*!
* regTestComparePix()
*
* Input: stream (for output; use NULL to generate golden files)
* argv ([0] == name of reg test)
* pix1, pix2 (to be tested for equality)
* index (of the pix pair test; 0-based for the reg test)
* &success (<return> 0 on failure; input value on success)
* Return: 0 if OK, 1 on error (a failure in comparison is not an error)
*
* Notes:
* (1) This function compares two pix for equality. When called
* with @fp == NULL, it records success or failure to stderr.
* Otherwise, it writes on failure to @fp.
* (2) This function can be called repeatedly in a single reg test.
* (3) The value for @success is initialized to TRUE in the reg test
* setup before this function is called for the first time.
* A failure in any pix comparison is registered as a failure
* of the regression test.
*/
l_int32
regTestComparePix(FILE *fp,
char **argv,
PIX *pix1,
PIX *pix2,
l_int32 index,
l_int32 *psuccess)
{
l_int32 same;
PROCNAME("regTestComparePix");
if (!psuccess)
return ERROR_INT("&success not defined", procName, 1);
if (index < 0)
return ERROR_INT("index is negative", procName, 1);
if (!pix1 || !pix2)
return ERROR_INT("pix1 and pix2 not both defined", procName, 1);
pixEqual(pix1, pix2, &same);
if (!fp) { /* just output result to stderr */
if (same)
fprintf(stderr, "%s: Success in comparison %d\n", argv[0], index);
else
fprintf(stderr, "Failure in comparison %d\n", index);
return 0;
}
/* Record on failure */
if (!same) {
fprintf(fp, "Failure in %s: pix comparison %d\n", argv[0], index);
fprintf(stderr, "Failure in %s: pix comparison %d\n", argv[0], index);
*psuccess = 0;
}
return 0;
}
/*!
* regTestCompareSimilarPix()
*
* Input: stream (for output; use NULL to generate golden files)
* argv ([0] == name of reg test)
* pix1, pix2 (to be tested for equality)
* mindiff (minimum pixel difference to be counted; > 0)
* maxfract (maximum fraction of pixels allowed to have
* diff greater than or equal to mindiff)
* index (of the pix pair test; 0-based for the reg test)
* &success (<return> 0 on failure; input value on success)
* printstats (use 1 to print normalized histogram to stderr)
* Return: 0 if OK, 1 on error (a failure in similarity comparison
* is not an error)
*
* Notes:
* (1) This function compares two pix for similarity, not equality.
* When called with @fp == NULL, it records success or failure
* to stderr. Otherwise, it writes on failure to @fp.
* (2) To identify two images as 'similar', select @maxfract to be
* the upper bound for what you expect. Typical values might
* be @mindiff = 15 and @maxfract = 0.01.
* (3) This function can be called repeatedly in a single reg test.
* (4) The value for @success is initialized to TRUE in the reg test
* setup before this function is called for the first time.
* A failure in any pix comparison is registered as a failure
* of the regression test.
* (5) Normally, use @printstats = 0. In debugging mode, to see
* the relation between @mindiff and the minimum value of
* @maxfract for success, set this to 1.
*/
l_int32
regTestCompareSimilarPix(FILE *fp,
char **argv,
PIX *pix1,
PIX *pix2,
l_int32 mindiff,
l_float32 maxfract,
l_int32 index,
l_int32 *psuccess,
l_int32 printstats)
{
l_int32 w, h, factor, similar;
PROCNAME("regTestCompareSimilarPix");
if (!psuccess)
return ERROR_INT("&success not defined", procName, 1);
if (index < 0)
return ERROR_INT("index is negative", procName, 1);
if (!pix1 || !pix2)
return ERROR_INT("pix1 and pix2 not both defined", procName, 1);
pixGetDimensions(pix1, &w, &h, NULL);
factor = L_MAX(w, h) / 400;
factor = L_MAX(1, L_MIN(factor, 4)); /* between 1 and 4 */
pixTestForSimilarity(pix1, pix2, factor, mindiff, maxfract, 0.0,
&similar, printstats);
if (!fp) { /* just output result to stderr */
if (similar)
fprintf(stderr, "%s: Success in similarity comparison %d\n",
argv[0], index);
else
fprintf(stderr, "Failure in similarity comparison %d\n", index);
return 0;
}
/* Record on failure */
if (!similar) {
fprintf(fp, "Failure in %s: pix similarity comparison %d\n",
argv[0], index);
fprintf(stderr, "Failure in %s: pix similarity comparison %d\n",
argv[0], index);
*psuccess = 0;
}
return 0;
}
/*!
* regTestCheckFile()
*
* Input: stream (for output; use NULL to generate golden files)
* argv ([0] == name of reg test)
* localname (name of output file from reg test)
* index (of the output file under test; 0-based for the reg test)
* &success (<return> 0 on failure; input value on success)
* Return: 0 if OK, 1 on error (a failure in comparison is not an error)
*
* Notes:
* (1) This function either compares an input file with a "golden" file,
* or generates a "golden" file as a copy @localname.
* Call with @fp == NULL to generate a new golden file.
* (2) This function can be called repeatedly in a single reg test.
* (3) The value for @success is initialized to TRUE in the reg test
* setup before this function is called for the first time.
* A failure in any single file comparison is registered
* as a failure of the regression test.
* (4) The canonical format of the golden filenames is:
* /tmp/<root of main name>_golden.<index>.<ext of localname>
* e.g.,
* /tmp/maze_golden.0.png
*/
l_int32
regTestCheckFile(FILE *fp,
char **argv,
const char *localname,
l_int32 index,
l_int32 *psuccess)
{
char *root, *ext;
char namebuf[64];
l_int32 n, ret, same;
PROCNAME("regTestCheckFile");
if (!psuccess)
return ERROR_INT("&success not defined", procName, 1);
if (!localname)
return ERROR_INT("local name not defined", procName, 1);
if (index < 0)
return ERROR_INT("index is negative", procName, 1);
if ((n = strlen(argv[0])) < 5)
return ERROR_INT("invalid argv; too small", procName, 1);
/* Generate the golden file name */
root = stringNew(argv[0]);
root[n - 4] = '\0';
splitPathAtExtension(localname, NULL, &ext);
snprintf(namebuf, sizeof(namebuf), "/tmp/%s_golden.%d%s", root, index, ext);
FREE(root);
FREE(ext);
/* Save the file as a golden file */
if (!fp) {
ret = fileCopy(localname, namebuf);
if (!ret)
fprintf(stderr, "Copy: %s to %s\n", localname, namebuf);
return ret;
}
/* Test and record on failure */
filesAreIdentical(localname, namebuf, &same);
if (!same) {
fprintf(fp, "Failure in %s: comparing %s with %s\n", argv[0],
localname, namebuf);
fprintf(stderr, "Failure in %s: comparing %s with %s\n", argv[0],
localname, namebuf);
*psuccess = 0;
}
return 0;
}
/*!
* regTestCompareFiles()
*
* Input: stream (for output; use NULL to generate golden files)
* argv ([0] == name of reg test)
* index1 (of one output file from reg test)
* index2 (of another output file from reg test)
* &success (<return> 0 on if different; input value on success)
* Return: 0 if OK, 1 on error (a failure in comparison is not an error)
*
* Notes:
* (1) If @fp != NULL, this function compares two golden files to
* determine if they are the same. If @fp == NULL, this is a
* "generate" operation; don't do the comparison.
* (2) This function can be called repeatedly in a single reg test.
* (3) The value for @success is initialized to TRUE in the reg test
* setup before this function is called for the first time.
* A failure in any file comparison is registered as a failure
* of the regression test.
* (4) The canonical format of the golden filenames is:
* /tmp/<root of main name>_golden.<index>.<ext of localname>
* e.g.,
* /tmp/maze_golden.0.png
*/
l_int32
regTestCompareFiles(FILE *fp,
char **argv,
l_int32 index1,
l_int32 index2,
l_int32 *psuccess)
{
char *root, *name1, *name2;
char namebuf[64];
l_int32 n, error,same;
SARRAY *sa;
PROCNAME("regTestCompareFiles");
if (!psuccess)
return ERROR_INT("&success not defined", procName, 1);
if (index1 < 0 || index2 < 0)
return ERROR_INT("index1 and/or index2 is negative", procName, 1);
if (index1 == index2)
return ERROR_INT("index1 must differ from index2", procName, 1);
if ((n = strlen(argv[0])) < 5)
return ERROR_INT("invalid argv; too small", procName, 1);
if (!fp) /* no-op */
return 0;
/* Generate partial golden file names and find the actual
* paths to them. */
error = FALSE;
name1 = name2 = NULL;
root = stringNew(argv[0]);
root[n - 4] = '\0';
snprintf(namebuf, sizeof(namebuf), "%s_golden.%d.", root, index1);
sa = getSortedPathnamesInDirectory("/tmp", namebuf, 0, 0);
if (sarrayGetCount(sa) != 1)
error = TRUE;
else
name1 = sarrayGetString(sa, 0, L_COPY);
sarrayDestroy(&sa);
snprintf(namebuf, sizeof(namebuf), "%s_golden.%d.", root, index2);
sa = getSortedPathnamesInDirectory("/tmp", namebuf, 0, 0);
if (sarrayGetCount(sa) != 1)
error = TRUE;
else
name2 = sarrayGetString(sa, 0, L_COPY);
sarrayDestroy(&sa);
FREE(root);
if (error == TRUE) {
if (name1) FREE(name1);
if (name2) FREE(name2);
L_ERROR("golden files not found", procName);
return 1;
}
/* Test and record on failure */
filesAreIdentical(name1, name2, &same);
if (!same) {
fprintf(fp, "Failure in %s: comparing %s with %s\n", argv[0],
name1, name2);
fprintf(stderr, "Failure in %s: comparing %s with %s\n", argv[0],
name1, name2);
*psuccess = 0;
}
return 0;
}
/*!
* regTestWritePixAndCheck()
*
* Input: pix (to be written)
* format (of output pix)
* &count (index to be written into filename)
* rp (regression test params; required here)
* Return: 0 if OK, 1 on error (a failure in comparison is not an error)
*
* Notes:
* (1) This function makes it easy to write the pix in a numbered
* sequence of files, and either write the golden files (with
* the "generate" argument to the regression test) or compare
* the written file with an existing golden file.
* (2) This function can be called repeatedly in a single reg test.
* Each time it is called, the count is incremented.
* (3) The canonical format of the local filename is:
* /tmp/junk<root of main name>.<count>.<format extension string>
* e.g., for scale_reg,
* /tmp/junkscale.0.png
*/
l_int32
regTestWritePixAndCheck(PIX *pix,
l_int32 format,
l_int32 *pcount,
L_REGPARAMS *rp)
{
char *root;
char namebuf[256];
l_int32 n;
PROCNAME("regTestWritePixAndCheck");
if (!pix)
return ERROR_INT("pix not defined", procName, 1);
if (!pcount)
return ERROR_INT("&count not defined", procName, 1);
if (format < 0 || format >= NumImageFileFormatExtensions)
return ERROR_INT("invalid format", procName, 1);
if (!rp)
return ERROR_INT("rp not defined", procName, 1);
if ((n = strlen(rp->argv[0])) < 5)
return ERROR_INT("invalid argv; too small", procName, 1);
/* Generate the local file name */
root = stringNew(rp->argv[0]);
root[n - 4] = '\0';
snprintf(namebuf, sizeof(namebuf), "/tmp/junk%s.%d.%s", root, *pcount,
ImageFileFormatExtensions[format]);
FREE(root);
/* Write the local file. Then, either write the golden file
* or check the local file against an existing golden file. */
pixWrite(namebuf, pix, format);
regTestCheckFile(rp->fp, rp->argv, namebuf, (*pcount)++, &rp->success);
return 0;
}