about summary refs log tree commit diff
path: root/analyzer
diff options
context:
space:
mode:
Diffstat (limited to 'analyzer')
-rw-r--r--analyzer/Makefile48
-rw-r--r--analyzer/pamfile.c243
-rw-r--r--analyzer/pamsharpmap.c188
-rw-r--r--analyzer/pamsharpness.c153
-rw-r--r--analyzer/pamslice.c199
-rw-r--r--analyzer/pamsumm.c231
-rw-r--r--analyzer/pamtilt.c437
-rw-r--r--analyzer/pbmminkowski.c168
-rw-r--r--analyzer/pgmhist.c87
-rw-r--r--analyzer/pgmminkowski.c222
-rw-r--r--analyzer/pgmtexture.c1047
-rw-r--r--analyzer/pnmhistmap.c483
-rw-r--r--analyzer/pnmpsnr.c209
-rw-r--r--analyzer/ppmhist.c290
14 files changed, 4005 insertions, 0 deletions
diff --git a/analyzer/Makefile b/analyzer/Makefile
new file mode 100644
index 00000000..6a447ea7
--- /dev/null
+++ b/analyzer/Makefile
@@ -0,0 +1,48 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = analyzer
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/Makefile.config
+
+# We tend to separate out the build targets so that we don't have
+# any more dependencies for a given target than it really needs.
+# That way, if there is a problem with a dependency, we can still
+# successfully build all the stuff that doesn't depend upon it.
+# This package is so big, it's useful even when some parts won't 
+# build.
+
+PORTBINARIES = pamfile pamslice pamsumm pamtilt \
+               pgmhist pnmhistmap ppmhist pgmminkowski
+MATHBINARIES = pamsharpmap pamsharpness pgmtexture pnmpsnr 
+
+BINARIES = $(PORTBINARIES) $(MATHBINARIES)
+SCRIPT =
+
+OBJECTS = $(BINARIES:%=%.o)
+
+# We don't include programs that have special library dependencies in the
+# merge scheme, because we don't want those dependencies to prevent us
+# from building all the other programs.
+
+MERGEBINARIES = $(BINARIES)
+MERGE_OBJECTS = $(MERGEBINARIES:%=%.o2)
+
+.PHONY: all
+all: $(BINARIES)
+
+include $(SRCDIR)/Makefile.common
+
+install.bin: install.bin.local
+.PHONY: install.bin.local
+install.bin.local: $(PKGDIR)/bin
+# Remember that $(SYMLINK) might just be a copy command.
+	cd $(PKGDIR)/bin ; \
+	$(SYMLINK) pamslice$(EXE) pgmslice
+	cd $(PKGDIR)/bin ; \
+	$(SYMLINK) pamfile$(EXE) pnmfile
+
+FORCE:
+
diff --git a/analyzer/pamfile.c b/analyzer/pamfile.c
new file mode 100644
index 00000000..efb2cbee
--- /dev/null
+++ b/analyzer/pamfile.c
@@ -0,0 +1,243 @@
+/* pamfile.c - describe a portable anymap
+**
+** Copyright (C) 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "mallocvar.h"
+#include "nstring.h"
+#include "shhopt.h"
+#include "pam.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    int inputFileCount;  /* Number of input files */
+    const char ** inputFilespec;  /* Filespecs of input files */
+    unsigned int allimages;  /* -allimages or -count */
+    unsigned int count;      /* -count */
+    unsigned int comments;   /* -comments */
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to as as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+    
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "allimages", OPT_FLAG,  NULL, &cmdlineP->allimages,   0);
+    OPTENT3(0,   "count",     OPT_FLAG,  NULL, &cmdlineP->count,       0);
+    OPTENT3(0,   "comments",  OPT_FLAG,  NULL, &cmdlineP->comments,    0);
+
+    opt.opt_table     = option_def;
+    opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */
+    opt.allowNegNum   = FALSE; /* We have no parms that are negative numbers */
+    
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others */
+
+    cmdlineP->inputFilespec = (const char **)&argv[1];
+    cmdlineP->inputFileCount = argc - 1;
+}
+
+
+
+static void
+dumpHeader(struct pam const pam) {
+
+    switch (pam.format) {
+    case PAM_FORMAT:
+        printf("PAM, %d by %d by %d maxval %ld\n", 
+               pam.width, pam.height, pam.depth, pam.maxval);
+        printf("    Tuple type: %s\n", pam.tuple_type);
+        break;
+
+	case PBM_FORMAT:
+        printf("PBM plain, %d by %d\n", pam.width, pam.height );
+        break;
+
+	case RPBM_FORMAT:
+        printf("PBM raw, %d by %d\n", pam.width, pam.height);
+        break;
+
+	case PGM_FORMAT:
+        printf("PGM plain, %d by %d  maxval %ld\n", 
+               pam.width, pam.height, pam.maxval);
+        break;
+
+	case RPGM_FORMAT:
+        printf("PGM raw, %d by %d  maxval %ld\n", 
+               pam.width, pam.height, pam.maxval);
+        break;
+
+	case PPM_FORMAT:
+        printf("PPM plain, %d by %d  maxval %ld\n", 
+               pam.width, pam.height, pam.maxval);
+        break;
+
+	case RPPM_FORMAT:
+        printf("PPM raw, %d by %d  maxval %ld\n", 
+               pam.width, pam.height, pam.maxval);
+        break;
+    }
+}
+
+
+
+static void
+dumpComments(const char * const comments) {
+
+    const char * p;
+    bool startOfLine;
+    
+    printf("Comments:\n");
+
+    for (p = &comments[0], startOfLine = TRUE; *p; ++p) {
+        if (startOfLine)
+            printf("  #");
+        
+        fputc(*p, stdout);
+        
+        if (*p == '\n')
+            startOfLine = TRUE;
+        else
+            startOfLine = FALSE;
+    }
+    if (!startOfLine)
+        fputc('\n', stdout);
+}
+
+
+
+static void
+doOneImage(const char * const name,
+           unsigned int const imageDoneCount,
+           FILE *       const fileP,
+           bool         const allimages,
+           bool         const justCount,
+           bool         const wantComments,
+           bool *       const eofP) {
+                    
+    struct pam pam;
+    const char * comments;
+    enum pm_check_code checkRetval;
+
+    pam.comment_p = &comments;
+
+    pnm_readpaminit(fileP, &pam, PAM_STRUCT_SIZE(comment_p));
+        
+    if (!justCount) {
+        if (allimages)
+            printf("%s:\tImage %d:\t", name, imageDoneCount);
+        else 
+            printf("%s:\t", name);
+            
+        dumpHeader(pam);
+        if (wantComments)
+            dumpComments(comments);
+    }
+    strfree(comments);
+
+    pnm_checkpam(&pam, PM_CHECK_BASIC, &checkRetval);
+    if (allimages) {
+        tuple * tuplerow;
+        unsigned int row;
+        
+        tuplerow = pnm_allocpamrow(&pam);
+        
+        for (row = 0; row < pam.height; ++row) 
+            pnm_readpamrow(&pam, tuplerow);
+        
+        pnm_freepamrow(tuplerow);
+        
+        pnm_nextimage(fileP, eofP);
+    }
+}
+
+
+
+static void
+describeOneFile(const char * const name,
+                FILE *       const fileP,
+                bool         const allimages,
+                bool         const justCount,
+                bool         const wantComments) {
+/*----------------------------------------------------------------------------
+   Describe one image stream (file).  Its name, for purposes of display,
+   is 'name'.  The file is open as *fileP and positioned to the beginning.
+
+   'allimages' means report on every image in the stream and read all of
+   every image from it, as opposed to reading just the header of the first
+   image and reporting just on that.
+
+   'justCount' means don't tell anything about the stream except how
+   many images are in it.  Pretty useless without 'allimages'.
+
+   'wantComments' means to show the comments from the image header.
+   Meaningless with 'justCount'.
+-----------------------------------------------------------------------------*/
+    unsigned int imageDoneCount;
+        /* Number of images we've processed so far */
+    bool eof;
+    
+    eof = FALSE;
+    imageDoneCount = 0;
+
+    while (!eof && (imageDoneCount < 1 || allimages)) {
+        doOneImage(name, imageDoneCount, fileP,
+                   allimages, justCount, wantComments,
+                   &eof);
+        ++imageDoneCount;
+    }
+    if (justCount)
+        printf("%s:\t%u images\n", name, imageDoneCount);
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    if (cmdline.inputFileCount == 0)
+        describeOneFile("stdin", stdin, cmdline.allimages || cmdline.count,
+                        cmdline.count, cmdline.comments);
+    else {
+        unsigned int i;
+        for (i = 0; i < cmdline.inputFileCount; ++i) {
+            FILE * ifP;
+            ifP = pm_openr(cmdline.inputFilespec[i]);
+            describeOneFile(cmdline.inputFilespec[i], ifP, 
+                            cmdline.allimages || cmdline.count,
+                            cmdline.count, cmdline.comments);
+            pm_close(ifP);
+	    }
+	}
+    
+    return 0;
+}
diff --git a/analyzer/pamsharpmap.c b/analyzer/pamsharpmap.c
new file mode 100644
index 00000000..73923ab9
--- /dev/null
+++ b/analyzer/pamsharpmap.c
@@ -0,0 +1,188 @@
+/*----------------------------------------------------------------------------
+                                Pnmsharpness
+------------------------------------------------------------------------------
+
+  Bryan Henderson derived this (January 2004) from the program Pnmsharp
+  by B.W. van Schooten and distributed to Bryan under the Perl
+  Artistic License, as part of the Photopnmtools package.  Bryan placed
+  his modifications in the public domain.
+
+  This is the copyright/license notice from the original:
+
+   Copyright (c) 2002, 2003 by B.W. van Schooten. All rights reserved.
+   This software is distributed under the Perl Artistic License.
+   No warranty. See file 'artistic.license' for more details.
+
+   boris@13thmonkey.org
+   www.13thmonkey.org/~boris/photopnmtools/ 
+-----------------------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <math.h>
+
+#include "pam.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* '-' if stdin */
+    unsigned int context;
+};
+
+
+
+static void
+parseCommandLine ( int argc, char ** argv,
+                   struct cmdlineInfo *cmdlineP ) {
+/*----------------------------------------------------------------------------
+   parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int contextSpec;
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "context",       OPT_UINT,   &cmdlineP->context,       
+            &contextSpec,         0 );
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!contextSpec)
+        cmdlineP->context = 1;
+
+    if (cmdlineP->context < 1)
+        pm_error("-context must be at least 1");
+
+
+    if (argc-1 > 1)
+        pm_error("The only argument is the input file name");
+    else if (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else
+        cmdlineP->inputFilespec = argv[1];
+}
+
+
+
+static void
+makeSharpnessPixel(struct pam * const pamP,
+                   float *      const sharpness,
+                   tuple        const sharpnessTuple) {
+
+    unsigned int plane;
+    for (plane = 0; plane < pamP->depth; ++plane)
+        sharpnessTuple[plane] = 
+            (sample)(sharpness[plane] * pamP->maxval + 0.5);
+}
+
+
+
+static void
+makeBlackTuplen(struct pam * const pamP,
+                tuplen       const tuplen) {
+
+    unsigned int plane;
+    for (plane = 0; plane < pamP->depth; ++plane)
+        tuplen[plane] = 0.0;
+}
+
+
+
+static void
+makeBlackRown(struct pam * const pamP,
+              tuplen *     const tuplenrow) {
+
+    unsigned int col;
+    for (col = 0; col < pamP->width; ++col)
+        makeBlackTuplen(pamP, tuplenrow[col]);
+}
+
+
+
+int
+main(int argc, char **argv) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    tuplen ** tuplenarray;
+    struct pam inpam;
+    struct pam mappam;
+    tuple ** map;
+    int row;
+    float * sharpness;
+
+	pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+	tuplenarray = pnm_readpamn(ifP, &inpam, sizeof(inpam));
+
+    mappam = inpam;
+    mappam.file = stdout;
+    mappam.maxval = 255;
+
+    MALLOCARRAY_NOFAIL(sharpness, inpam.depth);
+            
+    map = pnm_allocpamarray(&mappam);
+    makeBlackRown(&inpam, tuplenarray[0]);
+    for (row = 1; row < inpam.height-1; ++row) {
+        int col;
+        makeBlackTuplen(&inpam, tuplenarray[row][0]);
+        for (col = 1; col < inpam.width-1; ++col) {
+            int dy;
+            unsigned int plane;
+            
+            for (plane = 0; plane < inpam.depth; ++plane)
+                sharpness[plane] = 0.0;
+
+            for (dy = -1; dy <= 1; ++dy) {
+                int dx;
+                for (dx = -1; dx <= 1; ++dx) {
+                    if (dx != 0 || dy != 0) {
+                        unsigned int plane;
+                        for (plane = 0; plane < inpam.depth; ++plane) {
+                            samplen const sampleval = 
+                                tuplenarray[row][col][plane];
+                            samplen const sampleval2 = 
+                                tuplenarray[row+dy][col+dx][plane];
+                            sharpness[plane] += fabs(sampleval - sampleval2);
+                        }
+                    }
+                }
+            }
+            makeSharpnessPixel(&mappam, sharpness, map[row][col]);
+        }
+        makeBlackTuplen(&inpam, tuplenarray[row][inpam.width-1]);
+    }
+    makeBlackRown(&inpam, tuplenarray[inpam.height-1]);
+    free(sharpness);
+    
+    pnm_writepam(&mappam, map);
+
+    pnm_freepamarray(map, &mappam);
+	pnm_freepamarrayn(tuplenarray, &inpam);
+
+	return 0;
+}
diff --git a/analyzer/pamsharpness.c b/analyzer/pamsharpness.c
new file mode 100644
index 00000000..7e52a9ba
--- /dev/null
+++ b/analyzer/pamsharpness.c
@@ -0,0 +1,153 @@
+/*----------------------------------------------------------------------------
+                                Pnmsharpness
+------------------------------------------------------------------------------
+
+  Bryan Henderson derived this (January 2004) from the program of the
+  same name by B.W. van Schooten and distributed to Bryan under the Perl
+  Artistic License, as part of the Photopnmtools package.  Bryan placed
+  his modifications in the public domain.
+
+  This is the copyright/license notice from the original:
+
+   Copyright (c) 2002, 2003 by B.W. van Schooten. All rights reserved.
+   This software is distributed under the Perl Artistic License.
+   No warranty. See file 'artistic.license' for more details.
+
+   boris@13thmonkey.org
+   www.13thmonkey.org/~boris/photopnmtools/ 
+-----------------------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <math.h>
+
+#include "pam.h"
+#include "shhopt.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* '-' if stdin */
+    unsigned int context;
+};
+
+
+
+static void
+parseCommandLine ( int argc, char ** argv,
+                   struct cmdlineInfo *cmdlineP ) {
+/*----------------------------------------------------------------------------
+   parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int contextSpec;
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "context",       OPT_UINT,   &cmdlineP->context,       
+            &contextSpec,         0 );
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!contextSpec)
+        cmdlineP->context = 1;
+
+    if (cmdlineP->context < 1)
+        pm_error("-context must be at least 1");
+
+
+    if (argc-1 > 1)
+        pm_error("The only argument is the input file name");
+    else if (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else
+        cmdlineP->inputFilespec = argv[1];
+}
+
+
+
+static void
+computeSharpness(struct pam * const inpamP,
+                 tuplen **    const tuplenarray,
+                 double *     const sharpnessP) {
+
+    int row;
+    double totsharp;
+    
+    totsharp = 0.0;
+
+    for (row = 1; row < inpamP->height-1; ++row) {
+        int col;
+        for (col = 1; col < inpamP->width-1; ++col) {
+            int dy;
+            for (dy = -1; dy <= 1; ++dy) {
+                int dx;
+                for (dx = -1; dx <= 1; ++dx) {
+                    if (dx != 0 || dy != 0) {
+                        unsigned int plane;
+                        for (plane = 0; plane < inpamP->depth; ++plane) {
+                            samplen const sampleval = 
+                                tuplenarray[row][col][plane];
+                            samplen const sampleval2 = 
+                                tuplenarray[row+dy][col+dx][plane];
+                            totsharp += fabs(sampleval - sampleval2);
+                        }
+                    }
+                }
+            }
+		}
+	}
+    *sharpnessP = 
+        totsharp / (inpamP->width * inpamP->height * inpamP->depth * 8);
+        /* The 8 above is for the 8 neighbors to which we compare each pixel */
+}
+
+
+
+int
+main(int argc, char **argv) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    tuplen ** tuplenarray;
+    struct pam inpam;
+    double sharpness;
+
+	pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+	tuplenarray = pnm_readpamn(ifP, &inpam, sizeof(inpam));
+
+    if (inpam.height < 3 || inpam.width < 3)
+        pm_error("sharpness is undefined for an image less than 3 pixels "
+                 "in all directions.  This image is %d x %d",
+                 inpam.width, inpam.height);
+
+    computeSharpness(&inpam, tuplenarray, &sharpness);
+
+    pm_message("Sharpness = %f\n", sharpness);
+
+	pnm_freepamarrayn(tuplenarray, &inpam);
+    pm_close(ifP);
+	return 0;
+}
diff --git a/analyzer/pamslice.c b/analyzer/pamslice.c
new file mode 100644
index 00000000..fc63a2cc
--- /dev/null
+++ b/analyzer/pamslice.c
@@ -0,0 +1,199 @@
+/* 
+ *
+ * This program (Pamslice) was derived by Bryan Henderson in July 2002
+ * from Pgmslice by Jos Dingjan.  Pgmslice did the same thing, but
+ * only on PGM images.  Pamslice is a total rewrite.
+ *
+ * Copyright (C) 2000 Jos Dingjan <jos@tuatha.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of 
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+enum orientation {ROW, COLUMN};
+
+#include "pam.h"
+#include "shhopt.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespec of input file */
+    enum orientation orientation;
+    union {
+        unsigned int row;
+        unsigned int col;
+    } u;
+    unsigned int onePlane;
+    unsigned int plane;
+    unsigned int xmgr;
+};
+
+
+static void
+parseCommandLine(int argc, char ** const argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to OptParseOptions2 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int rowSpec, colSpec;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "row",    OPT_UINT,   &cmdlineP->u.row, &rowSpec,            0);
+    OPTENT3(0, "column", OPT_UINT,   &cmdlineP->u.col, &colSpec,            0);
+    OPTENT3(0, "plane",  OPT_UINT,   &cmdlineP->plane, &cmdlineP->onePlane, 0);
+    OPTENT3(0, "xmgr",   OPT_FLAG,   NULL,             &cmdlineP->xmgr, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (rowSpec && colSpec)
+        pm_error("You cannot specify both -row and -col");
+
+    if (argc-1 > 1)
+        pm_error("Too many arguments (%d).  Only argument is filename.",
+                 argc-1);
+    else if (argc-1 == 1) 
+        cmdlineP->inputFilespec = argv[1];
+    else
+        cmdlineP->inputFilespec = "-";
+
+    if (rowSpec) 
+        cmdlineP->orientation = ROW;
+    else if (colSpec)
+        cmdlineP->orientation = COLUMN;
+    else
+        pm_error("You must specify either -column or -row");
+}
+
+
+
+static void
+printSlice(FILE *       const outfile,
+           tuple **     const tuples,
+           unsigned int const rowstart,
+           unsigned int const rowend,
+           unsigned int const colstart,
+           unsigned int const colend,
+           unsigned int const planestart,
+           unsigned int const planeend,
+           bool         const xmgr) {
+
+    unsigned int count;
+    unsigned int row;
+
+
+    if (xmgr) {
+        fprintf(outfile,"@    title \"Graylevel\"\n");
+        fprintf(outfile,"@    subtitle \"from (%d,%d) to (%d,%d)\"\n",
+                colstart, rowstart, colend, rowend);
+        if (colend - colstart == 1)
+            fprintf(outfile,"@    xaxis label \"row\"\n");
+        else
+            fprintf(outfile,"@    xaxis label \"column\"\n");
+        fprintf(outfile,"@    yaxis label \"grayvalue\"\n");
+        if (planeend - planestart == 1)
+            fprintf(stdout,"@    type xy\n");
+        else
+            fprintf(stdout,"@    type  nxy\n");
+    }
+    
+    count = 0;
+    for (row = rowstart; row < rowend; ++row) {
+        unsigned int col;
+        for (col = colstart; col < colend; ++col) {
+            unsigned int plane;
+            fprintf(outfile, "%d ", count++);
+            for (plane = planestart; plane < planeend; ++plane)
+                fprintf(outfile, "%lu ", tuples[row][col][plane]);
+            fprintf(outfile, "\n");
+        }
+    }
+}
+
+
+
+int 
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE *ifP;
+    struct pam inpam;
+    tuple **tuples;
+    unsigned int colstart, colend, rowstart, rowend, planestart, planeend;
+
+    pgm_init( &argc, argv );
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    tuples = pnm_readpam(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    switch (cmdline.orientation) {
+    case COLUMN:
+        if (cmdline.u.col >= inpam.width)
+            pm_error("You specified column %u, but there are only %u columns "
+                     "in the image.", cmdline.u.col, inpam.width);
+
+        colstart = cmdline.u.col;
+        colend   = colstart + 1;
+        rowstart = 0;
+        rowend   = inpam.height;
+
+        break;
+    case ROW:
+        if (cmdline.u.row >= inpam.height)
+            pm_error("You specified row %u, but there are only %u rows "
+                     "in the image.", cmdline.u.row, inpam.height);
+        colstart = 0;
+        colend   = inpam.width;
+        rowstart = cmdline.u.row;
+        rowend   = rowstart + 1;
+
+        break;
+    }
+    
+    if (cmdline.onePlane) {
+        if (cmdline.plane >= inpam.depth)
+            pm_error("You specified plane %u, but there are only %u planes "
+                     "in the image.", cmdline.plane, inpam.depth);
+        planestart = cmdline.plane;
+        planeend = planestart + 1;
+    } else {
+        planestart = 0;
+        planeend = inpam.depth;
+    }
+
+    printSlice(stdout, tuples, 
+               rowstart, rowend, colstart, colend, planestart, planeend,
+               cmdline.xmgr);
+
+    pm_close(ifP);
+    pm_close(stdout);
+        
+    exit(0);
+}
diff --git a/analyzer/pamsumm.c b/analyzer/pamsumm.c
new file mode 100644
index 00000000..390b8ebb
--- /dev/null
+++ b/analyzer/pamsumm.c
@@ -0,0 +1,231 @@
+/******************************************************************************
+                               pamsumm
+*******************************************************************************
+  Summarize all the samples of a PAM image with various functions.
+
+  By Bryan Henderson, San Jose CA 2004.02.07.
+
+  Contributed to the public domain
+
+
+******************************************************************************/
+
+#include "pam.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+enum function {FN_ADD, FN_MEAN, FN_MIN, FN_MAX};
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespec of input file */
+    enum function function;
+    unsigned int normalize;
+    unsigned int brief;
+    unsigned int verbose;
+};
+
+
+static void
+parseCommandLine(int argc, char ** const argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to OptParseOptions2 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int sumSpec, meanSpec, minSpec, maxSpec;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0,   "sum",       OPT_FLAG,  NULL, &sumSpec,             0);
+    OPTENT3(0,   "mean",      OPT_FLAG,  NULL, &meanSpec,            0);
+    OPTENT3(0,   "min",       OPT_FLAG,  NULL, &minSpec,             0);
+    OPTENT3(0,   "max",       OPT_FLAG,  NULL, &maxSpec,             0);
+    OPTENT3(0,   "normalize", OPT_FLAG,  NULL, &cmdlineP->normalize, 0);
+    OPTENT3(0,   "brief",     OPT_FLAG,  NULL, &cmdlineP->brief,     0);
+    OPTENT3(0,   "verbose",   OPT_FLAG,  NULL, &cmdlineP->verbose,   0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (sumSpec + minSpec + maxSpec > 1)
+        pm_error("You may specify at most one of -sum, -min, and -max");
+
+    if (sumSpec) {
+        cmdlineP->function = FN_ADD;
+    } else if (meanSpec) {
+        cmdlineP->function = FN_MEAN;
+    } else if (minSpec) {
+        cmdlineP->function = FN_MIN;
+    } else if (maxSpec) {
+        cmdlineP->function = FN_MAX;
+    } else 
+        pm_error("You must specify one of -sum, -min, or -max");
+        
+    if (argc-1 > 1)
+        pm_error("Too many arguments (%d).  File spec is the only argument.",
+                 argc-1);
+
+    if (argc-1 < 1)
+        cmdlineP->inputFilespec = "-";
+    else 
+        cmdlineP->inputFilespec = argv[1];
+    
+}
+
+
+struct accum {
+    union {
+        double sum;
+        unsigned int min;
+        unsigned int max;
+    } u;
+};
+
+
+
+static void
+initAccumulator(struct accum * const accumulatorP,
+                enum function  const function) {
+
+    switch(function) {
+    case FN_ADD:  accumulatorP->u.sum = 0.0;      break;
+    case FN_MEAN: accumulatorP->u.sum = 0.0;      break;
+    case FN_MIN:  accumulatorP->u.min = UINT_MAX; break;
+    case FN_MAX:  accumulatorP->u.max = 0;        break;
+    }
+}
+
+
+
+static void
+aggregate(struct pam *   const inpamP,
+          tuple *        const tupleRow,
+          enum function  const function,
+          struct accum * const accumulatorP) {
+
+    unsigned int col;
+
+    for (col = 0; col < inpamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < inpamP->depth; ++plane) {
+            switch(function) {
+            case FN_ADD:  
+            case FN_MEAN: 
+                accumulatorP->u.sum += tupleRow[col][plane];
+            break;
+            case FN_MIN:  
+                if (tupleRow[col][plane] < accumulatorP->u.min)
+                    accumulatorP->u.min = tupleRow[col][plane];
+                break;
+            case FN_MAX:
+                if (tupleRow[col][plane] > accumulatorP->u.min)
+                    accumulatorP->u.min = tupleRow[col][plane];
+                break;
+            } 
+        }
+    }
+}
+
+
+
+static void
+printSummary(struct accum  const accumulator,
+             unsigned int  const scale,
+             unsigned int  const count,
+             enum function const function,
+             bool          const normalize,
+             bool          const brief) {
+
+    switch(function) {
+    case FN_ADD: {  
+        const char * const intro = brief ? "" : "the sum of all samples is ";
+
+        if (normalize)
+            printf("%s%f\n", intro, accumulator.u.sum/scale);
+        else
+            printf("%s%u\n", intro, (unsigned int)accumulator.u.sum);
+    }
+    break;
+    case FN_MEAN: {
+        const char * const intro = brief ? "" : "the mean of all samples is ";
+
+        if (normalize)
+            printf("%s%f\n", intro, accumulator.u.sum/count/scale);
+        else
+            printf("%s%f\n", intro, accumulator.u.sum/count);
+    }
+    break;
+    case FN_MIN: {
+        const char * const intro = 
+            brief ? "" : "the minimum of all samples is ";
+
+        if (normalize)
+            printf("%s%f\n", intro, (double)accumulator.u.min/scale);
+        else
+            printf("%s%u\n", intro, accumulator.u.min);
+    }
+    break;
+    case FN_MAX: {
+        const char * const intro = 
+            brief ? "" : "the maximum of all samples is ";
+
+        if (normalize)
+            printf("%s%f\n", intro, (double)accumulator.u.max/scale);
+        else
+            printf("%s%u\n", intro, accumulator.u.max);
+    }
+    break;
+    }
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    FILE* ifP;
+    tuple* inputRow;   /* Row from input image */
+    int row;
+    struct cmdlineInfo cmdline;
+    struct pam inpam;   /* Input PAM image */
+    struct accum accumulator;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    inputRow = pnm_allocpamrow(&inpam);
+
+    initAccumulator(&accumulator, cmdline.function);
+
+    for (row = 0; row < inpam.height; row++) {
+        pnm_readpamrow(&inpam, inputRow);
+
+        aggregate(&inpam, inputRow, cmdline.function, &accumulator);
+    }
+    printSummary(accumulator, (unsigned)inpam.maxval,
+                 inpam.height * inpam.width * inpam.depth, 
+                 cmdline.function, cmdline.normalize, cmdline.brief);
+
+    pnm_freepamrow(inputRow);
+    pm_close(inpam.file);
+    
+    return 0;
+}
diff --git a/analyzer/pamtilt.c b/analyzer/pamtilt.c
new file mode 100644
index 00000000..3753955b
--- /dev/null
+++ b/analyzer/pamtilt.c
@@ -0,0 +1,437 @@
+/*=============================================================================
+                             pgmtilt
+===============================================================================
+  Print the tilt angle of a PGM file
+
+  Based on pgmskew by Gregg Townsend, August 2005.
+
+  Adapted to Netpbm by Bryan Henderson, August 2005.
+
+  All work has been contributed to the public domain by its authors.
+=============================================================================*/
+
+#include <math.h>
+
+#include "pam.h"
+#include "mallocvar.h"
+#include "shhopt.h"
+
+/* program constants */
+#define DIFFMAX 255                     /* maximum scaling for differences */
+#define BARLENGTH 60                    /* length of bars printed with -v */
+
+struct cmdlineInfo {
+    /* command parameters */
+    const char * inputFilename;
+    float maxangle;     /* maximum angle attempted */
+    float astep;        /* initial angle increment */
+    float qmin;         /* minimum quality of solution */
+    unsigned int hstep; /* horizontal step size */
+    unsigned int vstep; /* vertical step size */
+    unsigned int dstep; /* difference distance */
+    unsigned int fast;    /* skip third iteration */
+    unsigned int verbose; /* generate commentary */
+};
+
+static void
+abandon(void) {
+
+    printf("00.00\n");
+    exit(0);
+}
+
+
+
+static void
+parseCommandLine(int argc, char *argv[],
+                 struct cmdlineInfo * const cmdlineP) {
+
+    static optEntry option_def[50];
+    static optStruct3 opt;
+    unsigned int option_def_index;
+
+    /* set defaults */
+    cmdlineP->hstep = 11;        /* read only every 11th column */
+    cmdlineP->vstep = 5;         /* calc differences every 5th row */
+    cmdlineP->dstep = 2;         /* check for differences two rows down */
+    cmdlineP->maxangle = 10.0;   /* assume skew is less than +/- ten degrees */
+    cmdlineP->astep = 1.0;       /* initially check by one-degree increments */
+    cmdlineP->qmin = 1.0;        /* don't require S/N better than 1.0 */
+
+    /* initialize option table */
+    option_def_index = 0;       /* incremented by OPTENT3 */
+    OPTENT3(0, "fast",     OPT_FLAG,  NULL, &cmdlineP->fast,     0);
+    OPTENT3(0, "verbose",  OPT_FLAG,  NULL, &cmdlineP->verbose,  0);
+    OPTENT3(0, "angle",    OPT_FLOAT, &cmdlineP->maxangle, NULL, 0);
+    OPTENT3(0, "quality",  OPT_FLOAT, &cmdlineP->qmin,     NULL, 0);
+    OPTENT3(0, "astep",    OPT_FLOAT, &cmdlineP->astep,    NULL, 0);
+    OPTENT3(0, "hstep",    OPT_UINT,  &cmdlineP->hstep,    NULL, 0);
+    OPTENT3(0, "vstep",    OPT_UINT,  &cmdlineP->vstep,    NULL, 0);
+    OPTENT3(0, "dstep",    OPT_UINT,  &cmdlineP->dstep,    NULL, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;          /* no short options used */
+    opt.allowNegNum = FALSE;            /* don't allow negative values */
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+
+    if (cmdlineP->hstep < 1)
+        pm_error("-hstep must be at least 1 column.");
+    if (cmdlineP->vstep < 1)
+        pm_error("-vstep must be at least 1 row.");
+    if (cmdlineP->dstep < 1)
+        pm_error("-dstep must be at least 1 row.");
+    if (cmdlineP->maxangle < 1 || cmdlineP->maxangle > 45)
+        pm_error("-maxangle must be between 1 and 45 degrees.");
+
+    if (argc-1 < 1)                      /* if input file name given */
+        cmdlineP->inputFilename = "-";
+    else {
+        cmdlineP->inputFilename = argv[1];
+
+        if (argc-1 > 1)
+            pm_error("There is at most one argument.  You specified %d",
+                     argc-1);
+    }
+}
+
+
+
+static void
+computeSteps(const struct pam * const pamP,
+             unsigned int       const hstepReq,
+             unsigned int       const vstepReq,
+             unsigned int *     const hstepP,
+             unsigned int *     const vstepP) {
+/*----------------------------------------------------------------------------
+  Adjust parameters if necessary now that we have the image size
+-----------------------------------------------------------------------------*/
+    if (pamP->width < 10 || pamP->height < 10)
+        abandon();
+
+    if (pamP->width < 10 * hstepReq)
+        *hstepP = pamP->width / 10;
+    else
+        *hstepP = hstepReq;
+
+    if (pamP->height < 10 * vstepReq)
+        *vstepP = pamP->height / 10;
+    else
+        *vstepP = vstepReq;
+}
+
+
+
+static void
+load(const struct pam * const pamP,
+     unsigned int       const hstep,
+     sample ***         const pixelsP,
+     unsigned int *     const hsamplesP) {
+/*----------------------------------------------------------------------------
+  read file into memory, returning array of rows
+-----------------------------------------------------------------------------*/
+    unsigned int const hsamples = 1 + (pamP->width - 1) / hstep;
+        /* use only this many cols */
+
+    unsigned int row;
+    tuple *  tuplerow;
+    sample ** pixels;
+
+    tuplerow = pnm_allocpamrow(pamP);
+    
+    MALLOCARRAY(pixels, pamP->height);
+    if (pixels == NULL)
+        pm_error("Unable to allocate array of %u pixel rows",
+                 pamP->height);
+
+    if (pixels == NULL)
+        pm_error("Unable to allocate array of %u rows", pamP->height);
+
+    for (row = 0; row < pamP->height; ++row) {
+        unsigned int i;
+        unsigned int col;
+
+        pnm_readpamrow(pamP, tuplerow);
+
+        MALLOCARRAY(pixels[row], hsamples);
+        if (pixels[row] == NULL)
+            pm_error("Unable to allocate %u-sample array for Row %u",
+                     hsamples, row);
+
+        /* save every hstep'th column */
+        for (i = col = 0; i < hsamples; ++i, col += hstep)
+            pixels[row][i] = tuplerow[col][0];
+    }
+
+    pnm_freepamrow(tuplerow);
+
+    *hsamplesP = hsamples;
+    *pixelsP   = pixels;
+}
+
+
+
+static void
+freePixels(sample **    const samples,
+           unsigned int const rows) {
+
+    unsigned int row;
+
+    for (row = 0; row < rows; ++row)
+        free(samples[row]);
+
+    free(samples);
+}
+
+
+
+static void
+replacePixelValuesWithScaledDiffs(
+    const struct pam * const pamP,
+    sample **          const pixels,
+    unsigned int       const hsamples,
+    unsigned int       const dstep) {
+/*----------------------------------------------------------------------------
+  Replace pixel values with scaled differences used for all
+  calculations
+-----------------------------------------------------------------------------*/
+    float const m = DIFFMAX / (float) pamP->maxval;  /* scale multiplier */
+
+    unsigned int row;
+
+    for (row = dstep; row < pamP->height; ++row) {
+        unsigned int col;
+        for (col = 0; col < hsamples; ++col) {
+            int const d = pixels[row - dstep][col] - pixels[row][col];
+            unsigned int const absd = d < 0 ? -d : d;
+            pixels[row - dstep][col] = m * absd;  /* scale the difference */
+        }
+    }
+}
+
+
+
+static void
+scoreAngle(const struct pam * const pamP,
+           sample **          const pixels,
+           unsigned int       const hstep,
+           unsigned int       const vstep,
+           unsigned int       const hsamples,
+           float              const deg,
+           float *            const scoreP) {
+/*----------------------------------------------------------------------------
+  calculate score for a given angle
+-----------------------------------------------------------------------------*/
+    float  const radians = (float)deg/360 * 2 * M_PI;
+    float  const dy      = hstep * tan(radians);
+    int    const dtotal  = pamP->width * tan(radians);
+    double const tscale  = 1.0 / hsamples;
+
+    double total;
+    int first;
+    int last;
+    
+    unsigned int row;
+
+    total = 0.0; /* initial value */
+
+    if (dtotal > 0) {
+        first = 0;
+        last = pamP->height - 1 - (dtotal + 1);
+    } else {
+        first = -(dtotal - 1);
+        last = pamP->height - 1;
+    }
+    for (row = first; row < last; row += vstep) {
+        float o;
+        long t;
+        double dt;
+
+        unsigned int i;
+
+        for (i = 0, t = 0, o = 0.5;
+             i < hsamples;
+             ++i, t += pixels[(int)(row + o)][i], o += dy) {
+        }
+        dt = tscale * t;
+        total += dt * dt;
+    }
+    *scoreP = total / (last - first);
+}
+
+
+
+static void
+getBestAngleLocal(
+    const struct pam * const pamP,
+    sample **          const pixels,
+    unsigned int       const hstep,
+    unsigned int       const vstep,
+    unsigned int       const hsamples,
+    float              const minangle,
+    float              const maxangle,
+    float              const incr,
+    bool               const verbose,
+    float *            const bestAngleP,
+    float *            const qualityP) {
+/*----------------------------------------------------------------------------
+  find angle of highest score within a range
+-----------------------------------------------------------------------------*/
+    int const nsamples = ((maxangle - minangle) / incr + 1.5);
+
+    float score;
+    float quality;  /* signal/noise ratio of the best angle */
+    float angle;
+    float bestangle;
+    float bestscore;
+    float total;
+    float others;
+    float * results;
+    int i;
+
+    MALLOCARRAY_NOFAIL(results, nsamples);      /* allocate array of results */
+
+    /* try all angles within the given range, stepping by incr */
+    bestangle = minangle;
+    bestscore = 0;
+    total = 0;
+    for (i = 0; i < nsamples; i++) {
+        angle = minangle + i * incr;
+        scoreAngle(pamP, pixels, hstep, vstep, hsamples, angle, &score);
+        results[i] = score;
+        if (score > bestscore ||
+            (score == bestscore && fabs(angle) < fabs(bestangle))) {
+            bestscore = score;
+            bestangle = angle;
+        }
+        total += score;
+    }
+
+    others = (total-bestscore) / (nsamples-1);  /* get mean of other scores */
+    quality = bestscore / others;
+
+    if (verbose) {
+        fprintf(stderr,
+                "\n%2d angles from %6.2f to %5.2f by %4.2f:  "
+                "best = %6.2f, S:N = %4.2f\n",
+                nsamples, minangle, maxangle, incr, bestangle, quality);
+        for (i = 0; i < nsamples; ++i) {
+            float const angle = minangle + i * incr;
+            int const n = (int) (BARLENGTH * results[i] / bestscore + 0.5);
+            fprintf(stderr, "%6.2f: %8.2f %0*d\n", angle, results[i], n, 0);
+        }
+    }
+    free(results);
+
+    *qualityP   = quality;
+    *bestAngleP = bestangle;
+}
+
+
+
+static void
+readRelevantPixels(const char *   const inputFilename,
+                   unsigned int   const hstepReq,
+                   unsigned int   const vstepReq,
+                   unsigned int * const hstepP,
+                   unsigned int * const vstepP,
+                   sample ***     const pixelsP,
+                   struct pam *   const pamP,
+                   unsigned int * const hsamplesP) {
+/*----------------------------------------------------------------------------
+  load the image, saving only the pixels we might actually inspect
+-----------------------------------------------------------------------------*/
+    FILE * ifP;
+    unsigned int hstep;
+    unsigned int vstep;
+
+    ifP = pm_openr(inputFilename);
+    pnm_readpaminit(ifP, pamP, PAM_STRUCT_SIZE(tuple_type));
+    computeSteps(pamP, hstepReq, vstepReq, &hstep, &vstep);
+
+    load(pamP, hstep, pixelsP, hsamplesP);
+
+    *hstepP = hstep;
+    *vstepP = vstep;
+    
+    pm_close(ifP);
+}
+
+
+
+static void
+getAngle(const struct pam * const pamP,
+         sample **          const pixels,
+         unsigned int       const hstep,
+         unsigned int       const vstep,
+         unsigned int       const hsamples,
+         float              const maxangle,
+         float              const astep,
+         float              const qmin,
+         bool               const fast,
+         bool               const verbose,
+         float *            const angleP) {
+
+    float a;
+    float da;
+    float lastq;        /* quality (s/n ratio) of last measurement */
+    
+    getBestAngleLocal(pamP, pixels, hstep, vstep, hsamples,
+                      -maxangle, maxangle, astep, verbose,
+                      &a, &lastq);
+
+    if ((a < -maxangle + astep / 2) || (a > maxangle - astep / 2))
+        /* extreme val almost certainly wrong */
+        abandon();
+    if (lastq < qmin)
+        /* insufficient s/n ratio */
+        abandon();
+
+    /* make a finer search in the neighborhood */
+    da = astep / 10;
+    getBestAngleLocal(pamP, pixels, hstep, vstep, hsamples,
+                      a - 9 * da, a + 9 * da, da, verbose,
+                      &a, &lastq);
+
+    /* iterate once more unless we don't need that much accuracy */
+    if (!fast) {
+        da /= 10;
+        getBestAngleLocal(pamP, pixels, hstep, vstep, hsamples,
+                          a - 9 * da, a + 9 * da, da, verbose,
+                          &a, &lastq);
+    }
+    *angleP = a;
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    struct pam pam;
+    sample ** pixels;       /* pixel data */
+    unsigned int hsamples; /* horizontal samples used */
+    unsigned int hstep;    /* horizontal step size */
+    unsigned int vstep;    /* vertical step size */
+    float angle;
+
+    pgm_init(&argc, argv);              /* initialize netpbm system */
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    readRelevantPixels(cmdline.inputFilename, cmdline.hstep, cmdline.vstep,
+                       &hstep, &vstep, &pixels, &pam, &hsamples);
+
+    replacePixelValuesWithScaledDiffs(&pam, pixels, hsamples, cmdline.dstep);
+
+    getAngle(&pam, pixels, hstep, vstep, hsamples,
+             cmdline.maxangle, cmdline.astep, cmdline.qmin,
+             cmdline.fast, cmdline.verbose, &angle);
+
+    /* report the result on stdout */
+    printf("%.2f\n", angle);
+
+    freePixels(pixels, pam.height);
+
+    return 0;
+}
diff --git a/analyzer/pbmminkowski.c b/analyzer/pbmminkowski.c
new file mode 100644
index 00000000..5edce506
--- /dev/null
+++ b/analyzer/pbmminkowski.c
@@ -0,0 +1,168 @@
+/* pbmminkowsky.c - read a portable bitmap and calculate the Minkowski Integrals
+**
+** Copyright (C) 2000 by Luuk van Dijk/Mind over Matter
+**
+** Based on pbmlife.c,
+** Copyright (C) 1988,1 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pbm.h"
+
+#define ISWHITE(x) ( (x) == PBM_WHITE )
+
+
+int main( int argc, char** argv ){
+
+  FILE* ifp;
+
+  bit* prevrow;
+  bit* thisrow;
+  bit* tmprow;
+  
+  int row;
+  int col; 
+
+  int countTile=0;
+  int countEdgeX=0;
+  int countEdgeY=0;
+  int countVertex=0;
+  
+  int rows;
+  int cols;
+  int format;
+
+  int area, perimeter, eulerchi;
+
+
+  /*
+   * parse arg and initialize
+   */ 
+
+  pbm_init( &argc, argv );
+  
+  if ( argc > 2 )
+    pm_usage( "[pbmfile]" );
+  
+  if ( argc == 2 )
+    ifp = pm_openr( argv[1] );
+  else
+    ifp = stdin;
+  
+  pbm_readpbminit( ifp, &cols, &rows, &format );
+
+  prevrow = pbm_allocrow( cols );
+  thisrow = pbm_allocrow( cols );
+
+
+  /* first row */
+
+  pbm_readpbmrow( ifp, thisrow, cols, format );
+
+  /* tiles */
+
+  for ( col = 0; col < cols; ++col ) 
+    if( ISWHITE(thisrow[col]) ) ++countTile;
+  
+  /* shortcut: for the first row, edgeY == countTile */
+  countEdgeY = countTile;
+
+  /* x-edges */
+
+  if( ISWHITE(thisrow[0]) ) ++countEdgeX;
+
+  for ( col = 0; col < cols-1; ++col ) 
+    if( ISWHITE(thisrow[col]) || ISWHITE(thisrow[col+1]) ) ++countEdgeX;
+
+  if( ISWHITE(thisrow[cols-1]) ) ++countEdgeX;
+
+  /* shortcut: for the first row, countVertex == countEdgeX */
+  
+  countVertex = countEdgeX;
+  
+
+  for ( row = 1; row < rows; ++row ){  
+    
+    tmprow = prevrow; 
+    prevrow = thisrow;
+    thisrow = tmprow;
+ 
+    pbm_readpbmrow( ifp, thisrow, cols, format );
+  
+    /* tiles */
+
+    for ( col = 0; col < cols; ++col ) 
+      if( ISWHITE(thisrow[col]) ) ++countTile;
+    
+    /* y-edges */
+
+    for ( col = 0; col < cols; ++col ) 
+      if( ISWHITE(thisrow[col]) || ISWHITE( prevrow[col] )) ++countEdgeY;
+    
+    /* x-edges */
+
+    if( ISWHITE(thisrow[0]) ) ++countEdgeX;
+
+    for ( col = 0; col < cols-1; ++col ) 
+      if( ISWHITE(thisrow[col]) || ISWHITE(thisrow[col+1]) ) ++countEdgeX;
+    
+    if( ISWHITE(thisrow[cols-1]) ) ++countEdgeX;
+    
+    /* vertices */
+
+    if( ISWHITE(thisrow[0]) || ISWHITE(prevrow[0]) ) ++countVertex;
+
+    for ( col = 0; col < cols-1; ++col ) 
+      if(    ISWHITE(thisrow[col]) || ISWHITE(thisrow[col+1]) 
+	  || ISWHITE(prevrow[col]) || ISWHITE(prevrow[col+1]) ) ++countVertex;
+
+    if( ISWHITE(thisrow[cols-1]) || ISWHITE(prevrow[cols-1]) ) ++countVertex;
+
+	  
+  } /* for row */
+
+  /* now thisrow contains the top row*/
+  /* tiles and x-edges have been counted, now y-edges and top vertices remain */
+
+  
+  /* y-edges */
+
+  for ( col = 0; col < cols; ++col ) 
+    if( ISWHITE(thisrow[col]) ) ++countEdgeY;
+
+  /* vertices */
+  
+  if( ISWHITE(thisrow[0]) ) ++countVertex;
+
+  for ( col = 0; col < cols-1; ++col ) 
+    if( ISWHITE(thisrow[col]) || ISWHITE(thisrow[col+1]) ) ++countVertex;
+
+  if( ISWHITE(thisrow[cols-1]) ) ++countVertex;
+
+
+  /* cleanup */
+
+  pm_close( ifp );
+
+  /* print results */
+
+  printf( "   tiles:\t%d\n x-edges:\t%d\n y-edges:\t%d\nvertices:\t%d\n",
+	  countTile, countEdgeX, countEdgeY,countVertex );
+
+  area      = countTile;
+  perimeter = 2*countEdgeX + 2*countEdgeY - 4*countTile;
+  eulerchi  = countTile - countEdgeX - countEdgeY + countVertex;
+
+  printf( "    area:\t%d\nperimeter:\t%d\n eulerchi:\t%d\n",
+	  area, perimeter, eulerchi );
+  
+  exit( 0 );
+
+} /* main */
+
diff --git a/analyzer/pgmhist.c b/analyzer/pgmhist.c
new file mode 100644
index 00000000..8f4e512e
--- /dev/null
+++ b/analyzer/pgmhist.c
@@ -0,0 +1,87 @@
+/* pgmhist.c - print a histogram of the values in a portable graymap
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pgm.h"
+#include "mallocvar.h"
+
+int
+main( argc, argv )
+    int argc;
+    char *argv[];
+{
+    FILE *ifp;
+    gray maxval, *grayrow;
+    register gray *gP;
+    int argn, rows, cols, format, row;
+    int i, *hist, *rcount, count, size;
+    register int col;
+    const char * const usage = "[pgmfile]";
+
+    pgm_init( &argc, argv );
+
+    argn = 1;
+
+    if ( argn < argc )
+	{
+        ifp = pm_openr( argv[argn] );
+        argn++;
+	}
+    else
+        ifp = stdin;
+
+    if ( argn != argc )
+        pm_usage( usage );
+
+    pgm_readpgminit( ifp, &cols, &rows, &maxval, &format );
+    grayrow = pgm_allocrow( cols );
+
+    /* Build histogram. */
+    MALLOCARRAY(hist, maxval + 1);
+    MALLOCARRAY(rcount, maxval + 1);
+    if ( hist == NULL || rcount == NULL )
+        pm_error( "out of memory" );
+    for ( i = 0; i <= maxval; i++ )
+        hist[i] = 0;
+    for ( row = 0; row < rows; row++ )
+	{
+        pgm_readpgmrow( ifp, grayrow, cols, maxval, format );
+        for ( col = 0, gP = grayrow; col < cols; col++, gP++ )
+            hist[(int) *gP]++;
+	}
+
+    pm_close( ifp );
+
+    /* Compute count-down */
+    count = 0;
+    for ( i = maxval; i >= 0; i-- )
+	{
+        count += hist[i];
+        rcount[i] = count;
+	}
+
+    /* And print it. */
+    printf( "value\tcount\tb%%\tw%%\n" );
+    printf( "-----\t-----\t--\t--\n" );
+    count = 0;
+    size = rows * cols;
+    for ( i = 0; i <= maxval; i++ )
+        if ( hist[i] > 0 )
+	    {
+            count += hist[i];
+            printf(
+                "%d\t%d\t%5.3g%%\t%5.3g%%\n", i, hist[i],
+                (float) count * 100.0 / size, 
+                (float) rcount[i] * 100.0 / size );
+	    }
+
+    exit( 0 );
+}
diff --git a/analyzer/pgmminkowski.c b/analyzer/pgmminkowski.c
new file mode 100644
index 00000000..dfb08429
--- /dev/null
+++ b/analyzer/pgmminkowski.c
@@ -0,0 +1,222 @@
+/* pgmminkowsky.c - read a portable graymap and calculate the Minkowski 
+** Integrals as a function of the threshold.
+**
+** Copyright (C) 2000 by Luuk van Dijk/Mind over Matter
+**
+** Based on pgmhist.c, 
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include "pgm.h"
+#include "mallocvar.h"
+
+
+#define MAX2(a,b) ( ( (a)>(b) ) ? (a) : (b) )
+#define MAX4(a,b,c,d) MAX2( MAX2((a),(b)), MAX2((c),(d)) )
+
+int main( int argc, char** argv ){
+  
+  FILE *ifp;
+
+  gray maxval;
+  int cols, rows, format;
+
+  gray* prevrow;
+  gray* thisrow;
+  gray* tmprow;
+  
+  int* countTile;   
+  int* countEdgeX;  
+  int* countEdgeY; 
+  int* countVertex; 
+
+  int i, col, row;
+
+  int maxtiles, maxedgex, maxedgey, maxvertex;
+  int area, perimeter, eulerchi;
+
+  double l2inv, linv;
+
+  /*
+   * parse arg and initialize
+   */ 
+
+  pgm_init( &argc, argv );
+
+  if ( argc > 2 ) pm_usage( "[pgmfile]" );
+  
+  if ( argc == 2 )
+    ifp = pm_openr( argv[1] );
+  else
+    ifp = stdin;
+
+  /*
+   * initialize
+   */
+
+  pgm_readpgminit( ifp, &cols, &rows, &maxval, &format );
+  
+  prevrow = pgm_allocrow( cols );
+  thisrow = pgm_allocrow( cols );
+  
+  MALLOCARRAY(countTile   , maxval + 1 );
+  MALLOCARRAY(countEdgeX  , maxval + 1 );
+  MALLOCARRAY(countEdgeY  , maxval + 1 );
+  MALLOCARRAY(countVertex , maxval + 1 );
+ 
+  if (countTile == NULL || countEdgeX == NULL || countEdgeY == NULL ||
+      countVertex == NULL)
+      pm_error( "out of memory" );
+  
+  for ( i = 0; i <= maxval; i++ ) countTile[i]   = 0;
+  for ( i = 0; i <= maxval; i++ ) countEdgeX[i]  = 0;
+  for ( i = 0; i <= maxval; i++ ) countEdgeY[i]  = 0;
+  for ( i = 0; i <= maxval; i++ ) countVertex[i] = 0;
+
+
+
+
+  /* first row */
+
+  pgm_readpgmrow( ifp, thisrow, cols, maxval, format );
+
+  /* tiles */
+
+  for ( col = 0; col < cols; ++col ) ++countTile[thisrow[col]]; 
+  
+  /* y-edges */
+
+  for ( col = 0; col < cols; ++col ) ++countEdgeY[thisrow[col]]; 
+
+  /* x-edges */
+
+  ++countEdgeX[thisrow[0]];
+
+  for ( col = 0; col < cols-1; ++col ) 
+    ++countEdgeX[ MAX2(thisrow[col], thisrow[col+1]) ];
+  
+  ++countEdgeX[thisrow[cols-1]];
+  
+  /* shortcut: for the first row, countVertex == countEdgeX */
+  
+  ++countVertex[thisrow[0]];
+
+  for ( col = 0; col < cols-1; ++col ) 
+    ++countVertex[ MAX2(thisrow[col], thisrow[col+1]) ];
+
+  ++countVertex[thisrow[cols-1]];
+
+  
+
+  for ( row = 1; row < rows; ++row ){  
+    
+    tmprow = prevrow; 
+    prevrow = thisrow;
+    thisrow = tmprow;
+ 
+    pgm_readpgmrow( ifp, thisrow, cols, maxval, format );
+  
+    /* tiles */
+
+    for ( col = 0; col < cols; ++col ) ++countTile[thisrow[col]]; 
+    
+    /* y-edges */
+    
+    for ( col = 0; col < cols; ++col ) 
+      ++countEdgeY[ MAX2(thisrow[col], prevrow[col]) ];
+    /* x-edges */
+    
+    ++countEdgeX[thisrow[0]];
+    
+    for ( col = 0; col < cols-1; ++col ) 
+      ++countEdgeX[ MAX2(thisrow[col], thisrow[col+1]) ];
+    
+    ++countEdgeX[thisrow[cols-1]];
+    
+    /* vertices */
+
+    ++countVertex[ MAX2(thisrow[0],prevrow[0]) ];
+
+    for ( col = 0; col < cols-1; ++col ) 
+      ++countVertex[
+        MAX4(thisrow[col], thisrow[col+1], prevrow[col], prevrow[col+1])
+      ];
+    
+    ++countVertex[ MAX2(thisrow[cols-1],prevrow[cols-1]) ];
+    
+  } /* for row */
+  
+  /* now thisrow contains the top row*/
+
+  /* tiles and x-edges have been counted, now upper
+     y-edges and top vertices remain */
+  
+  /* y-edges */
+
+  for ( col = 0; col < cols; ++col ) ++countEdgeY[ thisrow[col] ];
+
+  /* vertices */
+  
+  ++countVertex[thisrow[0]];
+
+  for ( col = 0; col < cols-1; ++col ) 
+    ++countVertex[ MAX2(thisrow[col],thisrow[col+1]) ];
+
+  ++countVertex[ thisrow[cols-1] ];
+
+
+  /* cleanup */
+
+  maxtiles =  rows    * cols;
+  maxedgex =  rows    * (cols+1);
+  maxedgey = (rows+1) *  cols;
+  maxvertex= (rows+1) * (cols+1);
+  
+  l2inv = 1.0/maxtiles;
+  linv  = 0.5/(rows+cols);
+
+  /* And print it. */
+  printf( "#threshold\t tiles\tx-edges\ty-edges\tvertices\n" );
+  printf( "#---------\t -----\t-------\t-------\t--------\n" );
+  for ( i = 0; i <= maxval; i++ ){
+
+    if( !(countTile[i] || countEdgeX[i] || countEdgeY[i] || countVertex[i] ) ) 
+      continue; /* skip empty slots */
+
+    area      = maxtiles;
+    perimeter = 2*maxedgex + 2*maxedgey - 4*maxtiles;
+    eulerchi  = maxtiles - maxedgex - maxedgey + maxvertex;
+
+    printf( "%f\t%6d\t%7d\t%7d\t%8d\t%g\t%g\t%6d\n", (float) i/(1.0*maxval), 
+        maxtiles, maxedgex, maxedgey, maxvertex,
+        area*l2inv, perimeter*linv, eulerchi
+        );
+
+
+    maxtiles -= countTile[i];
+    maxedgex -= countEdgeX[i];
+    maxedgey -= countEdgeY[i];
+    maxvertex-= countVertex[i];
+
+    /*  i, countTile[i], countEdgeX[i], countEdgeY[i], countVertex[i] */
+
+  }
+
+  /* these should be zero: */
+  printf( "#  check:\t%6d\t%7d\t%7d\t%8d\n", 
+          maxtiles, maxedgex, maxedgey, maxvertex );
+
+  pm_close( ifp );
+  
+  exit( 0 );
+  
+} /*main*/
+
+
diff --git a/analyzer/pgmtexture.c b/analyzer/pgmtexture.c
new file mode 100644
index 00000000..38eab114
--- /dev/null
+++ b/analyzer/pgmtexture.c
@@ -0,0 +1,1047 @@
+/* pgmtexture.c - calculate textural features on a portable graymap
+**
+** Author: James Darrell McCauley
+**         Texas Agricultural Experiment Station
+**         Department of Agricultural Engineering
+**         Texas A&M University
+**         College Station, Texas 77843-2117 USA
+**
+** Code written partially taken from pgmtofs.c in the PBMPLUS package
+** by Jef Poskanzer.
+**
+** Algorithms for calculating features (and some explanatory comments) are
+** taken from:
+**
+**   Haralick, R.M., K. Shanmugam, and I. Dinstein. 1973. Textural features
+**   for image classification.  IEEE Transactions on Systems, Man, and
+**   Cybertinetics, SMC-3(6):610-621.
+**
+** Copyright (C) 1991 Texas Agricultural Experiment Station, employer for
+** hire of James Darrell McCauley
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+**
+** THE TEXAS AGRICULTURAL EXPERIMENT STATION (TAES) AND THE TEXAS A&M
+** UNIVERSITY SYSTEM (TAMUS) MAKE NO EXPRESS OR IMPLIED WARRANTIES
+** (INCLUDING BY WAY OF EXAMPLE, MERCHANTABILITY) WITH RESPECT TO ANY
+** ITEM, AND SHALL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL
+** OR CONSEQUENTAL DAMAGES ARISING OUT OF THE POSESSION OR USE OF
+** ANY SUCH ITEM. LICENSEE AND/OR USER AGREES TO INDEMNIFY AND HOLD
+** TAES AND TAMUS HARMLESS FROM ANY CLAIMS ARISING OUT OF THE USE OR
+** POSSESSION OF SUCH ITEMS.
+** 
+** Modification History:
+** 24 Jun 91 - J. Michael Carstensen <jmc@imsor.dth.dk> supplied fix for 
+**             correlation function.
+**
+** 05 Oct 05 - Marc Breithecker <Marc.Breithecker@informatik.uni-erlangen.de>
+**             Fix calculation or normalizing constants for d > 1.
+*/
+
+#include <math.h>
+
+#include "pm_c_util.h"
+#include "pgm.h"
+#include "mallocvar.h"
+
+#define RADIX 2.0
+#define EPSILON 0.000000001
+#define BL  "Angle                 "
+#define F1  "Angular Second Moment "
+#define F2  "Contrast              "
+#define F3  "Correlation           "
+#define F4  "Variance              "
+#define F5  "Inverse Diff Moment   "
+#define F6  "Sum Average           "
+#define F7  "Sum Variance          "
+#define F8  "Sum Entropy           "
+#define F9  "Entropy               "
+#define F10 "Difference Variance   "
+#define F11 "Difference Entropy    "
+#define F12 "Meas of Correlation-1 "
+#define F13 "Meas of Correlation-2 "
+#define F14 "Max Correlation Coeff "
+
+#define SIGN(x,y) ((y)<0 ? -fabs(x) : fabs(x))
+#define DOT fprintf(stderr,".")
+#define SWAP(a,b) {y=(a);(a)=(b);(b)=y;}
+
+
+static bool sortit = FALSE;
+
+static float *
+vector (int nl, int nh)
+{
+    float *v;
+
+    MALLOCARRAY(v, (unsigned) (nh - nl + 1));
+    if (v == NULL)
+        pm_error("Unable to allocate memory for a vector.");
+    return v - nl;
+}
+
+
+static float **
+matrix (int nrl, int nrh, int ncl, int nch)
+
+/* Allocates a float matrix with range [nrl..nrh][ncl..nch] */
+{
+    int i;
+    float **m;
+
+    /* allocate pointers to rows */
+    MALLOCARRAY(m, (unsigned) (nrh - nrl + 1));
+    if (m == NULL)
+        pm_error("Unable to allocate memory for a matrix.");
+
+    m -= ncl;
+
+    /* allocate rows and set pointers to them */
+    for (i = nrl; i <= nrh; i++)
+    {
+        MALLOCARRAY(m[i], (unsigned) (nch - ncl + 1));
+        if (m[i] == NULL)
+            pm_error("Unable to allocate memory for a matrix row.");
+        m[i] -= ncl;
+    }
+    /* return pointer to array of pointers to rows */
+    return m;
+}
+
+static void 
+results (const char * const c, const float * const a)
+{
+    int i;
+
+    DOT;
+    fprintf (stdout, "%s", c);
+    for (i = 0; i < 4; ++i)
+        fprintf (stdout, "% 1.3e ", a[i]);
+    fprintf (stdout, "% 1.3e\n", (a[0] + a[1] + a[2] + a[3]) / 4);
+}
+
+static void 
+simplesrt (int n, float arr[])
+{
+    int i, j;
+    float a;
+
+    for (j = 2; j <= n; j++)
+    {
+        a = arr[j];
+        i = j - 1;
+        while (i > 0 && arr[i] > a)
+        {
+            arr[i + 1] = arr[i];
+            i--;
+        }
+        arr[i + 1] = a;
+    }
+}
+
+static void 
+mkbalanced (float **a, int n)
+{
+    int last, j, i;
+    float s, r, g, f, c, sqrdx;
+
+    sqrdx = RADIX * RADIX;
+    last = 0;
+    while (last == 0)
+    {
+        last = 1;
+        for (i = 1; i <= n; i++)
+        {
+            r = c = 0.0;
+            for (j = 1; j <= n; j++)
+                if (j != i)
+                {
+                    c += fabs (a[j][i]);
+                    r += fabs (a[i][j]);
+                }
+            if (c && r)
+            {
+                g = r / RADIX;
+                f = 1.0;
+                s = c + r;
+                while (c < g)
+                {
+                    f *= RADIX;
+                    c *= sqrdx;
+                }
+                g = r * RADIX;
+                while (c > g)
+                {
+                    f /= RADIX;
+                    c /= sqrdx;
+                }
+                if ((c + r) / f < 0.95 * s)
+                {
+                    last = 0;
+                    g = 1.0 / f;
+                    for (j = 1; j <= n; j++)
+                        a[i][j] *= g;
+                    for (j = 1; j <= n; j++)
+                        a[j][i] *= f;
+                }
+            }
+        }
+    }
+}
+
+
+static void 
+reduction (float **a, int n)
+{
+    int m, j, i;
+    float y, x;
+
+    for (m = 2; m < n; m++)
+    {
+        x = 0.0;
+        i = m;
+        for (j = m; j <= n; j++)
+        {
+            if (fabs (a[j][m - 1]) > fabs (x))
+            {
+                x = a[j][m - 1];
+                i = j;
+            }
+        }
+        if (i != m)
+        {
+            for (j = m - 1; j <= n; j++)
+                SWAP (a[i][j], a[m][j])  
+                    for (j = 1; j <= n; j++)
+                        SWAP (a[j][i], a[j][m]) 
+                            a[j][i] = a[j][i];
+        }
+        if (x)
+        {
+            for (i = m + 1; i <= n; i++)
+            {
+                if ((y = a[i][m - 1]))
+                {
+                    y /= x;
+                    a[i][m - 1] = y;
+                    for (j = m; j <= n; j++)
+                        a[i][j] -= y * a[m][j];
+                    for (j = 1; j <= n; j++)
+                        a[j][m] += y * a[j][i];
+                }
+            }
+        }
+    }
+}
+
+
+
+static void 
+hessenberg (float **a, int n, float wr[], float wi[])
+
+{
+    int nn, m, l, k, j, its, i, mmin;
+    float z, y, x, w, v, u, t, s, r, q, p, anorm;
+
+    anorm = fabs (a[1][1]);
+    for (i = 2; i <= n; i++)
+        for (j = (i - 1); j <= n; j++)
+            anorm += fabs (a[i][j]);
+    nn = n;
+    t = 0.0;
+    while (nn >= 1)
+    {
+        its = 0;
+        do
+        {
+            for (l = nn; l >= 2; l--)
+            {
+                s = fabs (a[l - 1][l - 1]) + fabs (a[l][l]);
+                if (s == 0.0)
+                    s = anorm;
+                if ((float) (fabs (a[l][l - 1]) + s) == s)
+                    break;
+            }
+            x = a[nn][nn];
+            if (l == nn)
+            {
+                wr[nn] = x + t;
+                wi[nn--] = 0.0;
+            }
+            else
+            {
+                y = a[nn - 1][nn - 1];
+                w = a[nn][nn - 1] * a[nn - 1][nn];
+                if (l == (nn - 1))
+                {
+                    p = 0.5 * (y - x);
+                    q = p * p + w;
+                    z = sqrt (fabs (q));
+                    x += t;
+                    if (q >= 0.0)
+                    {
+                        z = p + SIGN (z, p); 
+                        wr[nn - 1] = wr[nn] = x + z;
+                        if (z)
+                            wr[nn] = x - w / z;
+                        wi[nn - 1] = wi[nn] = 0.0;
+                    }
+                    else
+                    {
+                        wr[nn - 1] = wr[nn] = x + p;
+                        wi[nn - 1] = -(wi[nn] = z);
+                    }
+                    nn -= 2;
+                }
+                else
+                {
+                    if (its == 30)
+                        pm_error("Too many iterations to required "
+                                 "to find %s.  Giving up", F14);
+                    if (its == 10 || its == 20)
+                    {
+                        t += x;
+                        for (i = 1; i <= nn; i++)
+                            a[i][i] -= x;
+                        s = fabs (a[nn][nn - 1]) + fabs (a[nn - 1][nn - 2]);
+                        y = x = 0.75 * s;
+                        w = -0.4375 * s * s;
+                    }
+                    ++its;
+                    for (m = (nn - 2); m >= l; m--)
+                    {
+                        z = a[m][m];
+                        r = x - z;
+                        s = y - z;
+                        p = (r * s - w) / a[m + 1][m] + a[m][m + 1];
+                        q = a[m + 1][m + 1] - z - r - s;
+                        r = a[m + 2][m + 1];
+                        s = fabs (p) + fabs (q) + fabs (r);
+                        p /= s;
+                        q /= s;
+                        r /= s;
+                        if (m == l)
+                            break;
+                        u = fabs (a[m][m - 1]) * (fabs (q) + fabs (r));
+                        v = fabs (p) * (fabs (a[m - 1][m - 1]) + fabs (z) + 
+                                        fabs (a[m + 1][m + 1]));
+                        if ((float) (u + v) == v)
+                            break;
+                    }
+                    for (i = m + 2; i <= nn; i++)
+                    {
+                        a[i][i - 2] = 0.0;
+                        if (i != (m + 2))
+                            a[i][i - 3] = 0.0;
+                    }
+                    for (k = m; k <= nn - 1; k++)
+                    {
+                        if (k != m)
+                        {
+                            p = a[k][k - 1];
+                            q = a[k + 1][k - 1];
+                            r = 0.0;
+                            if (k != (nn - 1))
+                                r = a[k + 2][k - 1];
+                            if ((x = fabs (p) + fabs (q) + fabs (r)))
+                            {
+                                p /= x;
+                                q /= x;
+                                r /= x;
+                            }
+                        }
+                        if ((s = SIGN (sqrt (p * p + q * q + r * r), p))) 
+                        {
+                            if (k == m)
+                            {
+                                if (l != m)
+                                    a[k][k - 1] = -a[k][k - 1];
+                            }
+                            else
+                                a[k][k - 1] = -s * x;
+                            p += s;
+                            x = p / s;
+                            y = q / s;
+                            z = r / s;
+                            q /= p;
+                            r /= p;
+                            for (j = k; j <= nn; j++)
+                            {
+                                p = a[k][j] + q * a[k + 1][j];
+                                if (k != (nn - 1))
+                                {
+                                    p += r * a[k + 2][j];
+                                    a[k + 2][j] -= p * z;
+                                }
+                                a[k + 1][j] -= p * y;
+                                a[k][j] -= p * x;
+                            }
+                            mmin = nn < k + 3 ? nn : k + 3;
+                            for (i = l; i <= mmin; i++)
+                            {
+                                p = x * a[i][k] + y * a[i][k + 1];
+                                if (k != (nn - 1))
+                                {
+                                    p += z * a[i][k + 2];
+                                    a[i][k + 2] -= p * r;
+                                }
+                                a[i][k + 1] -= p * q;
+                                a[i][k] -= p;
+                            }
+                        }
+                    }
+                }
+            }
+        } while (l < nn - 1);
+    }
+}
+
+
+
+static float 
+f1_asm (float **P, int Ng)
+
+/* Angular Second Moment */
+{
+    int i, j;
+    float sum = 0;
+
+    for (i = 0; i < Ng; ++i)
+        for (j = 0; j < Ng; ++j)
+            sum += P[i][j] * P[i][j];
+
+    return sum;
+
+    /*
+     * The angular second-moment feature (ASM) f1 is a measure of homogeneity
+     * of the image. In a homogeneous image, there are very few dominant
+     * gray-tone transitions. Hence the P matrix for such an image will have
+     * fewer entries of large magnitude.
+     */
+}
+
+
+static float 
+f2_contrast (float **P, int Ng)
+
+/* Contrast */
+{
+    int i, j, n;
+    float sum = 0, bigsum = 0;
+
+    for (n = 0; n < Ng; ++n)
+    {
+        for (i = 0; i < Ng; ++i)
+            for (j = 0; j < Ng; ++j)
+                if ((i - j) == n || (j - i) == n)
+                    sum += P[i][j];
+        bigsum += n * n * sum;
+
+        sum = 0;
+    }
+    return bigsum;
+
+    /*
+     * The contrast feature is a difference moment of the P matrix and is a
+     * measure of the contrast or the amount of local variations present in an
+     * image.
+     */
+}
+
+static float 
+f3_corr (float **P, int Ng)
+
+/* Correlation */
+{
+    int i, j;
+    float sum_sqrx = 0, sum_sqry = 0, tmp, *px;
+    float meanx =0 , meany = 0 , stddevx, stddevy;
+
+    px = vector (0, Ng);
+    for (i = 0; i < Ng; ++i)
+        px[i] = 0;
+
+    /*
+     * px[i] is the (i-1)th entry in the marginal probability matrix obtained
+     * by summing the rows of p[i][j]
+     */
+    for (i = 0; i < Ng; ++i)
+        for (j = 0; j < Ng; ++j)
+            px[i] += P[i][j];
+
+
+    /* Now calculate the means and standard deviations of px and py */
+    /*- fix supplied by J. Michael Christensen, 21 Jun 1991 */
+    /*- further modified by James Darrell McCauley, 16 Aug 1991 
+     *     after realizing that meanx=meany and stddevx=stddevy
+     */
+    for (i = 0; i < Ng; ++i)
+    {
+        meanx += px[i]*i;
+        sum_sqrx += px[i]*i*i;
+    }
+    meany = meanx;
+    sum_sqry = sum_sqrx;
+    stddevx = sqrt (sum_sqrx - (meanx * meanx));
+    stddevy = stddevx;
+
+    /* Finally, the correlation ... */
+    for (tmp = 0, i = 0; i < Ng; ++i)
+        for (j = 0; j < Ng; ++j)
+            tmp += i*j*P[i][j];
+
+    return (tmp - meanx * meany) / (stddevx * stddevy);
+    /*
+     * This correlation feature is a measure of gray-tone linear-dependencies
+     * in the image.
+     */
+}
+
+
+static float 
+f4_var (float **P, int Ng)
+
+/* Sum of Squares: Variance */
+{
+    int i, j;
+    float mean = 0, var = 0;
+
+    /*- Corrected by James Darrell McCauley, 16 Aug 1991
+     *  calculates the mean intensity level instead of the mean of
+     *  cooccurrence matrix elements 
+     */
+    for (i = 0; i < Ng; ++i)
+        for (j = 0; j < Ng; ++j)
+            mean += i * P[i][j];
+
+    for (i = 0; i < Ng; ++i)
+        for (j = 0; j < Ng; ++j)
+            var += (i + 1 - mean) * (i + 1 - mean) * P[i][j];
+
+    return var;
+}
+
+static float 
+f5_idm (float **P, int Ng)
+
+/* Inverse Difference Moment */
+{
+    int i, j;
+    float idm = 0;
+
+    for (i = 0; i < Ng; ++i)
+        for (j = 0; j < Ng; ++j)
+            idm += P[i][j] / (1 + (i - j) * (i - j));
+
+    return idm;
+}
+
+static float 
+Pxpy[2 * PGM_MAXMAXVAL];
+
+static float 
+f6_savg (float **P, int Ng)
+
+/* Sum Average */
+{
+    int i, j;
+    float savg = 0;
+
+    for (i = 0; i <= 2 * Ng; ++i)
+        Pxpy[i] = 0;
+
+    for (i = 0; i < Ng; ++i)
+        for (j = 0; j < Ng; ++j)
+            Pxpy[i + j + 2] += P[i][j];
+    for (i = 2; i <= 2 * Ng; ++i)
+        savg += i * Pxpy[i];
+
+    return savg;
+}
+
+
+static float 
+f7_svar (float **P, int Ng, float S) {
+/* Sum Variance */
+    int i, j;
+    float var = 0;
+
+    for (i = 0; i <= 2 * Ng; ++i)
+        Pxpy[i] = 0;
+
+    for (i = 0; i < Ng; ++i)
+        for (j = 0; j < Ng; ++j)
+            Pxpy[i + j + 2] += P[i][j];
+
+    for (i = 2; i <= 2 * Ng; ++i)
+        var += (i - S) * (i - S) * Pxpy[i];
+
+    return var;
+}
+
+static float 
+f8_sentropy (float **P, int Ng)
+
+/* Sum Entropy */
+{
+    int i, j;
+    float sentropy = 0;
+
+    for (i = 0; i <= 2 * Ng; ++i)
+        Pxpy[i] = 0;
+
+    for (i = 0; i < Ng; ++i)
+        for (j = 0; j < Ng; ++j)
+            Pxpy[i + j + 2] += P[i][j];
+
+    for (i = 2; i <= 2 * Ng; ++i)
+        sentropy -= Pxpy[i] * log10 (Pxpy[i] + EPSILON);
+
+    return sentropy;
+}
+
+
+static float 
+f9_entropy (float **P, int Ng)
+
+/* Entropy */
+{
+    int i, j;
+    float entropy = 0;
+
+    for (i = 0; i < Ng; ++i)
+        for (j = 0; j < Ng; ++j)
+            entropy += P[i][j] * log10 (P[i][j] + EPSILON);
+
+    return -entropy;
+}
+
+
+static float 
+f10_dvar (float **P, int Ng)
+
+/* Difference Variance */
+{
+    int i, j, tmp;
+    float sum = 0, sum_sqr = 0, var = 0;
+
+    for (i = 0; i <= 2 * Ng; ++i)
+        Pxpy[i] = 0;
+
+    for (i = 0; i < Ng; ++i)
+        for (j = 0; j < Ng; ++j)
+            Pxpy[abs (i - j)] += P[i][j];
+
+    /* Now calculate the variance of Pxpy (Px-y) */
+    for (i = 0; i < Ng; ++i)
+    {
+        sum += Pxpy[i];
+        sum_sqr += Pxpy[i] * Pxpy[i];
+    }
+    tmp = Ng * Ng;
+    var = ((tmp * sum_sqr) - (sum * sum)) / (tmp * tmp);
+
+    return var;
+}
+
+static float 
+f11_dentropy (float **P, int Ng)
+    
+/* Difference Entropy */
+{
+    int i, j;
+    float sum = 0;
+
+    for (i = 0; i <= 2 * Ng; ++i)
+        Pxpy[i] = 0;
+
+    for (i = 0; i < Ng; ++i)
+        for (j = 0; j < Ng; ++j)
+            Pxpy[abs (i - j)] += P[i][j];
+
+    for (i = 0; i < Ng; ++i)
+        sum += Pxpy[i] * log10 (Pxpy[i] + EPSILON);
+
+    return -sum;
+}
+
+static float 
+f12_icorr (float **P, int Ng)
+
+/* Information Measures of Correlation */
+{
+    int i, j;
+    float *px, *py;
+    float hx = 0, hy = 0, hxy = 0, hxy1 = 0, hxy2 = 0;
+
+    px = vector (0, Ng);
+    py = vector (0, Ng);
+
+    /*
+     * px[i] is the (i-1)th entry in the marginal probability matrix obtained
+     * by summing the rows of p[i][j]
+     */
+    for (i = 0; i < Ng; ++i)
+    {
+        for (j = 0; j < Ng; ++j)
+        {
+            px[i] += P[i][j];
+            py[j] += P[i][j];
+        }
+    }
+
+    for (i = 0; i < Ng; ++i)
+        for (j = 0; j < Ng; ++j)
+        {
+            hxy1 -= P[i][j] * log10 (px[i] * py[j] + EPSILON);
+            hxy2 -= px[i] * py[j] * log10 (px[i] * py[j] + EPSILON);
+            hxy -= P[i][j] * log10 (P[i][j] + EPSILON);
+        }
+
+    /* Calculate entropies of px and py - is this right? */
+    for (i = 0; i < Ng; ++i)
+    {
+        hx -= px[i] * log10 (px[i] + EPSILON);
+        hy -= py[i] * log10 (py[i] + EPSILON);
+    }
+/*  fprintf(stderr,"hxy1=%f\thxy=%f\thx=%f\thy=%f\n",hxy1,hxy,hx,hy); */
+    return ((hxy - hxy1) / (hx > hy ? hx : hy));
+}
+
+static float 
+f13_icorr (float **P, int Ng)
+
+/* Information Measures of Correlation */
+{
+    int i, j;
+    float *px, *py;
+    float hx = 0, hy = 0, hxy = 0, hxy1 = 0, hxy2 = 0;
+
+    px = vector (0, Ng);
+    py = vector (0, Ng);
+
+    /*
+     * px[i] is the (i-1)th entry in the marginal probability matrix obtained
+     * by summing the rows of p[i][j]
+     */
+    for (i = 0; i < Ng; ++i)
+    {
+        for (j = 0; j < Ng; ++j)
+        {
+            px[i] += P[i][j];
+            py[j] += P[i][j];
+        }
+    }
+
+    for (i = 0; i < Ng; ++i)
+        for (j = 0; j < Ng; ++j)
+        {
+            hxy1 -= P[i][j] * log10 (px[i] * py[j] + EPSILON);
+            hxy2 -= px[i] * py[j] * log10 (px[i] * py[j] + EPSILON);
+            hxy -= P[i][j] * log10 (P[i][j] + EPSILON);
+        }
+
+    /* Calculate entropies of px and py */
+    for (i = 0; i < Ng; ++i)
+    {
+        hx -= px[i] * log10 (px[i] + EPSILON);
+        hy -= py[i] * log10 (py[i] + EPSILON);
+    }
+    /* fprintf(stderr,"hx=%f\thxy2=%f\n",hx,hxy2); */
+    return (sqrt (fabs (1 - exp (-2.0 * (hxy2 - hxy)))));
+}
+
+static float 
+f14_maxcorr (float **P, int Ng)
+
+/* Returns the Maximal Correlation Coefficient */
+{
+    int i, j, k;
+    float *px, *py, **Q;
+    float *x, *iy, tmp;
+
+    px = vector (0, Ng);
+    py = vector (0, Ng);
+    Q = matrix (1, Ng + 1, 1, Ng + 1);
+    x = vector (1, Ng);
+    iy = vector (1, Ng);
+
+    /*
+     * px[i] is the (i-1)th entry in the marginal probability matrix obtained
+     * by summing the rows of p[i][j]
+     */
+    for (i = 0; i < Ng; ++i)
+    {
+        for (j = 0; j < Ng; ++j)
+        {
+            px[i] += P[i][j];
+            py[j] += P[i][j];
+        }
+    }
+
+    /* Find the Q matrix */
+    for (i = 0; i < Ng; ++i)
+    {
+        for (j = 0; j < Ng; ++j)
+        {
+            Q[i + 1][j + 1] = 0;
+            for (k = 0; k < Ng; ++k)
+                Q[i + 1][j + 1] += P[i][k] * P[j][k] / px[i] / py[k];
+        }
+    }
+
+    /* Balance the matrix */
+    mkbalanced (Q, Ng);
+    /* Reduction to Hessenberg Form */
+    reduction (Q, Ng);
+    /* Finding eigenvalue for nonsymetric matrix using QR algorithm */
+    hessenberg (Q, Ng, x, iy);
+    if (sortit)
+        simplesrt(Ng,x);
+    /* Returns the sqrt of the second largest eigenvalue of Q */
+    for (i = 2, tmp = x[1]; i <= Ng; ++i)
+        tmp = (tmp > x[i]) ? tmp : x[i];
+    return sqrt (x[Ng - 1]);
+}
+
+int
+main (int argc, char *argv[]) {
+    FILE *ifp;
+    register gray **grays;
+    int tone[PGM_MAXMAXVAL], R0, R45, R90, angle, d = 1, x, y;
+    int argn, rows, cols, row, col;
+    int itone, jtone, tones;
+    float **P_matrix0, **P_matrix45, **P_matrix90, **P_matrix135;
+    float ASM[4], contrast[4], corr[4], var[4], idm[4], savg[4];
+    float sentropy[4], svar[4], entropy[4], dvar[4], dentropy[4];
+    float icorr[4], maxcorr[4];
+    gray maxval;
+    const char * const usage = "[-d <d>] [pgmfile]";
+
+
+    pgm_init( &argc, argv );
+
+    argn = 1;
+
+    /* Check for flags. */
+    if ( argn < argc && argv[argn][0] == '-' )
+    {
+        if ( argv[argn][1] == 'd' )
+        {
+            ++argn;
+            if ( argn == argc || sscanf( argv[argn], "%d", &d ) != 1 )
+                pm_usage( usage );
+        }
+        else
+            pm_usage( usage );
+        ++argn;
+    }
+
+    if ( argn < argc )
+    {
+        ifp = pm_openr( argv[argn] );
+        ++argn;
+    }
+    else
+        ifp = stdin;
+
+    if ( argn != argc )
+        pm_usage( usage );
+
+    grays = pgm_readpgm (ifp, &cols, &rows, &maxval);
+    pm_close (ifp);
+
+    if (maxval > PGM_MAXMAXVAL) 
+        pm_error("The maxval of the image (%d) is too high.  \n"
+                 "This program's maximum is %d.", maxval, PGM_MAXMAXVAL);
+
+    /* Determine the number of different gray scales (not maxval) */
+    for (row = PGM_MAXMAXVAL; row >= 0; --row)
+        tone[row] = -1;
+    for (row = rows - 1; row >= 0; --row)
+        for (col = 0; col < cols; ++col)
+            tone[grays[row][col]] = grays[row][col];
+    for (row = PGM_MAXMAXVAL, tones = 0; row >= 0; --row)
+        if (tone[row] != -1)
+            tones++;
+    pm_message("(Image has %d graylevels.)", tones);
+
+    /* Collapse array, taking out all zero values */
+    for (row = 0, itone = 0; row <= PGM_MAXMAXVAL; row++)
+        if (tone[row] != -1)
+            tone[itone++] = tone[row];
+    /* Now array contains only the gray levels present (in ascending order) */
+
+    /* Allocate memory for gray-tone spatial dependence matrix */
+    P_matrix0 = matrix (0, tones, 0, tones);
+    P_matrix45 = matrix (0, tones, 0, tones);
+    P_matrix90 = matrix (0, tones, 0, tones);
+    P_matrix135 = matrix (0, tones, 0, tones);
+    for (row = 0; row < tones; ++row)
+        for (col = 0; col < tones; ++col)
+        {
+            P_matrix0[row][col] = P_matrix45[row][col] = 0;
+            P_matrix90[row][col] = P_matrix135[row][col] = 0;
+        }
+
+    /* Find gray-tone spatial dependence matrix */
+    fprintf (stderr, "(Computing spatial dependence matrix...");
+    for (row = 0; row < rows; ++row)
+        for (col = 0; col < cols; ++col)
+            for (x = 0, angle = 0; angle <= 135; angle += 45)
+            {
+                while (tone[x] != grays[row][col])
+                    x++;
+                if (angle == 0 && col + d < cols)
+                {
+                    y = 0;
+                    while (tone[y] != grays[row][col + d])
+                        y++;
+                    P_matrix0[x][y]++;
+                    P_matrix0[y][x]++;
+                }
+                if (angle == 90 && row + d < rows)
+                {
+                    y = 0;
+                    while (tone[y] != grays[row + d][col])
+                        y++;
+                    P_matrix90[x][y]++;
+                    P_matrix90[y][x]++;
+                }
+                if (angle == 45 && row + d < rows && col - d >= 0)
+                {
+                    y = 0;
+                    while (tone[y] != grays[row + d][col - d])
+                        y++;
+                    P_matrix45[x][y]++;
+                    P_matrix45[y][x]++;
+                }
+                if (angle == 135 && row + d < rows && col + d < cols)
+                {
+                    y = 0;
+                    while (tone[y] != grays[row + d][col + d])
+                        y++;
+                    P_matrix135[x][y]++;
+                    P_matrix135[y][x]++;
+                }
+            }
+    /* Gray-tone spatial dependence matrices are complete */
+
+    /* Find normalizing constants */
+    R0 = 2 * rows * (cols - d);
+    R45 = 2 * (rows - d) * (cols - d);
+    R90 = 2 * (rows - d) * cols;
+
+    /* Normalize gray-tone spatial dependence matrix */
+    for (itone = 0; itone < tones; ++itone)
+        for (jtone = 0; jtone < tones; ++jtone)
+        {
+            P_matrix0[itone][jtone] /= R0;
+            P_matrix45[itone][jtone] /= R45;
+            P_matrix90[itone][jtone] /= R90;
+            P_matrix135[itone][jtone] /= R45;
+        }
+
+    fprintf (stderr, " done.)\n");
+    fprintf (stderr, "(Computing textural features");
+    fprintf (stdout, "\n");
+    DOT;
+    fprintf (stdout,
+             "%s         0         45         90        135        Avg\n",
+             BL);
+
+    ASM[0] = f1_asm (P_matrix0, tones);
+    ASM[1] = f1_asm (P_matrix45, tones);
+    ASM[2] = f1_asm (P_matrix90, tones);
+    ASM[3] = f1_asm (P_matrix135, tones);
+    results (F1, ASM);
+
+    contrast[0] = f2_contrast (P_matrix0, tones);
+    contrast[1] = f2_contrast (P_matrix45, tones);
+    contrast[2] = f2_contrast (P_matrix90, tones);
+    contrast[3] = f2_contrast (P_matrix135, tones);
+    results (F2, contrast);
+
+
+    corr[0] = f3_corr (P_matrix0, tones);
+    corr[1] = f3_corr (P_matrix45, tones);
+    corr[2] = f3_corr (P_matrix90, tones);
+    corr[3] = f3_corr (P_matrix135, tones);
+    results (F3, corr);
+
+    var[0] = f4_var (P_matrix0, tones);
+    var[1] = f4_var (P_matrix45, tones);
+    var[2] = f4_var (P_matrix90, tones);
+    var[3] = f4_var (P_matrix135, tones);
+    results (F4, var);
+
+
+    idm[0] = f5_idm (P_matrix0, tones);
+    idm[1] = f5_idm (P_matrix45, tones);
+    idm[2] = f5_idm (P_matrix90, tones);
+    idm[3] = f5_idm (P_matrix135, tones);
+    results (F5, idm);
+
+    savg[0] = f6_savg (P_matrix0, tones);
+    savg[1] = f6_savg (P_matrix45, tones);
+    savg[2] = f6_savg (P_matrix90, tones);
+    savg[3] = f6_savg (P_matrix135, tones);
+    results (F6, savg);
+
+    sentropy[0] = f8_sentropy (P_matrix0, tones);
+    sentropy[1] = f8_sentropy (P_matrix45, tones);
+    sentropy[2] = f8_sentropy (P_matrix90, tones);
+    sentropy[3] = f8_sentropy (P_matrix135, tones);
+    svar[0] = f7_svar (P_matrix0, tones, sentropy[0]);
+    svar[1] = f7_svar (P_matrix45, tones, sentropy[1]);
+    svar[2] = f7_svar (P_matrix90, tones, sentropy[2]);
+    svar[3] = f7_svar (P_matrix135, tones, sentropy[3]);
+    results (F7, svar);
+    results (F8, sentropy);
+
+    entropy[0] = f9_entropy (P_matrix0, tones);
+    entropy[1] = f9_entropy (P_matrix45, tones);
+    entropy[2] = f9_entropy (P_matrix90, tones);
+    entropy[3] = f9_entropy (P_matrix135, tones);
+    results (F9, entropy);
+
+    dvar[0] = f10_dvar (P_matrix0, tones);
+    dvar[1] = f10_dvar (P_matrix45, tones);
+    dvar[2] = f10_dvar (P_matrix90, tones);
+    dvar[3] = f10_dvar (P_matrix135, tones);
+    results (F10, dvar);
+
+    dentropy[0] = f11_dentropy (P_matrix0, tones);
+    dentropy[1] = f11_dentropy (P_matrix45, tones);
+    dentropy[2] = f11_dentropy (P_matrix90, tones);
+    dentropy[3] = f11_dentropy (P_matrix135, tones);
+    results (F11, dentropy);
+
+    icorr[0] = f12_icorr (P_matrix0, tones);
+    icorr[1] = f12_icorr (P_matrix45, tones);
+    icorr[2] = f12_icorr (P_matrix90, tones);
+    icorr[3] = f12_icorr (P_matrix135, tones);
+    results (F12, icorr);
+
+    icorr[0] = f13_icorr (P_matrix0, tones);
+    icorr[1] = f13_icorr (P_matrix45, tones);
+    icorr[2] = f13_icorr (P_matrix90, tones);
+    icorr[3] = f13_icorr (P_matrix135, tones);
+    results (F13, icorr);
+
+    maxcorr[0] = f14_maxcorr (P_matrix0, tones);
+    maxcorr[1] = f14_maxcorr (P_matrix45, tones);
+    maxcorr[2] = f14_maxcorr (P_matrix90, tones);
+    maxcorr[3] = f14_maxcorr (P_matrix135, tones);
+    results (F14, maxcorr);
+
+
+    fprintf (stderr, " done.)\n");
+
+    return 0;
+}
diff --git a/analyzer/pnmhistmap.c b/analyzer/pnmhistmap.c
new file mode 100644
index 00000000..0b1eeb1f
--- /dev/null
+++ b/analyzer/pnmhistmap.c
@@ -0,0 +1,483 @@
+/* pnmhistmap.c -
+ *  Draw a histogram for a PGM or PPM file
+ *
+ * Options: -verbose: the usual
+ *      -max N: force scaling value to N
+ *      -black: ignore all-black count
+ *      -white: ignore all-white count
+ *
+ * - PGM histogram is a PBM file, PPM histogram is a PPM file
+ * - No conditional code - assumes all three: PBM, PGM, PPM
+ *
+ * Copyright (C) 1993 by Wilson H. Bent, Jr (whb@usc.edu)
+ *
+ * 2004-12-11 john h. dubois iii (john@armory.com)
+ * - Added options:
+ *   -dots, -nmax, -red, -green, -blue, -width, -height, -lval, -rval
+ * - Deal properly with maxvals other than 256
+ */
+
+#include <string.h>
+
+#include "pnm.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+static double const epsilon = .00001;
+
+#define SCALE_H(value) (hscale_unity ? (value) : (int)((value) * hscale))
+
+enum wantedColor {WANT_RED=0, WANT_GRN=1, WANT_BLU=2};
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *inputFilespec;  /* Filespecs of input files */
+    unsigned int black;
+    unsigned int white;
+    unsigned int dots;
+    bool         colorWanted[3];
+        /* subscript is enum wantedColor */
+    unsigned int verbose;
+    unsigned int nmaxSpec;
+    float        nmax;
+    unsigned int lval;
+    unsigned int rval;
+    unsigned int widthSpec;
+    unsigned int width;
+    unsigned int height;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+    unsigned int lvalSpec, rvalSpec, heightSpec;
+    unsigned int redSpec, greenSpec, blueSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "black",     OPT_FLAG, NULL, &cmdlineP->black,   0);
+    OPTENT3(0, "white",     OPT_FLAG, NULL, &cmdlineP->white,   0);
+    OPTENT3(0, "dots",      OPT_FLAG, NULL, &cmdlineP->dots,    0);
+    OPTENT3(0, "red",       OPT_FLAG, NULL, &redSpec,           0);
+    OPTENT3(0, "green",     OPT_FLAG, NULL, &greenSpec,         0);
+    OPTENT3(0, "blue",      OPT_FLAG, NULL, &blueSpec,          0);
+    OPTENT3(0, "verbose",   OPT_FLAG, NULL, &cmdlineP->verbose, 0);
+    OPTENT3(0, "nmax",      OPT_FLOAT, &cmdlineP->nmax,
+            &cmdlineP->nmaxSpec,   0);
+    OPTENT3(0, "lval",      OPT_UINT, &cmdlineP->lval,
+            &lvalSpec,             0);
+    OPTENT3(0, "rval",      OPT_UINT, &cmdlineP->rval,
+            &rvalSpec,             0);
+    OPTENT3(0, "width",     OPT_UINT, &cmdlineP->width,
+            &cmdlineP->widthSpec,  0);
+    OPTENT3(0, "height",    OPT_UINT, &cmdlineP->height,
+            &heightSpec, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!lvalSpec)
+        cmdlineP->lval = 0;
+    if (!rvalSpec)
+        cmdlineP->rval = PNM_OVERALLMAXVAL;
+
+    if (!redSpec && !greenSpec && !blueSpec) {
+        cmdlineP->colorWanted[WANT_RED] = TRUE;
+        cmdlineP->colorWanted[WANT_GRN] = TRUE;
+        cmdlineP->colorWanted[WANT_BLU] = TRUE;
+    } else {
+        cmdlineP->colorWanted[WANT_RED] = redSpec;
+        cmdlineP->colorWanted[WANT_GRN] = greenSpec;
+        cmdlineP->colorWanted[WANT_BLU] = blueSpec;
+    }
+
+    if (!heightSpec)
+        cmdlineP->height = 200;
+
+    if (argc-1 == 0) 
+        cmdlineP->inputFilespec = "-";
+    else if (argc-1 != 1)
+        pm_error("Program takes zero or one argument (filename).  You "
+                 "specified %d", argc-1);
+    else
+        cmdlineP->inputFilespec = argv[1];
+}
+
+
+
+static unsigned int
+maxSlotCount(const unsigned int * const hist,
+             unsigned int         const hist_width,
+             bool                 const no_white,
+             bool                 const no_black) {
+/*----------------------------------------------------------------------------
+   Return the maximum count among all the slots in hist[], not counting
+   the first and last as suggested by 'no_white' and 'no_black'.
+-----------------------------------------------------------------------------*/
+    unsigned int hmax;
+    unsigned int i;
+
+    unsigned int const start = (no_black ? 1 : 0);
+    unsigned int const finish = (no_white ? hist_width - 1 : hist_width);
+    for (hmax = 0, i = start; i < finish; ++i)
+        if (hmax < hist[i])
+            hmax = hist[i];
+
+    return hmax;
+}
+
+
+
+static void
+clipHistogram(unsigned int * const hist,
+              unsigned int   const hist_width,
+              unsigned int   const hmax) {
+
+            unsigned int i;
+
+            for (i = 0; i < hist_width; ++i)
+                hist[i] = MIN(hmax, hist[i]);
+}
+
+
+
+static void
+pgm_hist(FILE *       const fp,
+         int          const cols,
+         int          const rows,
+         xelval       const maxval,
+         int          const format,
+         bool         const dots,
+         bool         const no_white,
+         bool         const no_black,
+         bool         const verbose,
+         xelval       const startval,
+         xelval       const endval,
+         unsigned int const hist_width,
+         unsigned int const hist_height,
+         bool         const clipSpec,
+         unsigned int const clipCount,
+         double       const hscale) {
+
+    bool const hscale_unity = hscale - 1 < epsilon;
+
+    gray * grayrow;
+    bit ** bits;
+    int i, j;
+    unsigned int * ghist;
+    double vscale;
+    unsigned int hmax;
+
+    if ((ghist = calloc(hist_width, sizeof(int))) == NULL)
+        pm_error ("Not enough memory for histogram array (%d bytes)",
+                  hist_width * sizeof(int));
+    if ((bits = pbm_allocarray (hist_width, hist_height)) == NULL)
+        pm_error ("no space for output array (%d bits)",
+                  hist_width * hist_height);
+    memset (ghist, 0, sizeof (ghist));
+
+    /* read the pixel values into the histogram arrays */
+    grayrow = pgm_allocrow (cols);
+    /*XX error-check! */
+    if (verbose) pm_message ("making histogram...");
+    for (i = rows; i > 0; --i) {
+        pgm_readpgmrow (fp, grayrow, cols, maxval, format);
+        for (j = cols-1; j >= 0; --j) {
+            int value;
+
+            if ((value = grayrow[j]) >= startval && value <= endval)
+                ghist[SCALE_H(value-startval)]++;
+        }
+    }
+    pgm_freerow (grayrow);
+    fclose (fp);
+
+    /* find the highest-valued slot and set the vertical scale value */
+    if (verbose)
+        pm_message ("finding max. slot height...");
+    if (clipSpec)
+        hmax = clipCount;
+    else 
+        hmax = maxSlotCount(ghist, hist_width, no_white, no_black);
+
+    if (verbose)
+        pm_message ("Done: height = %u", hmax);
+
+    clipHistogram(ghist, hist_width, hmax);
+
+    vscale = (double) hist_height / hmax;
+
+    for (i = 0; i < hist_width; ++i) {
+        int mark = hist_height - (int)(vscale * ghist[i]);
+        for (j = 0; j < mark; ++j)
+            bits[j][i] = PBM_BLACK;
+        if (j < hist_height)
+            bits[j++][i] = PBM_WHITE;
+        for ( ; j < hist_height; ++j)
+            bits[j][i] = dots ? PBM_BLACK : PBM_WHITE;
+    }
+
+    pbm_writepbm (stdout, bits, hist_width, hist_height, 0);
+}
+
+
+
+static unsigned int
+maxSlotCountAll(unsigned int *       const hist[3],
+                unsigned int         const hist_width,
+                bool                 const no_white,
+                bool                 const no_black) {
+/*----------------------------------------------------------------------------
+   Return the maximum count among all the slots in hist[x] not
+   counting the first and last as suggested by 'no_white' and
+   'no_black'.  hist[x] may be NULL to indicate none.
+-----------------------------------------------------------------------------*/
+    unsigned int hmax;
+    unsigned int color;
+
+    hmax = 0;
+
+    for (color = 0; color < 3; ++color)
+        if (hist[color])
+            hmax = MAX(hmax, 
+                       maxSlotCount(hist[color], 
+                                    hist_width, no_white, no_black));
+    
+    return hmax;
+}
+
+
+
+static void
+createHist(bool             const colorWanted[3],
+           unsigned int     const hist_width,
+           unsigned int * (* const histP)[3]) {
+/*----------------------------------------------------------------------------
+   Allocate the histogram arrays and set each slot count to zero.
+-----------------------------------------------------------------------------*/
+    unsigned int color;
+
+    for (color = 0; color < 3; ++color)
+        if (colorWanted[color]) {
+            unsigned int * hist;
+            unsigned int i;
+            MALLOCARRAY(hist, hist_width);
+            if (hist == NULL)
+                pm_error ("Not enough memory for histogram arrays (%u bytes)",
+                          hist_width * sizeof(int) * 3);
+
+            for (i = 0; i < hist_width; ++i)
+                hist[i] = 0;
+            (*histP)[color] = hist;
+        } else
+            (*histP)[color] = NULL;
+}
+
+
+
+static void
+clipHistogramAll(unsigned int * const hist[3],
+                 unsigned int   const hist_width,
+                 unsigned int   const hmax) {
+
+    unsigned int color;
+
+    for (color = 0; color < 3; ++color)
+        if (hist[color])
+            clipHistogram(hist[color], hist_width, hmax);
+}
+
+
+
+static void
+ppm_hist(FILE *       const fp,
+         int          const cols,
+         int          const rows,
+         xelval       const maxval,
+         int          const format,
+         bool         const dots,
+         bool         const no_white,
+         bool         const no_black,
+         bool         const colorWanted[3],
+         bool         const verbose,
+         xelval       const startval,
+         xelval       const endval,
+         unsigned int const hist_width,
+         unsigned int const hist_height,
+         bool         const clipSpec,
+         unsigned int const clipCount,
+         double       const hscale) {
+
+    bool const hscale_unity = hscale - 1 < epsilon;
+
+    pixel *pixrow;
+    pixel **pixels;
+    int i, j;
+    unsigned int * hist[3];  /* Subscript is enum wantedColor */
+    double vscale;
+    unsigned int hmax;
+
+    createHist(colorWanted, hist_width, &hist);
+
+    if ((pixels = ppm_allocarray (hist_width, hist_height)) == NULL)
+        pm_error ("no space for output array (%d pixels)",
+                  hist_width * hist_height);
+    for (i = 0; i < hist_height; ++i)
+        memset (pixels[i], 0, hist_width * sizeof (pixel));
+
+    /* read the pixel values into the histogram arrays */
+    pixrow = ppm_allocrow (cols);
+    /*XX error-check! */
+    if (verbose) pm_message ("making histogram...");
+    for (i = rows; i > 0; --i) {
+        ppm_readppmrow (fp, pixrow, cols, maxval, format);
+        for (j = cols-1; j >= 0; --j) {
+            int value;
+
+            if (colorWanted[WANT_RED] && 
+                (value = PPM_GETR(pixrow[j])) >= startval && 
+                value <= endval)
+                hist[WANT_RED][SCALE_H(value-startval)]++;
+            if (colorWanted[WANT_GRN] && 
+                (value = PPM_GETG(pixrow[j])) >= startval && 
+                value <= endval)
+                hist[WANT_GRN][SCALE_H(value-startval)]++;
+            if (colorWanted[WANT_BLU] && 
+                (value = PPM_GETB(pixrow[j])) >= startval && 
+                value <= endval)
+                hist[WANT_BLU][SCALE_H(value-startval)]++;
+        }
+    }
+    ppm_freerow (pixrow);
+    fclose (fp);
+
+    /* find the highest-valued slot and set the vertical scale value */
+    if (verbose)
+        pm_message ("finding max. slot height...");
+    if (clipSpec)
+        hmax = clipCount;
+    else 
+        hmax = maxSlotCountAll(hist, hist_width, no_white, no_black);
+
+    clipHistogramAll(hist, hist_width, hmax);
+
+    vscale = (double) hist_height / hmax;
+    if (verbose)
+        pm_message("Done: height = %d, vertical scale factor = %g", 
+                   hmax, vscale);
+
+    for (i = 0; i < hist_width; ++i) {
+        if (hist[WANT_RED]) {
+            unsigned int j;
+            bool plotted;
+            plotted = FALSE;
+            for (j = hist_height - (int)(vscale * hist[WANT_RED][i]); 
+                 j < hist_height && !plotted; 
+                 ++j) {
+                PPM_PUTR(pixels[j][i], maxval);
+                plotted = dots;
+            }
+        }
+        if (hist[WANT_GRN]) {
+            unsigned int j;
+            bool plotted;
+            plotted = FALSE;
+            for (j = hist_height - (int)(vscale * hist[WANT_GRN][i]); 
+                 j < hist_height && !plotted; 
+                 ++j) {
+                PPM_PUTG(pixels[j][i], maxval);
+                plotted = dots;
+            }
+        }
+        if (hist[WANT_BLU]) {
+            unsigned int j;
+            bool plotted;
+            plotted = FALSE;
+            for (j = hist_height - (int)(vscale * hist[WANT_BLU][i]); 
+                 j < hist_height && !plotted; 
+                 ++j) {
+                PPM_PUTB(pixels[j][i], maxval);
+                plotted = dots;
+            }
+        }
+    }
+    ppm_writeppm (stdout, pixels, hist_width, hist_height, maxval, 0);
+}
+
+
+
+int
+main (int argc, char ** argv) {
+
+    struct cmdlineInfo cmdline;
+    FILE* ifP;
+    int cols, rows;
+    xelval maxval;
+    int format;
+    unsigned int hist_width;
+    unsigned int range;
+    double hscale;
+    int hmax;
+
+    pnm_init (&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    pnm_readpnminit(ifP, &cols, &rows, &maxval, &format);
+
+    range = MIN(maxval, cmdline.rval) - cmdline.lval + 1;
+
+    if (cmdline.widthSpec)
+        hist_width = cmdline.width;
+    else
+        hist_width = range;
+
+    hscale = (float)hist_width / range;
+    if (hscale - 1.0 < epsilon && cmdline.verbose)
+        pm_message("Horizontal scale factor: %g (maxval = %u)", 
+                   hscale, maxval);
+
+    if (cmdline.nmaxSpec)
+        hmax = cols * rows / hist_width * cmdline.nmax;
+
+    switch (PNM_FORMAT_TYPE(format)) {
+    case PPM_TYPE:
+        ppm_hist(ifP, cols, rows, maxval, format,
+                 cmdline.dots, cmdline.white, cmdline.black,
+                 cmdline.colorWanted,
+                 cmdline.verbose, cmdline.lval, cmdline.rval, 
+                 hist_width, cmdline.height, cmdline.nmaxSpec, hmax, hscale);
+        break;
+    case PGM_TYPE:
+        pgm_hist(ifP, cols, rows, maxval, format,
+                 cmdline.dots, cmdline.white, cmdline.black,
+                 cmdline.verbose, cmdline.lval, cmdline.rval,
+                 hist_width, cmdline.height, cmdline.nmaxSpec, hmax, hscale);
+        break;
+    case PBM_TYPE:
+        pm_error("Cannot do a histogram of a a PBM file");
+        break;
+    }
+    return 0;
+}
diff --git a/analyzer/pnmpsnr.c b/analyzer/pnmpsnr.c
new file mode 100644
index 00000000..4a6bfe56
--- /dev/null
+++ b/analyzer/pnmpsnr.c
@@ -0,0 +1,209 @@
+/*
+ *  pnmpsnr.c: Compute error (RMSE, PSNR) between images
+ *
+ *
+ *  Derived from pnmpnsmr by Ulrich Hafner, part of his fiasco package,
+ *  On 2001.03.04.
+
+ *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <math.h>
+
+#include "pm_c_util.h"
+#include "pam.h"
+
+#define MAXFILES 16
+
+static int
+udiff(unsigned int const subtrahend, unsigned int const subtractor) {
+    return subtrahend-subtractor;
+}
+
+
+static double
+square(double const arg) {
+    return(arg*arg);
+}
+
+
+static void
+validate_input(const struct pam pam1, const struct pam pam2) {
+
+    if (pam1.width != pam2.width)
+        pm_error("images are not the same width, so can't be compared.  "
+                 "The first is %d columns wide, "
+                 "while the second is %d columns wide.",
+                 pam1.width, pam2.width);
+    if (pam1.height != pam2.height)
+        pm_error("images are not the same height, so can't be compared.  "
+                 "The first is %d rows high, "
+                 "while the second is %d rows high.",
+                 pam1.height, pam2.height);
+
+    if (pam1.maxval != pam2.maxval)
+        pm_error("images do not have the same maxval.  This programs works "
+                 "only on like maxvals.  "
+                 "The first image has maxval %u, "
+                 "while the second has %u.  Use Pnmdepth to change the "
+                 "maxval of one of them.",
+                 (unsigned int) pam1.maxval, (unsigned int) pam2.maxval);
+
+    if (strcmp(pam1.tuple_type, pam2.tuple_type) != 0)
+        pm_error("images are not of the same type.  The tuple types are "
+                 "'%s' and '%s', respectively.",
+                 pam1.tuple_type, pam2.tuple_type);
+
+    if (strcmp(pam1.tuple_type, PAM_PBM_TUPLETYPE) != 0 &&
+        strcmp(pam1.tuple_type, PAM_PGM_TUPLETYPE) != 0 &&
+        strcmp(pam1.tuple_type, PAM_PPM_TUPLETYPE) != 0)
+        pm_error("Images are not of a PNM type.  Tuple type is '%s'",
+                 pam1.tuple_type);
+}
+
+
+
+static void
+psnr_color(tuple const tuple1, tuple const tuple2,
+           double * const ySqDiffP, 
+           double * const cbSqDiffP, double * const crSqDiffP) {
+
+    double y1, y2, cb1, cb2, cr1, cr2;
+    
+    pnm_YCbCrtuple(tuple1, &y1, &cb1, &cr1);
+    pnm_YCbCrtuple(tuple2, &y2, &cb2, &cr2);
+    
+    *ySqDiffP =  square(y1  - y2);
+    *cbSqDiffP = square(cb1 - cb2);
+    *crSqDiffP = square(cr1 - cr2);
+}
+
+
+
+static void
+reportPsnr(struct pam const pam1, struct pam const pam2, 
+           double const ySumSqDiff, 
+           double const crSumSqDiff, double const cbSumSqDiff,
+           const char filespec1[], const char filespec2[]) {
+
+    bool const color = (strcmp(pam1.tuple_type, PAM_PPM_TUPLETYPE) == 0);
+
+    /* The PSNR is the mean of the sum of squares of the differences,
+       normalized to the range 0..1
+    */
+    double const yPsnr = ySumSqDiff 
+        / (pam1.width * pam1.height) 
+        / square(pam1.maxval);
+
+    if (color) {
+        double const cbPsnr = cbSumSqDiff 
+            / (pam1.width * pam1.height) 
+            / square(pam1.maxval);
+        double const crPsnr = crSumSqDiff 
+            / (pam1.width * pam1.height) 
+            / (pam1.maxval * pam2.maxval);
+
+        pm_message("PSNR between %s and %s:", filespec1, filespec2);
+        if (yPsnr > 1e-9)
+            pm_message("Y  color component: %.2f dB", 10 * log10(1/yPsnr));
+        else
+            pm_message("Y color component does not differ.");
+        if (cbPsnr > 1e-9)
+            pm_message("Cb color component: %.2f dB", 10 * log10(1/cbPsnr));
+        else
+        pm_message("Cb color component does not differ.");
+        if (crPsnr > 1e-9)
+            pm_message("Cr color component: %.2f dB", 10 * log10(1/crPsnr));
+        else
+            pm_message("Cr color component does not differ.");
+    } else {
+        if (yPsnr > 1e-9)
+            pm_message("PSNR between %s and %s: %.2f dB",
+                       filespec1, filespec2, 10 * log10(1/yPsnr));
+        else
+            pm_message("Images %s and %s don't differ.",
+                       filespec1, filespec2);
+    }
+}
+
+
+
+int
+main (int argc, char **argv) {
+    char *filespec1, *filespec2;  /* specs of two files to compare */
+    FILE *file1, *file2;
+    struct pam pam1, pam2;
+    bool color;
+        /* It's a color image */
+    double ySumSqDiff, crSumSqDiff, cbSumSqDiff;
+    tuple *tuplerow1, *tuplerow2;  /* malloc'ed */
+    int row;
+    
+    pnm_init(&argc, argv);
+
+    if (argc < 2) 
+        pm_error("Takes two arguments:  specifications of the two files.");
+    else {
+        filespec1 = argv[1];
+        filespec2 = argv[2];
+    }
+    
+    file1 = pm_openr(filespec1);
+    file2 = pm_openr(filespec2);
+
+    pnm_readpaminit(file1, &pam1, PAM_STRUCT_SIZE(tuple_type));
+    pnm_readpaminit(file2, &pam2, PAM_STRUCT_SIZE(tuple_type));
+
+    validate_input(pam1, pam2);
+
+    if (strcmp(pam1.tuple_type, PAM_PPM_TUPLETYPE) == 0) 
+        color = TRUE;
+    else
+        color = FALSE;
+
+    tuplerow1 = pnm_allocpamrow(&pam1);
+    tuplerow2 = pnm_allocpamrow(&pam2);
+    
+    ySumSqDiff = 0.0;
+    cbSumSqDiff = 0.0;
+    crSumSqDiff = 0.0;
+
+    for (row = 0; row < pam1.height; ++row) {
+        int col;
+        
+        pnm_readpamrow(&pam1, tuplerow1);
+        pnm_readpamrow(&pam2, tuplerow2);
+
+        for (col = 0; col < pam1.width; ++col) {
+            if (color) {
+                double ySqDiff, cbSqDiff, crSqDiff;
+                psnr_color(tuplerow1[col], tuplerow2[col], 
+                           &ySqDiff, &cbSqDiff, &crSqDiff);
+                ySumSqDiff += ySqDiff;
+                cbSumSqDiff += cbSqDiff;
+                crSumSqDiff += crSqDiff;
+                
+            } else {
+                unsigned int yDiffSq;
+                yDiffSq = square(udiff(tuplerow1[col][0], tuplerow2[col][0]));
+                ySumSqDiff += yDiffSq;
+            }
+        }
+    }
+
+    reportPsnr(pam1, pam2, ySumSqDiff, crSumSqDiff, cbSumSqDiff,
+               filespec1, filespec2);
+
+    pnm_freepamrow(tuplerow1);
+    pnm_freepamrow(tuplerow2);
+
+    return 0;
+}
+
+
+
+
+
+
diff --git a/analyzer/ppmhist.c b/analyzer/ppmhist.c
new file mode 100644
index 00000000..4c4d3c55
--- /dev/null
+++ b/analyzer/ppmhist.c
@@ -0,0 +1,290 @@
+/* ppmhist.c - read a PPM image and compute a color histogram
+**
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#include <assert.h>
+
+#include "ppm.h"
+#include "shhopt.h"
+#include "nstring.h"
+
+enum sort {SORT_BY_FREQUENCY, SORT_BY_RGB};
+
+enum colorFmt {FMT_DECIMAL, FMT_HEX, FMT_FLOAT, FMT_PPMPLAIN};
+
+struct cmdline_info {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *input_filespec;  /* Filespecs of input files */
+    unsigned int noheader;    /* -noheader option */
+    enum colorFmt colorFmt;
+    unsigned int colorname;   /* -colorname option */
+    enum sort sort;           /* -sort option */
+};
+
+
+
+static void
+parse_command_line(int argc, char ** argv,
+                   struct cmdline_info * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optStruct3 opt;  /* set by OPTENT3 */
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+    unsigned int option_def_index;
+    
+    unsigned int hexcolorOpt, floatOpt, mapOpt, nomapOpt;
+    const char * sort_type;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0,   "map",       OPT_FLAG, NULL,  &mapOpt,                0);
+    OPTENT3(0,   "nomap",     OPT_FLAG, NULL,  &nomapOpt,              0);
+    OPTENT3(0,   "noheader",  OPT_FLAG, NULL,  &cmdlineP->noheader,    0);
+    OPTENT3(0,   "hexcolor",  OPT_FLAG, NULL,  &hexcolorOpt,           0);
+    OPTENT3(0,   "float",     OPT_FLAG, NULL,  &floatOpt,              0);
+    OPTENT3(0,   "colorname", OPT_FLAG, NULL,  &cmdlineP->colorname,   0);
+    OPTENT3(0,   "sort",      OPT_STRING, &sort_type, NULL,            0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    /* Set defaults */
+    sort_type = "frequency";
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (argc-1 == 0) 
+        cmdlineP->input_filespec = "-";
+    else if (argc-1 != 1)
+        pm_error("Program takes zero or one argument (filename).  You "
+                 "specified %d", argc-1);
+    else
+        cmdlineP->input_filespec = argv[1];
+
+    if (hexcolorOpt + floatOpt + mapOpt > 1)
+        pm_error("You can specify only one of -hexcolor, -float, and -map");
+    if (hexcolorOpt)
+        cmdlineP->colorFmt = FMT_HEX;
+    else if (floatOpt)
+        cmdlineP->colorFmt = FMT_FLOAT;
+    else if (mapOpt)
+        cmdlineP->colorFmt = FMT_PPMPLAIN;
+    else 
+        cmdlineP->colorFmt = FMT_DECIMAL;
+
+    if (strcmp(sort_type, "frequency") == 0)
+        cmdlineP->sort = SORT_BY_FREQUENCY;
+    else if (strcmp(sort_type, "rgb") == 0)
+        cmdlineP->sort = SORT_BY_RGB;
+    else
+        pm_error("Invalid -sort value: '%s'.  The valid values are "
+                 "'frequency' and 'rgb'.", sort_type);
+}
+
+
+
+static int
+countcompare(const void *ch1, const void *ch2) {
+    return ((colorhist_vector)ch2)->value - ((colorhist_vector)ch1)->value;
+}
+
+
+static int
+rgbcompare(const void * arg1, const void * arg2) {
+
+    colorhist_vector const ch1 = (colorhist_vector) arg1;
+    colorhist_vector const ch2 = (colorhist_vector) arg2;
+
+    int retval;
+
+    retval = (PPM_GETR(ch1->color) - PPM_GETR(ch2->color));
+    if (retval == 0) {
+        retval = (PPM_GETG(ch1->color) - PPM_GETG(ch2->color));
+        if (retval == 0)
+            retval = (PPM_GETB(ch1->color) - PPM_GETB(ch2->color));
+    }
+    return retval;
+}
+
+
+
+static const char *
+colornameLabel(pixel        const color, 
+               pixval       const maxval,
+               unsigned int const nDictColor,
+               pixel        const dictColors[],
+               const char * const dictColornames[]) {
+/*----------------------------------------------------------------------------
+   Return the name of the color 'color' or the closest color in the
+   dictionary to it.  If the name returned is not the exact color,
+   prefix it with "*".  Otherwise, prefix it with " ".
+
+   'nDictColor', dictColors[], and dictColorNames[] are the color 
+   dictionary.
+
+   Return the name in static storage within this subroutine.
+-----------------------------------------------------------------------------*/
+    static char retval[32];
+    int colorIndex;
+    
+    pixel color255;  
+        /* The color, normalized to a maxval of 255: the maxval of a color
+           dictionary.
+        */
+
+    PPM_DEPTH(color255, color, maxval, 255);
+
+    colorIndex = ppm_findclosestcolor(dictColors, nDictColor, &color);
+
+    assert(colorIndex >= 0 && colorIndex < nDictColor);
+    
+    if (PPM_EQUAL(dictColors[colorIndex], color))
+        STRSCPY(retval, " ");
+    else
+        STRSCPY(retval, "*");
+    
+    STRSCAT(retval, dictColornames[colorIndex]);
+    
+    return retval;
+}
+                
+
+
+static void
+printColors(colorhist_vector const chv, 
+            int              const nColors,
+            pixval           const maxval,
+            enum colorFmt    const colorFmt,
+            unsigned int     const nKnown,
+            pixel            const knownColors[],
+            const char *     const colornames[]) {
+
+    int i;
+
+    for (i = 0; i < nColors; i++) {
+        pixval       const r          = PPM_GETR(chv[i].color);
+        pixval       const g          = PPM_GETG(chv[i].color);
+        pixval       const b          = PPM_GETB(chv[i].color);
+        double       const lum        = PPM_LUMIN(chv[i].color);
+        unsigned int const intLum     = lum + 0.5;
+        double       const floatLum   = lum / maxval;
+        unsigned int const count      = chv[i].value;
+
+        const char * colornameValue;
+
+        if (colornames)
+            colornameValue = colornameLabel(chv[i].color, maxval, 
+                                            nKnown, knownColors, colornames);
+        else
+            colornameValue = "";
+
+        switch(colorFmt) {
+        case FMT_FLOAT:
+            printf(" %1.3f %1.3f %1.3f\t%1.3f\t%7d %s\n",
+                   (double)r / maxval,
+                   (double)g / maxval,
+                   (double)b / maxval,
+                   floatLum, count, colornameValue);
+            break;
+        case FMT_HEX:
+            printf("  %04x  %04x  %04x\t%5d\t%7d %s\n",
+                   r, g, b, intLum, count, colornameValue);
+            break;
+        case FMT_DECIMAL:
+            printf(" %5d %5d %5d\t%5d\t%7d %s\n",
+                   r, g, b, intLum, count, colornameValue);
+            break;
+        case FMT_PPMPLAIN:
+            printf(" %5d %5d %5d#\t%5d\t%7d %s\n",
+                   r, g, b, intLum, count, colornameValue);
+            break;
+        }
+    }
+}
+
+
+
+int
+main(int argc, char *argv[] ) {
+    struct cmdline_info cmdline;
+    FILE* ifP;
+    colorhist_vector chv;
+    int rows, cols;
+    pixval maxval;
+    int format;
+    int nColors;
+    int (*compare_function)(const void *, const void *);
+        /* The compare function to be used with qsort() to sort the
+           histogram for output
+        */
+    unsigned int nDictColor;
+    const char ** dictColornames;
+    pixel * dictColors;
+
+    ppm_init( &argc, argv );
+
+    parse_command_line(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.input_filespec);
+
+    ppm_readppminit(ifP, &cols, &rows, &maxval, &format);
+
+    chv = ppm_computecolorhist2(ifP, cols, rows, maxval, format, 0, &nColors);
+
+    pm_close(ifP);
+
+    switch (cmdline.sort) {
+    case SORT_BY_FREQUENCY:
+        compare_function = countcompare; break;
+    case SORT_BY_RGB:
+        compare_function = rgbcompare; break;
+    }
+
+    qsort((char*) chv, nColors, sizeof(struct colorhist_item), 
+          compare_function);
+
+    /* And print the histogram. */
+    if (cmdline.colorFmt == FMT_PPMPLAIN) 
+        printf("P3\n# color map\n%d 1\n%d\n", nColors, maxval);
+
+    if (!cmdline.noheader) {
+        const char commentDelim = cmdline.colorFmt == FMT_PPMPLAIN ? '#' : ' ';
+        printf("%c  r     g     b   \t lum \t count  %s\n",
+               commentDelim, cmdline.colorname ? "name" : "");
+        printf("%c----- ----- ----- \t-----\t------- %s\n",
+               commentDelim, cmdline.colorname ? "----" : "");
+    }
+    if (cmdline.colorname) {
+        bool mustOpenTrue = TRUE;
+        ppm_readcolordict(NULL, mustOpenTrue, 
+                          &nDictColor, &dictColornames, &dictColors, NULL);
+    } else {
+        dictColors = NULL;
+        dictColornames = NULL;
+    }
+        
+    printColors(chv, nColors, maxval,
+                cmdline.colorFmt, nDictColor, dictColors, dictColornames);
+
+    if (dictColors)
+        free(dictColors);
+    if (dictColornames)
+        free(dictColornames);
+
+    ppm_freecolorhist(chv);
+
+    return 0;
+}