/* table2image.c

Program:   table2image.c
Version:   1.0.2
Date:      01-DEC-2005
Author:    David Mathog, Biology Division, Caltech
email:     mathog@caltech.edu
Copyright: 2005,2003 David Mathog and California Institute of Technology (Caltech)

Description:

     Converts x,y,intensity tables (text)
     to an image, or extracts some statistics from the table.
     Requires ImageMagick libraries. 
      
Changes:

  1.0.2 01-DEC-2005 DRM, fixed problems following upgrade to
        ImageMagick 6.5.2.  Mostly this involved changing "box"
        to "undercolor" in drawinfo structure.
  1.0.1 29-JAN-2003 DRM added -x/yloge/10,-txt*, and conditional
        display options.
  1.0.0 12-NOV-2001  Original code

License terms:
    As specified in ImageMagick.  Where those license terms
    do not apply the following do:

    You may run this program on any platform. You may
    redistribute the source code of this program subject to
    the condition that you do not first modify it in any way.
    You may  distribute binary versions of this program so long
    as they were compiled from unmodified source code.  There
    is no charge for using this software.  You may not charge
    others for the use of this software.
        
Miscellaneous:
  On Unix compile with:
  
    gcc `Magick-config --cflags --cppflags` -Wall -pedantic \
         table2image.c -o table2image \
         `Magick-config --ldflags --libs`
      
         
*/
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <math.h>
#include <ctype.h>
#include <sys/types.h>
#include <magick/api.h>
#include <magick/draw.h>

#if defined(__cplusplus) || defined(c_plusplus)
#undef class
#endif
#define DegreesToRadians(x) (M_PI*(x)/180.0)

/* structures */

typedef struct isastatstruct STATSTRUCT;
struct isastatstruct {
   int    good;   /* number of good values */
   double min;    /* minimum value */
   double max;    /* maximum value */
   double sum;    /* sum of values */
   double sum2;   /* sum of squares of values */
};

/* definitions */
#define VERSTRING "1.0.2 02-DEC-2005"
#define DELMAX 10         /* max number of delimiter characters */
#define COLMAX 1002       /* more than 1000 columns is pathological, also used for max bins */
#define MAXINLINE 100000  /* no text line longer than 100kb */
#define FORMNUM 2         /* formulas supported, currently 0->2 */
#define WHICHVAL(A) ( A>COLMAX ? lc : val )
#define TESTONE(A,B,C) if(cc  ==  A || A >COLMAX){tests_total++;if(WHICHVAL(A)>=B && WHICHVAL(A)<=C)tests_passed++;}

/* globals */

int     imginit=1;        /* 1 means initialize, 2 read from input image, 0 do nothing */
FILE   *infile;
int     stats=0;
char   *in=NULL;
char   *out=NULL;
char   *overimage=NULL;
int     flag_columns=0;     /* disable column number checking */
int     flag_nonnumeric=1;  /* enable warnings on nonnumeric data */
char    delimits[DELMAX+1];
char    nodata[1]="";
int     quotes=1;       /* default is special processing of double quotes */
int     parse_single=1; /* default is for single delimiter limited tokens */
int     sr=0;
int     er=2000000000;
int     xform=0;        /* defaults to linear coordinate transformations */
int     yform=0;        /* defaults to linear coordinate transformations */
int     xc=0;
int     yc=0;
double  xcval,xbval;
double  ycval,ybval;
int     xsize=0;
int     condlogic=0;
int     xcondc=0;
double  xcondmin=-1000000000.0;
double  xcondmax= 1000000000.0;
double  xcondval;
int     ycondc=0;
double  ycondmin=-1000000000.0;
double  ycondmax= 1000000000.0;
double  ycondval;
int     wcondc=0;
double  wcondmin=-1000000000.0;
double  wcondmax= 1000000000.0;
double  wcondval;
int     rcondc=0;
double  rcondmin=-1000000000.0;
double  rcondmax= 1000000000.0;
double  rcondval;
int     gcondc=0;
double  gcondmin=-1000000000.0;
double  gcondmax= 1000000000.0;
double  gcondval;
int     bcondc=0;
double  bcondmin=-1000000000.0;
double  bcondmax= 1000000000.0;
double  bcondval;
int     ysize=0;
int     hisc=0;
double  hismin=0.0;
double  hismax=100.0;
int     bins=20;
int     xinv=0;
int     yinv=0;
int     xlog=0; /* 0 is no log, 1 is loge, 2 is log 10 */
int     ylog=0;
double  xscale=1.;
double  yscale=1.;
double  xd,yd;
int     radius=0;
int     iwsc=0;
int     iwbc=0;
double  iwmin=0.;
double  iwmax=255.0;
int     irsc=0;
int     irbc=0;
double  irmin=0.;
double  irmax=255.0;
int     igsc=0;
int     igbc=0;
double  igmin=0.;
double  igmax=255.0;
int     ibsc=0;
int     ibbc=0;
double  ibmin=0.;
double  ibmax=255.0;
int     fill=0;
int     xoff=0;
int     yoff=0;
int     xbase=0;
int     ybase=0;
char    defaulttxtfont[30]="helvetica";
char *  txtfont=defaulttxtfont;   /* like "helvetica" */
PixelPacket  txtfg;     /* fg color, as an RGB triplet (0-MaxRGB) range MaxRGB MaxRGB MaxRGB is white*/
PixelPacket  txtbg;     /* bg color, black, opaque */
double  txtangle=0.0;   /* text angle in degrees between 0 and 360 */
int     txtpntsize=10;  /* size of text */
int     txtc=0;         /* column that donates text */
int     txtpos=5;       /* x,y position on text box, default is center */
char *  holdtxt=NULL;   /* this holds the text which will be written, if any */
char    holdlctxt[32];  /* static storage for text form of lc */

PixelPacket  rfg;       /* structure that holds real foreground colors */
PixelPacket  rbg;       /* structure that holds real background colors (for text, for instance) */

/* prototypes */
void insane(char *string);
void   emit_help(void);
int    lcl_strcasecmp(char *s1, char *s2);
void   process_command_line_args(int argc,char **argv, int *numarg);
int    scaledintense(double val,double imin,double imax);
void   setdnumeric(double *val,int *numarg,int argc,char **argv,char * label);
void   setinumeric(int *val,int *numarg,int argc,char **argv,char * label);
void   setpixelpacket(PixelPacket *vals,int *numarg,int argc,char **argv,char * label);
void   setstring(char **val,int *numarg,int argc,char **argv,char * label);
char * strtok_variant(char *instring,const char *delim);
void   stuff_delimits(char *dest, char *src);
void   t2i_dostats(char *buffer);
void   t2i_drawpixel(int x, int y, Image *image, PixelPacket *lfg);
void   t2i_drawtext(char *text, int x, int y,
          char *font, int antialias,
          PixelPacket *fg, PixelPacket *bg,
          int pointsize, int position, double angle,
          Image *image, DrawInfo *draw_info);
void   t2i_drawsolidcircle(int x, int y, Image *image, PixelPacket *lfg, int radius);
void   t2i_initimage(Image **image_out, ImageInfo **image_info_out);
double t2i_log(double val,int logflag);
void   t2i_setfromrow(char *buffer,int lc, int *xp,int *yp, int *status);
void   t2i_useformula(int form, double cval, double bval, double *formval);
void   validrange(int val, int vmin, int vmax, char *message);

/* functions */

void insane(char *string){
 (void) fprintf(stderr,"%s\n",string);
 exit(EXIT_FAILURE);
}


/* This strtok implementation can either merge a series of delimiters into one
(as does strtok) or it can treat a series DELIMIT DELIMIT as DELIMIT EMPTYSTRING DELIMIT,
the latter form is required for properly handling tab delimited data when data fields
may be empty.  It returns a pointer to the "nodata".  This is modified from mpcf_strtok
in the miniproc program.  It lacks the single quote processing and the & processing. */

char * strtok_variant(char *instring,const char *delim){
static char *holdstring;
static int  ftoken, etoken, dqetoken, endstring, firstcase;
int cpnt;
enum sstates {NEXTDELIM,NEXTDQUOTE,NEXTDELIMDQ,NEXTREGULAR,ENDSEARCH};
enum sstates searchstate;
enum vdquote {ISVALID,ISINVALID};
enum vdquote validdquote;

  if(instring != NULL){  /* new string */
     endstring = strlen(instring) - 1;
     holdstring = instring; /* write on the actual input string, not a copy */
     ftoken=0;
     etoken=0;
     firstcase=1;
  }
  else{  /* look for next token in existing string */
     etoken++;     /* last call would have left this on a delimiter */
     firstcase=0;
  }

  /* this is the part that does the search
     an exit path is left from this loop to a return=NULL outside so that
     some compilers (DECC) won't complain about there not being a final
     return statement */

  for( searchstate = NEXTREGULAR; searchstate != ENDSEARCH ; etoken++){
    if(searchstate != NEXTREGULAR){firstcase=0;}
    if(etoken > endstring){  /* always check first to see if search has extended past string end */
       switch (searchstate){
          case ENDSEARCH: /* gcc wants ENDSEARCH case, but from for()logic case is never used */
          case NEXTREGULAR: /* hit end of string, no tokens found */
             searchstate=ENDSEARCH;
             break;
          case NEXTDELIM:   /* end of token is end of string */
             return &holdstring[ftoken];
          case NEXTDELIMDQ:   /* handle double quote, the potential double 
                                 quoted string MUST be followed by a delimiter or an EOL */
             etoken=dqetoken;
             etoken++;
             validdquote=ISINVALID;
             if(etoken == endstring + 1){ /* EOL case */
               validdquote=ISVALID;
             }
             else{
               for(cpnt=0; delim[cpnt] != '\0';cpnt++){
                 if(holdstring[etoken] == delim[cpnt]){  /* NON-EOL case */
                   validdquote=ISVALID;
                   break;
                 }
               }
             }
             if(validdquote == ISVALID){
               holdstring[etoken]=' ';    /* put a space (delimiter) in over the double quote */
               etoken--;
               holdstring[etoken]='\0';   /* delimit string */
               etoken++;                  /* restore etoken, there may be another call */   
               ftoken++;                  /* nip off leading quote/double quote */
               return &holdstring[ftoken];
             }
             else{
               insane("fatal error, unmatched double quotes");
             }
          case NEXTDQUOTE:   /* unmatched double quotes - bad */
             insane("fatal error, unmatched double quotes");
       }
    }
    else{
       switch (searchstate){
          case ENDSEARCH: /* gcc wants ENDSEARCH case, but from for()logic case is never used */
            break;
          case NEXTREGULAR:    /* is the current character NOT a delimiter? */
            switch(holdstring[etoken]){
              case '"':            /* start of a double quote delimited token */
                if(quotes){
                  searchstate = NEXTDQUOTE;
                  ftoken = etoken;  /* double quotes are part of the token */
                  break;
                } /* DO NOT SEPARATE the " case from default: or the noquotes logic breaks! */
              default:
                searchstate=NEXTDELIM;
                ftoken = etoken;
                for(cpnt=0; delim[cpnt] != '\0';cpnt++){
                  if(holdstring[etoken] == delim[cpnt]){
                    searchstate=NEXTREGULAR;
                  }
                }
                if(searchstate!=NEXTDELIM){
                  if(parse_single){ /* 1 delimiter = full token delimit in ths modelimiters = 1 delimit, in this mode EACH is a delimit*/
                    ftoken=etoken;
                    return &nodata[0];
                  }
                }
            }
            break;
          case NEXTDELIM: /* see if the current character is a delimiter */
            for(cpnt=0; delim[cpnt] != '\0';cpnt++){
              if(holdstring[etoken] == delim[cpnt]){  /* a token has been found */
                 holdstring[etoken] = '\0';     /* terminate it */
                 return &holdstring[ftoken];
              }
            }
            break;
          case NEXTDELIMDQ: /* after a "foo foo", this must be a delimiter */
            for(cpnt=0; delim[cpnt] != '\0';cpnt++){
              if(holdstring[etoken] == delim[cpnt]){  /* a token has been found */
                 etoken--;
                 holdstring[etoken] = '\0';     /* terminate it */
                 etoken++;
                 ftoken++;
                 return &holdstring[ftoken];
              }
            }
            insane("fatal error, double quotes followed by more text - should be EOL or delimiter");
            break;
          case NEXTDQUOTE:  /* see if the current character is a quote */
            if(holdstring[etoken] == '"'){    /* a valid token has been found, may not be final one though */
               searchstate = NEXTDELIMDQ;     /* indicates that at least one valid token was found */
               dqetoken = etoken;
            }
            break;
       }
    } 
  } 
  return NULL;
}

  
int scaledintense(double val,double imin,double imax){
  if(val < imin)val=imin;
  if(val > imax)val=imax;
  return (int) ((double) MaxRGB * (val - imin) /(imax -imin));
}
 
void emit_help(void){
(void) fprintf(stdout,"table2image command summary:\n");
(void) fprintf(stdout,"\n");
(void) fprintf(stdout,"table2image converts a table of information such as might be\n");
(void) fprintf(stdout,"  exported from a spreadsheet into an image representation.  It\n");
(void) fprintf(stdout,"  plots as a pixel or centered circle the intensity values from\n");
(void) fprintf(stdout,"  one or more columns to locations specified by other columns.\n");
(void) fprintf(stdout,"  Example: data:  1  10  100 could be plotted like x=1,y=10,intensity=100.\n");
(void) fprintf(stdout,"  Alternatively, two types of statistical analysis of the input\n");
(void) fprintf(stdout,"  data may be performed.\n");
(void) fprintf(stdout,"Pixel coordinates are (X,Y): (Below J/j=XY/xy)\n");
(void) fprintf(stdout,"  Output ranges are J={0,jsize-1}\n");
(void) fprintf(stdout,"  Available coordinate transformations (set with -jform) are:\n");
(void) fprintf(stdout,"  0   J = (((value at jc)-jbase)*jscale + joff)   [Default]\n");
(void) fprintf(stdout,"  1   J = (((value at jc)*(value at jbase))*jscale + joff)\n");
(void) fprintf(stdout,"  2   J = (((value at jc)/(value at jbase))*jscale + joff)\n");
(void) fprintf(stdout,"Spots or circles centered on (X,Y) are written with intensities:\n");
(void) fprintf(stdout,"  MinRGB = 0; MaxRGB = %d\n",MaxRGB);
(void) fprintf(stdout,"  jscale   = (MaxRGB - MinRGB)/(jmax-jmin)\n");
(void) fprintf(stdout,"  Ichannel = jscale*((val at jsc) - (val at jbc))\n");
(void) fprintf(stdout,"    where j is iw or (ir, ig, or ib)\n");
(void) fprintf(stdout,"  If only iw is specified R,G,B channels all get the same value\n");
(void) fprintf(stdout,"  R,G,B channels may be controlled independently via the ir,ig,ib values\n");
(void) fprintf(stdout,"\n");
(void) fprintf(stdout,"Option abbreviations\n");
(void) fprintf(stdout,"  types:  R=required, O=optional\n");
(void) fprintf(stdout,"  values: N=integer, F=float, Name=file name, or none\n");
(void) fprintf(stdout,"  Columns: 0=ignore, 1-%d a column, >%d value is row number\n",COLMAX,COLMAX);
(void) fprintf(stdout,"\n");
(void) fprintf(stdout,"Statistics mode options:\n");
(void) fprintf(stdout," -stats          (R) Process the file and emit statistics to stdout for each\n");
(void) fprintf(stdout,"                     column: min,max,mean,median,std. dev.\n");
(void) fprintf(stdout," -hisc       N   (O) Histogram only this column, default disabled\n");
(void) fprintf(stdout," -bins       N   (O) Store histogram in this many bins, default 20, max 1000\n");
(void) fprintf(stdout," -hismin,hismax F\n");
(void) fprintf(stdout,"                 (O) Bin value range, default {0.0,100.0}\n");
(void) fprintf(stdout,"\n");
(void) fprintf(stdout,"Conversion mode options:\n");
(void) fprintf(stdout," -out     name   (R) Output file name.  Also determines type as in:  out.jpg\n");
(void) fprintf(stdout," -xsize      N   (R) Length of the sides of a square output image.\n");
(void) fprintf(stdout," -ysize      N   (O) Specify a rectangular image (default = xsize)\n");
(void) fprintf(stdout," -x,yc       N   (R) Columns corresponding to X,Y values\n");
(void) fprintf(stdout," -x,ybase    F   (O) See formula, default 0.0\n");
(void) fprintf(stdout," -x,yoff     N   (O) See formula, default 0.0\n");
(void) fprintf(stdout," -x,yscale   F   (O) See formula, default 1.0\n");
(void) fprintf(stdout," -x,yinv         (O) Invert X,Y:  y' = (ysize-1) - y\n");
(void) fprintf(stdout," -x,yloge,10     (O) Take log of value at (c - base) then scale and offset\n");
(void) fprintf(stdout," -x,yform    N   (O) Coordinate formulas, 0 = default, see above\n");
(void) fprintf(stdout," -iwsc       N   (R) column of data which will be the White intensity.\n");
(void) fprintf(stdout," -iwbc       N   (R) column of data to subtract from -iwsc (background)\n");
(void) fprintf(stdout," -iwmin,max  F   (O) Input range to map to output range\n");
(void) fprintf(stdout," -irsc,b,min,max (O) Similar to iw, but for Red   channel\n");
(void) fprintf(stdout," -igsc,b,min,max (O) Similar to iw, but for Green channel\n");
(void) fprintf(stdout," -ibsc,b,min,max (O) Similar to iw, but for Blue  channel\n");
(void) fprintf(stdout," -fill           (O) Write other areas with intensity MaxRGB, default = MinRGB\n");
(void) fprintf(stdout," -radius     N   (O) Radius of circle to draw at (X,Y) default=1=single pixel\n");
(void) fprintf(stdout," -over    name   (O) Input image file to be written over, defines x,ysize to match\n");
(void) fprintf(stdout,"\n");
(void) fprintf(stdout,"Conditional options: (j may be x,y,w,r,g, or b; ie xcondc)\n");
(void) fprintf(stdout," -jcondc     N   (O) Column to check for conditional draw\n");
(void) fprintf(stdout," -jcondmin   F   (O) Draw only when value is >=, default -1*10^9\n");
(void) fprintf(stdout," -jcondmax   F   (O) Draw only when value is <=, default  1*10^9\n");
(void) fprintf(stdout," -condlogic  N   (O) Draw if: 0 all pass (default), 1 any pass, 2 all fail, 3 any fail\n");
(void) fprintf(stdout,"\n");
(void) fprintf(stdout,"Text options:\n");
(void) fprintf(stdout," -txtfont        (O) Font to draw with, default Helvetica\n");
(void) fprintf(stdout," -txtfg          (O) Foreground color (RGB + opacity,  %d %d %d 0 is white)\n"
  ,MaxRGB,MaxRGB,MaxRGB);
(void) fprintf(stdout," -txtbg          (O) Background color (opacity 0 is opaque, %d is fully transparent)\n"
  ,MaxRGB);
(void) fprintf(stdout," -txtc           (O) Column to read text from\n");
(void) fprintf(stdout," -txtpos         (O) 1-9 - UL(upper left),UC,UR,CL,CC,CR,LL,LC,LR, default 4\n");
(void) fprintf(stdout," -txtangle       (O) write text at angle (degrees), default 0.0\n");
(void) fprintf(stdout," -txtpntsize     (O) Point size to use\n");
(void) fprintf(stdout,"\n");
(void) fprintf(stdout,"Common options:\n");
(void) fprintf(stdout," -in      name   (O) Read table from this file (default is from stdin)\n");
(void) fprintf(stdout," -sr         N   (O) Start row, default is first\n");
(void) fprintf(stdout," -er         N   (O) End   row, default is last\n");
(void) fprintf(stdout," -columns    N   (O) Flags any row with a different number of columns\n");
(void) fprintf(stdout," -nowarn         (O) Disable warnings on nonnumeric fields\n");
(void) fprintf(stdout," -white          (O) White space delimited (formatted) table.  Default\n");
(void) fprintf(stdout,"                     is comma/colon/tab delimited data.\n");
(void) fprintf(stdout," -noquotes       (O) Disable special treatment of double quotes in input processing\n");
(void) fprintf(stdout," -delimit string\n");
(void) fprintf(stdout,"                 (O) specify a custom set of data delimiter characters\n");
(void) fprintf(stdout,"                     \\t\\ or \\T\\ is a tab\n");
(void) fprintf(stdout,"                     \\\\ is a \\\n");
(void) fprintf(stdout,"                     \\12\\ is the character with value 12\n");
(void) fprintf(stdout,"                     Example: \\t\\\\13\\ is <tab> and <CR> delimited\n");
(void) fprintf(stdout," -draw           (O) Rewind input table and draw as specified to this point\n");
(void) fprintf(stdout,"                     Except for single pass, each group must terminate with -draw\n");
(void) fprintf(stderr," -h              (O) Print this help message (also -help --h --help -? --?)\n");
(void) fprintf(stderr," -i              (O) Emit version, copyright, license and contact information\n\n");
(void) fprintf(stdout,"\n");
exit(EXIT_SUCCESS);
}


/* handle user specified delimit string, converts \t\ or \T\ is a tab character.
  \###\ converts form numeric to char, ie \10 is a LF. To enter a
  \ use \\.  Any other use of \ is invalid. */
  
void stuff_delimits(char *dest, char *src){
char *from;
char *to;
int  count;
int  escape;
int  temp,tc;
char *anum;

  for(anum=NULL,to=dest,from=src,count=0,escape=0; ; from++){
    switch (*from) {
      case '\\': /* possible escape sequence */
        if(escape){ /* enter \ as a delimiter */
          escape=0;
          if(anum == from){ /* case \\ */
            *to='\\';
          }
          else {  /* case \t \T or \number */
            if(*anum == 't' || *anum == 'T'){
              *to='\t';
            }
            else {
              *from='\0';
              temp=sscanf(anum,"%d",&tc);
              if(temp != 1)insane("Fatal error: -delimit contains invalid \\###\\ string\n");
              *to=tc;
            }
          }
          to++;
          count++;
          if(count > DELMAX)insane("Fatal error: -delimit string too long\n");
        }
        else {
          escape=1;
          anum=from;
          anum++;
        }
        break;
      case '\0': /* entire delimiter string processed */
        if(escape)insane("Fatal error: -delimit contains unterminated \\###\\ string\n");
        *to='\0';
        return;
      default:
        if(!escape){
          *to=*from;
          to++;
          count++;
          if(count > DELMAX)insane("Fatal error: -delimit string too long\n");
        }
        break;
    }
  }
}

void setinumeric(int *val,int *numarg,int argc,char **argv,char * label){
      (*numarg)++;
      if( ( *numarg >= argc ) || (argv[*numarg] == NULL)){
        (void) fprintf( stderr, "%s: missing argument\n",label);
        exit(EXIT_FAILURE);
      }
      if(sscanf(argv[*numarg],"%d",val) != 1){
        (void) fprintf(stderr,"Bad integer argument/parameter >%s %s< \n",label,argv[*numarg]);
        exit(EXIT_FAILURE);
      }
}

void setpixelpacket(PixelPacket *vals,int *numarg,int argc,char **argv,char * label){
int temp;
      (*numarg)++;
      if( ( 3 + *numarg >= argc ) || (argv[*numarg] == NULL)){
        (void) fprintf( stderr, "%s: missing arguments\n",label);
        exit(EXIT_FAILURE);
      }
      if(sscanf(argv[*numarg],"%d",&temp) != 1){
        (void) fprintf(stderr,"Bad red triplet argument/parameter >%s %s< \n",label,argv[*numarg]);
        exit(EXIT_FAILURE);
      }
      vals->red=temp;
      (*numarg)++;
      if(sscanf(argv[*numarg],"%d",&temp) != 1){
        (void) fprintf(stderr,"Bad green triplet argument/parameter >%s %s< \n",label,argv[*numarg]);
        exit(EXIT_FAILURE);
      }
      vals->green=temp;
      (*numarg)++;
      if(sscanf(argv[*numarg],"%d",&temp) != 1){
        (void) fprintf(stderr,"Bad blue triplet argument/parameter >%s %s< \n",label,argv[*numarg]);
        exit(EXIT_FAILURE);
      }
      vals->blue=temp;
      (*numarg)++;
      if(sscanf(argv[*numarg],"%d",&temp) != 1){
        (void) fprintf(stderr,"Bad opacity triplet argument/parameter >%s %s< \n",label,argv[*numarg]);
        exit(EXIT_FAILURE);
      }
      vals->opacity=temp;
}

void setdnumeric(double *val,int *numarg,int argc,char **argv,char * label){
      (*numarg)++;
      if( ( *numarg >= argc ) || (argv[*numarg] == NULL)){
        (void) fprintf( stderr, "%s: missing argument\n",label);
        exit(EXIT_FAILURE);
      }
      if(sscanf(argv[*numarg],"%lf",val) != 1){
        (void) fprintf(stderr,"Bad double argument/parameter >%s %s< \n",label,argv[*numarg]);
        exit(EXIT_FAILURE);
      }
}

void setstring(char **val,int *numarg,int argc,char **argv,char * label){
      (*numarg)++;
      if( ( *numarg >= argc ) || (argv[*numarg] == NULL)){
        (void) fprintf( stderr, "%s: missing argument\n",label);
        exit(EXIT_FAILURE);
      }
      *val = argv[*numarg];
}

void  validrange(int val, int vmin, int vmax, char *message){
  if(val >= vmin && val <= vmax)return;
  fprintf(stderr,"%s out of range. value [%d], min [%d], max [%d]\n",message,val,vmin,vmax);
  exit(EXIT_FAILURE);
}
  
void  process_command_line_args(int argc,char **argv, int *numarg){
char *ctemp;

  if(argc <2){
    emit_help();
  }
  
  while( ++(*numarg) < argc){
/*
  (void) fprintf(stderr,"DBG TOKEN: argv[numarg] %s\n",argv[*numarg]);
*/
    if(lcl_strcasecmp(argv[*numarg], "-draw")==0){
      if(infile==stdin)insane("Fatal error: -draw rewinds input table, and stdin cannot be rewound");
      rewind(infile);
      break;
    }
    if(lcl_strcasecmp(argv[*numarg], "-stats")==0){
      stats=1;
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-columns")==0){
      setinumeric(&flag_columns,numarg,argc,argv,"-columns");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-sr")==0){
      setinumeric(&sr,numarg,argc,argv,"-sr");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-er")==0){
      setinumeric(&er,numarg,argc,argv,"-er");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-over")==0){
      if(imginit != 1)insane("Fatal error: cannot use -over in this context");
      setstring(&overimage,numarg,argc,argv,"-over");
      imginit=2;
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-in")==0){
      setstring(&in,numarg,argc,argv,"-in");
      if(infile != NULL && infile != stdin){
        if(EOF == fclose(infile))insane("could not open close input file");
      }
      infile=fopen(in, "r");
      if(infile == NULL)insane("could not open input file");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-out")==0){
      setstring(&out,numarg,argc,argv,"-out");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-delimit")==0){
      setstring(&ctemp,numarg,argc,argv,"-delimit");
      stuff_delimits(delimits,ctemp);
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-xform")==0){
      setinumeric(&xform,numarg,argc,argv,"-xform");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-yform")==0){
      setinumeric(&yform,numarg,argc,argv,"-yform");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-xc")==0){
      setinumeric(&xc,numarg,argc,argv,"-xc");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-yc")==0){
      setinumeric(&yc,numarg,argc,argv,"-yc");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-xsize")==0){
      setinumeric(&xsize,numarg,argc,argv,"-xsize");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-ysize")==0){
      setinumeric(&ysize,numarg,argc,argv,"-ysize");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-xbase")==0){
      setinumeric(&xbase,numarg,argc,argv,"-xbase");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-ybase")==0){
      setinumeric(&ybase,numarg,argc,argv,"-ybase");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-xoff")==0){
      setinumeric(&xoff,numarg,argc,argv,"-xoff");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-yoff")==0){
      setinumeric(&yoff,numarg,argc,argv,"-yoff");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-condlogic")==0){
      setinumeric(&condlogic,numarg,argc,argv,"-condlogic");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-xcondc")==0){
      setinumeric(&xcondc,numarg,argc,argv,"-xcondc");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-ycondc")==0){
      setinumeric(&ycondc,numarg,argc,argv,"-ycondc");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-wcondc")==0){
      setinumeric(&wcondc,numarg,argc,argv,"-wcondc");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-rcondc")==0){
      setinumeric(&rcondc,numarg,argc,argv,"-rcondc");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-gcondc")==0){
      setinumeric(&gcondc,numarg,argc,argv,"-gcondc");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-bcondc")==0){
      setinumeric(&bcondc,numarg,argc,argv,"-bcondc");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-xcondmin")==0){
      setdnumeric(&xcondmin,numarg,argc,argv,"-xcondmin");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-ycondmin")==0){
      setdnumeric(&ycondmin,numarg,argc,argv,"-ycondmin");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-wcondmin")==0){
      setdnumeric(&wcondmin,numarg,argc,argv,"-wcondmin");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-rcondmin")==0){
      setdnumeric(&rcondmin,numarg,argc,argv,"-rcondmin");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-gcondmin")==0){
      setdnumeric(&gcondmin,numarg,argc,argv,"-gcondmin");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-bcondmin")==0){
      setdnumeric(&bcondmin,numarg,argc,argv,"-bcondmin");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-xcondmax")==0){
      setdnumeric(&xcondmax,numarg,argc,argv,"-xcondmax");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-ycondmax")==0){
      setdnumeric(&ycondmax,numarg,argc,argv,"-ycondmax");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-wcondmax")==0){
      setdnumeric(&wcondmax,numarg,argc,argv,"-wcondmax");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-rcondmax")==0){
      setdnumeric(&rcondmax,numarg,argc,argv,"-rcondmax");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-gcondmax")==0){
      setdnumeric(&gcondmax,numarg,argc,argv,"-gcondmax");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-bcondmax")==0){
      setdnumeric(&bcondmax,numarg,argc,argv,"-bcondmax");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-iwsc")==0){
      setinumeric(&iwsc,numarg,argc,argv,"-iwsc");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-iwbc")==0){
      setinumeric(&iwbc,numarg,argc,argv,"-iwbc");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-iwmin")==0){
      setdnumeric(&iwmin,numarg,argc,argv,"-iwmin");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-iwmax")==0){
      setdnumeric(&iwmax,numarg,argc,argv,"-iwmax");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-irsc")==0){
      setinumeric(&irsc,numarg,argc,argv,"-irsc");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-irbc")==0){
      setinumeric(&irbc,numarg,argc,argv,"-irbc");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-irmin")==0){
      setdnumeric(&irmin,numarg,argc,argv,"-irmin");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-irmax")==0){
      setdnumeric(&irmax,numarg,argc,argv,"-irmax");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-igsc")==0){
      setinumeric(&igsc,numarg,argc,argv,"-igsc");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-igbc")==0){
      setinumeric(&igbc,numarg,argc,argv,"-igbc");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-igmin")==0){
      setdnumeric(&igmin,numarg,argc,argv,"-igmin");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-igmax")==0){
      setdnumeric(&igmax,numarg,argc,argv,"-igmax");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-ibsc")==0){
      setinumeric(&ibsc,numarg,argc,argv,"-ibsc");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-ibbc")==0){
      setinumeric(&ibbc,numarg,argc,argv,"-ibbc");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-ibmin")==0){
      setdnumeric(&ibmin,numarg,argc,argv,"-ibmin");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-ibmax")==0){
      setdnumeric(&ibmax,numarg,argc,argv,"-ibmax");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-xscale")==0){
      setdnumeric(&xscale,numarg,argc,argv,"-xscale");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-yscale")==0){
      setdnumeric(&yscale,numarg,argc,argv,"-yscale");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-fill")==0){
      fill=1;
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-nowarn")==0){
      flag_nonnumeric=0;
      continue;
    }
    
    if(lcl_strcasecmp(argv[*numarg], "-white")==0){
      (void) sprintf(delimits," \t");
      parse_single=0;
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-noquotes")==0){
      quotes=0;
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-xloge")==0){
      xlog=1;
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-yloge")==0){
      ylog=1;
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-xlog10")==0){
      xlog=2;
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-ylog10")==0){
      ylog=2;
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-xinv")==0){
      xinv=1;
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-yinv")==0){
      yinv=1;
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-hisc")==0){
      setinumeric(&hisc,numarg,argc,argv,"-hisc");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-bins")==0){
      setinumeric(&bins,numarg,argc,argv,"-bins");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-hismax")==0){
      setdnumeric(&hismax,numarg,argc,argv,"-hismax");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-hismin")==0){
      setdnumeric(&hismin,numarg,argc,argv,"-hismin");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-radius")==0){
      setinumeric(&radius,numarg,argc,argv,"-radius");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-txtfont")==0){
      setstring(&txtfont,numarg,argc,argv,"-txtfont");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-txtfg")==0){
      setpixelpacket(&txtfg,numarg,argc,argv,"-txtfg");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-txtbg")==0){
      setpixelpacket(&txtbg,numarg,argc,argv,"-txtbg");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-txtc")==0){
      setinumeric(&txtc,numarg,argc,argv,"-txtc");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-txtpos")==0){
      setinumeric(&txtpos,numarg,argc,argv,"-txtpos");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-txtpntsize")==0){
      setinumeric(&txtpntsize,numarg,argc,argv,"-txtpntsize");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-txtangle")==0){
      setdnumeric(&txtangle,numarg,argc,argv,"-txtangle");
      continue;
    }
    if(lcl_strcasecmp(argv[*numarg], "-i")==0){
      (void)fprintf(stderr,"Version:   %s\n",VERSTRING);
      (void)fprintf(stderr,"bugs to:   mathog@caltech.edu\n");
      (void)fprintf(stderr,"Copyright: 2005,2002 David Mathog and California Institute of Technology\n");
      (void)fprintf(stderr,"License terms:\n");
      (void)fprintf(stderr,"    As specified in ImageMagick.  Where those license terms\n");
      (void)fprintf(stderr,"    do not apply the following do:\n");
      (void)fprintf(stderr,"\n");
      (void)fprintf(stderr,"    You may run this program on any platform. You may\n");
      (void)fprintf(stderr,"    redistribute the source code of this program subject to\n");
      (void)fprintf(stderr,"    the condition that you do not first modify it in any way.\n");
      (void)fprintf(stderr,"    You may  distribute binary versions of this program so long\n");
      (void)fprintf(stderr,"    as they were compiled from unmodified source code.  There\n");
      (void)fprintf(stderr,"    is no charge for using this software.  You may not charge\n");
      (void)fprintf(stderr,"    others for the use of this software.\n");
      exit(EXIT_SUCCESS);
    }
    if(  (lcl_strcasecmp(argv[*numarg], "-h")==0)     ||
         (lcl_strcasecmp(argv[*numarg], "-help")==0)  ||
         (lcl_strcasecmp(argv[*numarg], "--help")==0) ||
         (lcl_strcasecmp(argv[*numarg], "-?")==0)     ||
         (lcl_strcasecmp(argv[*numarg], "?")==0)      ||
         (lcl_strcasecmp(argv[*numarg], "h")==0)
      ){
      emit_help();
      continue;
    }
    (void) fprintf(stderr,"Fatal error - unknown command line argument: %s\n",argv[*numarg]);
    exit(EXIT_FAILURE);
  }
  /* sanity checking */
  if(stats){
    if(sr >= er)insane("Fatal error:  sr >= er");
    if(sr <  0 )insane("Fatal error:  sr < 0");
    if(flag_columns < 0 )insane("Fatal error:  columns must be > 0"); /* lie, it can be 0 */
    if(hisc != 0){
      if(flag_columns != 0 )insane("Fatal error: -columns cannot be used with -hisc");
      if(hismin >= hismax)insane("Fatal error:  -hismin value must be >= -hismax value");
      if(bins < 1)insane("Fatal error:  bins value must be >= 1");
      if(bins +2 >COLMAX)insane("Fatal error:  -bins value must be <=n 1000");
    }
  }
  else {
    if(overimage != NULL){
      if(imginit!=0 && (xsize !=0 || ysize != 0))
                       insane("Fatal error:  -over cannot be used with -xsize or -ysize");
    }
    else {
      if(xsize <= 0 )  insane("Fatal error: invalid xsize value");
      if(ysize <  0 )  insane("Fatal error: invalid ysize value");
    }
    if(flag_columns < 0 )
                       insane("Fatal error:  columns must be > 0"); /* lie, it can be 0 */
    if(txtpntsize < 5) insane("Fatal error:  txtpntsize < 5 will be illegible");
    if(txtc !=0){
       validrange(txtfg.red,    0,MaxRGB,"txtfg red");
       validrange(txtfg.green,  0,MaxRGB,"txtfg green");
       validrange(txtfg.blue,   0,MaxRGB,"txtfg blue");
       validrange(txtfg.opacity,0,MaxRGB,"txtfg opacity");
       validrange(txtbg.red,    0,MaxRGB,"txtbg red");
       validrange(txtbg.green,  0,MaxRGB,"txtbg green");
       validrange(txtbg.blue,   0,MaxRGB,"txtbg blue");
       validrange(txtbg.opacity,0,MaxRGB,"txtbg opacity");
    }
    if(condlogic < 0 || condlogic >3  )   insane("Fatal error:  invalid condlogic value");
    if(xcondc < 0  )   insane("Fatal error:  invalid xcondc value");
    if(ycondc < 0  )   insane("Fatal error:  invalid ycondc value");
    if(wcondc < 0  )   insane("Fatal error:  invalid wcondc value");
    if(rcondc < 0  )   insane("Fatal error:  invalid rcondc value");
    if(gcondc < 0  )   insane("Fatal error:  invalid gcondc value");
    if(bcondc < 0  )   insane("Fatal error:  invalid bcondc value");
    if(xcondmin > xcondmax) insane("Fatal error:  xcondmin > xcondmax");
    if(ycondmin > ycondmax) insane("Fatal error:  ycondmin > ycondmax");
    if(wcondmin > wcondmax) insane("Fatal error:  wcondmin > wcondmax");
    if(rcondmin > rcondmax) insane("Fatal error:  rcondmin > rcondmax");
    if(gcondmin > gcondmax) insane("Fatal error:  gcondmin > gcondmax");
    if(bcondmin > bcondmax) insane("Fatal error:  bcondmin > bcondmax");
    if(txtc < 0  )     insane("Fatal error:  no or invalid txtc value");
    if(xform < 0 || xform > FORMNUM)
                       insane("Fatal error:  invalid xform value");
    if(yform < 0 || yform > FORMNUM)
                       insane("Fatal error:  invalid xform value");
    if(txtpos < 1 || txtpos > 9 )
                       insane("Fatal error:  txtpos value must in range 1 to 9");
    if(hisc != 0)      insane("Fatal error:  histogram only possible with -stats");
    if(ysize == 0 )ysize=xsize;
    if(out==NULL)      insane("Fatal error: no output file specified");
    if(xc <= 0  )      insane("Fatal error: no or invalid xc value");
    if(yc <= 0  )      insane("Fatal error: no or invalid yc value");
    if(yc == xc && xc < COLMAX && xform==0 && yform==0)
                       insane("Fatal error: xc and yc may not point to the same column");
    if(iwsc <  0 )     insane("Fatal error: invalid iwsc value");
    if(iwsc == 0 && irsc == 0 && igsc == 0 && ibsc == 0 && txtc == 0)
                       insane("Fatal error: specify at least one of iwsc,irsc,igsc,ibsc, or txtc");
    if(iwsc && (irsc || igsc || ibsc ))
                       insane("Fatal error: specify either iwsc or (irsc,igsc, or ibsc)");
    if(irsc <  0 )     insane("Fatal error: no or invalid irsc value");
    if(igsc <  0 )     insane("Fatal error: no or invalid igsc value");
    if(ibsc <  0 )     insane("Fatal error: no or invalid ibsc value");
    if(iwmin >= iwmax) insane("Fatal error: irmin >= irmax");
    if(irmin >= irmax) insane("Fatal error: irmin >= irmax");
    if(igmin >= igmax) insane("Fatal error: igmin >= igmax");
    if(ibmin >= ibmax) insane("Fatal error: ibmin >= ibmax");
    if(igbc !=0 && igsc ==  0 ) insane("Fatal error: igbc defined but not igsc");
    if(ibbc !=0 && ibsc ==  0 ) insane("Fatal error: ibbc defined but not ibsc");
    if(xscale ==  0.0 && yscale <=  0.0 )  
                       insane("Fatal error: either xscale or yscale must be nonzero");
    if(radius <   0 )  insane("Fatal error: radius must be >= 0");
  }
  if(fill){
     rfg.red     = MaxRGB;
     rfg.green   = MaxRGB;
     rfg.blue    = MaxRGB;
     rfg.opacity = 0;
  }
  else{
     rfg.red     = 0;
     rfg.green   = 0;
     rfg.blue    = 0;
     rfg.opacity = MaxRGB;
  }
} /* end of process_command_line_args() */

int  lcl_strcasecmp(char *s1, char *s2){
int c1;
int c2;
  for(; ;s1++,s2++){
    c1=toupper(*s1);
    c2=toupper(*s2);
    if(c1 < c2)return -1;
    if(c1 > c2)return  1;
    if(c1 == 0)return  0;  /*c2 also is 0 in this case */
  }
}

void t2i_drawpixel(int x, int y, Image *image, PixelPacket *lfg){
register PixelPacket   *q;
  if(x>=0 && y >=0){  /* point may not be inside of drawn image */
    q=SetImagePixels(image,x,y,1,1);
    if (q == (PixelPacket *) NULL){
      insane("oops, some problem with SetImagePixels");
    }
 
    q->red     = lfg->red;
    q->green   = lfg->green;
    q->blue    = lfg->blue;
    q->opacity = lfg->opacity;
    (void) SyncImagePixels (image );
  }
} /* end of t2i_drawpixel() */

void t2i_drawsolidcircle(int x, int y, Image *image, PixelPacket *lfg, int radius){
register PixelPacket   *q;
int                     x2,y2;
int                     xdelta,ydelta;
int                     xrun,xlast;

   /* Implement clipping, so that any part of circle inside
      of drawn area will be shown */
      
   for(ydelta=-radius ; ydelta<=radius ; ydelta++){
     xdelta = sqrt( (double) ((radius*radius)-(ydelta)*(ydelta)) );
     x2     = x - xdelta;
     y2     = y + ydelta;
     xlast  = x + xdelta;
 
     /* 3 cases where nothing will be drawn for this row */
 
     if(xlast <= 0 )continue;
     if(x2 > xsize-1)continue;
     if(y2 < 0 || y2 >= ysize)continue;
 
     /* Clip the drawn region of this line, if necessary */
     if(x2 < 0)x2=0;
     if(xlast >= xsize)xlast = xsize-1;
     xrun=xlast - x2 + 1;
     q=SetImagePixels(image,x2,y2,xrun,1);
     if (q == (PixelPacket *) NULL){
       insane("oops, some problem with SetImagePixels");
     }
     for( ; x2 <= xlast ; x2++,q++){
       q->red     = lfg->red;
       q->green   = lfg->green;
       q->blue    = lfg->blue;
       q->opacity = lfg->opacity;
       (void) SyncImagePixels (image );
     }
   }
}  /* end of    t2i_drawsolidcircle       */

void t2i_dostats(char *buffer){
double      mean,stddev;
int         i,lc,cc,cmax,good;
double      val;
char       *token;
STATSTRUCT  statarray[COLMAX];

  if(hisc != 0){
    (void) fprintf(stdout,"Histogram of %d bins for column %d from %f to %f\n",
      bins,hisc,hismin,hismax);
    for(i=0 ; i <= bins+1 ; i++){
      statarray[i].good=0;
    }
  }
  else {
    (void) fprintf(stdout,"doing stats\n");
    for(i=0;i<COLMAX;i++){
      statarray[i].good=0;
      statarray[i].min=2000000000.;
      statarray[i].max=-2000000000.;
      statarray[i].sum=0.;
      statarray[i].sum2=0.;
    }
  }
  lc=0;
  good=0;
  cmax=0;
  while(fgets(buffer,MAXINLINE-1,infile) != NULL){
    lc++;
    if(lc >= sr && lc <= er){
      good++;
      token=strtok_variant(buffer,delimits);
      cc=0;
      while(token!=NULL){
        cc++;
        if(cc >= COLMAX){
          (void) fprintf(stderr,"Too many columns at row %d\n",lc);
          exit(EXIT_FAILURE);
        }
        if(sscanf(token,"%lf",&val) != 1){
          if(flag_nonnumeric && (hisc==0 || cc==hisc))(void) fprintf(stderr,"Ignoring nonnumeric data at: (%d,%d) data:>%s<\n",lc,cc,token);
        }
        else {
          if(hisc !=0){
            if(cc == hisc){ /* only true if hisc != 0 */
              if(val<hismin){
                statarray[bins].good++;
                break;
              }
              if(val>hismax){
                statarray[bins+1].good++;
                break;
              }
              else{
                val = (((double) bins)*(val - hismin))/(hismax - hismin);
                if(val<0)val=0;
                if(val>bins-1)val=bins-1;
                cc=val;
                statarray[cc].good++;
                break;
              }
            }
          }
          else{
            statarray[cc].good++;
            if(val < statarray[cc].min)statarray[cc].min = val;
            if(val > statarray[cc].max)statarray[cc].max = val;
            statarray[cc].sum  += val;
            statarray[cc].sum2 += (val*val);
          }
        }
        token=strtok_variant(NULL,delimits);
      } /* token != NULL loop */
      if(cc > cmax)cmax=cc;
      if(flag_columns && flag_columns != cc){
        (void) fprintf(stderr,"Row %7d had %10d columns\n",lc,cc);
      }
    } /* lc in range condition */
  } /* loop over all input lines */
  if(hisc){
    (void) fprintf(stdout,"%12s%10s\n","bin","count");
    (void) fprintf(stdout,"<%11.3f%10d\n",hismin,statarray[bins].good);
    for(i=0;i<bins;i++){
      (void) fprintf(stdout,"%12.3f%10d\n",
        (double) i * (hismax - hismin)/(double) bins,
        statarray[i].good);
    }
    (void) fprintf(stdout,">=%10.3f%10d\n",hismax,statarray[bins+1].good);
  }
  else{
    (void) fprintf(stdout,"good lines: %d\n",good);
    (void) fprintf(stdout,"columns:    %d\n",cmax);
    (void) fprintf(stdout,"%10s%10s%10s%10s%10s%10s\n",
        "column","good","min","max","average","std. dev");
    for(i=1;i<=cmax;i++){
      if(statarray[i].good != 0){
        mean   = statarray[i].sum/(double) statarray[i].good;
        stddev = (statarray[i].sum2/(double) statarray[i].good) -
                 mean*mean;
        if(stddev < 0.0)stddev=0.0; /* watch out for floating glitches*/
        stddev = sqrt(stddev);
        fprintf(stdout, "%10d%10.0d%10.0f%10.0f%10.0f%10.0f\n",
          i,
          statarray[i].good,
          statarray[i].min,
          statarray[i].max,
          mean,
          stddev);
      }
      else {
    (void) fprintf(stdout,"%10d%10d%10s%10s%10s%10s\n",
        i,0,"NA","NA","NA","NA");
      }
    }
  }
  exit(EXIT_SUCCESS);
} /* end of t2i_dostats */

double t2i_log(double val,int logflag){
double retval=0.0;
  if(logflag == 0){
    retval = val;
  }
  else {
    if(val <= 0){
      retval = -1.0e+30; /* a very large negative number */
    }
    else {
      switch (logflag) {
        case 1:  /* loge */
          retval = log(val);
          break;
        case 2:  /* log10 */
          retval = log10(val);
          break;
        default: 
          insane("Fatal programming error detected in t2i_log ");
      }
    }
  }
  return retval;
}


void t2i_initimage(Image **image_out, ImageInfo **image_info_out){
int                     x,y;
register PixelPacket   *q;
ExceptionInfo           exception;
Image                  *image=NULL;
ImageInfo              *image_info=NULL;

  image_info=CloneImageInfo((ImageInfo *) NULL);
  if(imginit == 1){ /* create image */
    image=AllocateImage(image_info);
    if (image == (Image *) NULL)
      MagickError(ResourceLimitError,"Unable to display image",
        "Memory allocation failed");

    /*
      Initialize image - fill it with the appropriate empty color.
    */
    image->columns=xsize;
    image->rows=ysize;
    for (y=0; y < image->rows; y++)
    {
      q=SetImagePixels(image,0,y,image->columns,1);
      if (q == (PixelPacket *) NULL)
        break;
      for (x=0; x < image->columns; x++)
      {
        q->red   = rfg.red;
        q->green = rfg.green;
        q->blue  = rfg.blue;
        q++;
      }
      if (!SyncImagePixels(image))
        break;
    }
  }
  else if(imginit==2){ /* read from input image */
    GetExceptionInfo(&exception);
    (void) strcpy(image_info->filename,overimage);
    image=ReadImage(image_info,&exception);
    if (image == (Image *) NULL){
      insane("Fatal error, could not open or read input image");
      insane(exception.reason);
    }
    xsize=image->columns;
    ysize=image->rows;
  }
  imginit=0;  /* only ever initialize one time */
  *image_out=image;
  *image_info_out=image_info;
} /* end of t2i_initimage() */


void t2i_useformula(int form, double cval, double bval, double *formval){
   
   if(form == 0){
     *formval = (cval - bval);
   }
   else if(form == 1){
       *formval = (cval * bval);
   }
   else if(form == 2){
       if(bval == 0){
         (void)fprintf(stderr,"warning: divide by zero on coordinate, result set to .0000001\n");
         *formval=.0000001;
       }
       else {
         *formval = (cval / bval);
       }
   }
}

void t2i_setfromrow(char *buffer,int lc, int *xp,int *yp,int *status){
int           x=-10000; /* well outside of picture, in case they don't get set */
int           y=-10000;
double        val;
char         *token;
int           hold1,hold2,hold3;
int           cc,col;
int           tests_total,tests_passed;

  hold1=0;
  hold2=0;
  hold3=0;
  holdtxt=NULL;
  *status=1;
  tests_total=0;
  tests_passed=0;
  token=strtok_variant(buffer,delimits);
  cc=0;
  xbval=xbase;
  ybval=ybase;
  while(token!=NULL){
    cc++;
    if(cc >= COLMAX){
      (void) fprintf(stderr,"Too many columns at row %d\n",lc);
      exit(EXIT_FAILURE);
    }
        
    if(cc == txtc){
      holdtxt = token;
    }
    else if(txtc > COLMAX){
      (void) sprintf(holdlctxt,"%d",lc);
      holdtxt=holdlctxt;
    }
    if(sscanf(token,"%lf",&val) != 1){
      if(flag_nonnumeric  && cc != txtc )(void) fprintf(stderr,"Ignoring nonnumeric data at: (%d,%d) data:>%s<\n",lc,cc,token);
    }
    else {
      if(cc == xc  || xc > COLMAX     ){  xcval = WHICHVAL(xc);   }
      if(cc == yc  || yc > COLMAX     ){  ycval = WHICHVAL(yc);   }
      if(xform>0 && (cc == xbase || xbase > COLMAX) ){  col=xbase; xbval = WHICHVAL(col); }
      if(yform>0 && (cc == ybase || ybase > COLMAX) ){  col=ybase; ybval = WHICHVAL(col); }
      if(cc == iwsc  || iwsc > COLMAX  ){ hold1 += WHICHVAL(iwsc); }
      if(cc == irsc  || irsc > COLMAX  ){ hold1 += WHICHVAL(irsc); }
      if(cc == igsc  || igsc > COLMAX  ){ hold2 += WHICHVAL(igsc); }
      if(cc == ibsc  || ibsc > COLMAX  ){ hold3 += WHICHVAL(ibsc); }
      if(cc == iwbc  || iwbc > COLMAX  ){ hold1 -= WHICHVAL(iwbc); }
      if(cc == irbc  || irbc > COLMAX  ){ hold1 -= WHICHVAL(irbc); }
      if(cc == igbc  || igbc > COLMAX  ){ hold2 -= WHICHVAL(igbc); }
      if(cc == ibbc  || ibbc > COLMAX  ){ hold3 -= WHICHVAL(ibbc); }
      TESTONE(xcondc,xcondmin,xcondmax);
      TESTONE(ycondc,ycondmin,ycondmax);
      TESTONE(wcondc,wcondmin,wcondmax);
      TESTONE(rcondc,rcondmin,rcondmax);
      TESTONE(gcondc,gcondmin,gcondmax);
      TESTONE(bcondc,bcondmin,bcondmax);
    }
    token=strtok_variant(NULL,delimits);
  } /* loop on token != NULL */
  
  t2i_useformula(xform,xcval,xbval,&xd);
  t2i_useformula(yform,ycval,ybval,&yd);
  x = t2i_log(xd,xlog)*xscale + xoff;  /* convert pixel units */
  y = t2i_log(yd,ylog)*yscale + yoff;  /* convert pixel units */

  if(flag_columns && flag_columns != cc){
    (void) fprintf(stderr,"Row %7d had %10d columns\n",lc,cc);
  }
  if(tests_total>0){
    switch (condlogic){
      case 0:
        if(tests_passed  != tests_total)*status=0;
        break;
      case 1:
        if(tests_passed  == 0          )*status=0;
        break;
      case 2:
        if(tests_passed  != 0          )*status=0;
        break;
      case 3:
        if(tests_passed  == tests_total)*status=0;
        break;
    }
    if(*status == 0)return;  /* do not draw */
  }
  if(iwsc){
    rfg.red      = scaledintense(hold1,iwmin,iwmax);
    rfg.green    = rfg.red;
    rfg.blue     = rfg.red;
  }
  else {
    rfg.red      = scaledintense(hold1,irmin,irmax);
    rfg.green    = scaledintense(hold2,igmin,igmax);
    rfg.blue     = scaledintense(hold3,ibmin,ibmax);
  }
  rfg.opacity = 0; /* always want to see the foreground */

  /* ImageMagick X,Y coords are from upper left of picture, but normal world's
     X,Y are from lower left of picture.  So xinv yinv work in opposite directions
     for x,y.  */

  if(xinv){
    x = (xsize-1) - x;
  }
 
  if(! yinv){
    y = (ysize-1) - y;
  }
  /* return the values calculated/extracted */
  *xp=x;
  *yp=y;
} /* end of t2i_setfromrow() */ 

/* Text can be drawn from x,y at any of the following points

  1      2       3
  4      5       6
  7      8       9
  
in the text box.  Left to its own devices the ImageMagick software
will always draw from 7.  This routine uses draw_info and type_metric
to reset the affine tx,ty so that the current position ends up
in the appropriate place on the image. This is drawn in ImageMagick
x,y coordinates, which are from upper left corner. 

t2i_drawtext puts (x,y) at the position specified by "position" and
the text at the angle specified by "angle" (in degrees) with the font
set by "font" and text set by "text".  Foreground and Background
colors are set by fg and bg, respectively.  If fg is null white is
fg, if bg is null, no background is set.
*/

void  t2i_drawtext(char *text, int x, int y,
   char *font, int antialias,
   PixelPacket *fg, PixelPacket *bg,
   int pointsize, int position, double angle,
   Image *image, DrawInfo *draw_info){
TypeMetric type_metric;
double     xd,yd,xr,yr;
double     w,a;
#define    T2I_MAXSTRING 2000
char       tbuffer[T2I_MAXSTRING];  /* no string should be this big ! */

  if(pointsize <= 0)insane("Fatal error, cannot draw text with pointsize <= 0");
  if(text == NULL)  insane("Fatal error, null pointer to text string in t2i_drawtext");
  if(strlen(text) >= T2I_MAXSTRING - 10)
                    insane("Fatal error, text string too long to draw"); 
                    
  (void)sprintf(tbuffer,"text 0,0 \"%s\"",text);
  draw_info->primitive=tbuffer;  /* this is what will be drawn */
  draw_info->text=text;          /* this is needed for gettypemetrics */
                   
  draw_info->font=font;          /* "helvetica", for instance */
  draw_info->text_antialias=antialias;  /* 0 disables, 1 sets*/
  draw_info->pointsize=pointsize;
  
  if(fg == NULL){
    draw_info->fill.red     = MaxRGB;
    draw_info->fill.green   = MaxRGB;
    draw_info->fill.blue    = MaxRGB;
    draw_info->fill.opacity = 0;
  }
  else {
    draw_info->fill.red     = fg->red;
    draw_info->fill.green   = fg->green;
    draw_info->fill.blue    = fg->blue;
    draw_info->fill.opacity = fg->opacity;
  }
  
  if(bg == NULL){
    draw_info->undercolor.opacity = MaxRGB; /* disables background */
  }
  else {
    draw_info->undercolor.red     = bg->red;
    draw_info->undercolor.green   = bg->green;
    draw_info->undercolor.blue    = bg->blue;
    draw_info->undercolor.opacity = bg->opacity;
  }
  
  /* Stroke is not used here, so disable it with the opacity value */
  draw_info->stroke.opacity=MaxRGB; 
  
  draw_info->affine.sx= cos(DegreesToRadians(fmod(txtangle,360.0)));
  draw_info->affine.rx= sin(DegreesToRadians(fmod(txtangle,360.0)));
  draw_info->affine.ry=-sin(DegreesToRadians(fmod(txtangle,360.0)));
  draw_info->affine.sy= cos(DegreesToRadians(fmod(txtangle,360.0)));
  /* tx,ty are set below */
 
  (void) GetTypeMetrics(image,draw_info,&type_metric);  

  w = (double) type_metric.width;
  a = (double) type_metric.ascent;
/*
  (void) printf("width, height, max_advance are %f %f %d \n",w,a,type_metric.max_advance);
*/
  xr= (double) x;
  yr= (double) y;
  switch (position){
    case 1:
      xd=  0.;
      yd=  a;
      break;
    case 2:
      xd= -w/2.;
      yd=  a;
      break;
    case 3:
      xd= -w;
      yd=  a;
      break;
    case 4:
      xd=  0.;
      yd=  a/2;
      break;
    case 5:
      xd= -w/2;
      yd=  a/2;
      break;
    case 6:
      xd= -w;
      yd=  a/2.;
      break;
    case 7:
      xd= 0.;
      yd= 0.;
      break;
    case 8:
      xd= -w/2.;
      yd=  0.;
      break;
    case 9:
      xd= -w;
      yd=  0.;
      break;
    default:
      insane("fatal programming error, text position not in range {1,9}");
      return;  /* this is unreachable but most compilers don't know that */
  }
  /* sx is cos(angle) and rx is sin(angle) */
  draw_info->affine.tx = xr + xd*draw_info->affine.sx - yd*draw_info->affine.rx;
  draw_info->affine.ty = yr + xd*draw_info->affine.rx + yd*draw_info->affine.sx;
/*
  (void) printf("final x,y %f %f \n",draw_info->affine.tx,draw_info->affine.ty);
*/
  DrawImage(image,draw_info); /* draw the text */
}


int main(int argc,char **argv)
{
  Image                  *image=NULL;
  ImageInfo              *image_info=NULL;
  int                     x,y;
  char                    buffer[MAXINLINE];
  int                     lc;
  int                     numarg;
  PixelPacket            *fg=NULL;
  PixelPacket            *bg=NULL;
  DrawInfo               *draw_info=NULL;
  int                     drawok;

  fg=&rfg;
  bg=&rbg;
  
  infile=stdin;  /* default, unless -in is used */
  numarg=0;
  (void) sprintf(delimits,",:\t");

  while(numarg < argc){
    process_command_line_args(argc,argv,&numarg);
 
    /* conditionally do statistics, if yes, then also exit program */
    
    if(stats)t2i_dostats(buffer);

    /*
      Create an empty image (all 0 or all MaxRGB) or open an existing one
      Side effect, sets imginit to 0
    */
    
    if(imginit != 0)t2i_initimage(&image,&image_info);
    draw_info = CloneDrawInfo( image_info, draw_info ); /* init the draw_info structure */

      
    /* read in the image data and write it into the image */
 
    lc=0;
    while(fgets(buffer,MAXINLINE-1,infile) != NULL){
      lc++;
      if(lc >= sr && lc <= er){

        t2i_setfromrow(buffer,lc,&x,&y,&drawok);
        if(!drawok)continue;
        if(txtc != 0){ /* text drawing modes */
/*
           (void)fprintf(stderr,"DBG txtfg    %d %d %d %d\n",
              txtfg.red,txtfg.green,txtfg.blue,txtfg.opacity);
           (void)fprintf(stderr,"DBG txtbg    %d %d %d %d\n",
              txtbg.red,txtbg.green,txtbg.blue,txtbg.opacity);
           (void)fprintf(stderr,"DBG x y      %d %d\n",x,y);
           (void)fprintf(stderr,"DBG pntsize  %d\n",txtpntsize);
           (void)fprintf(stderr,"DBG txtpos   %d\n",txtpos);
           (void)fprintf(stderr,"DBG txtangle %f\n",txtangle);
           (void)fprintf(stderr,"DBG font     %s\n",txtfont);
           (void)fprintf(stderr,"DBG text     %s\n",holdtxt);
*/
         t2i_drawtext(holdtxt, x, y,
              txtfont, 0,
              &txtfg, &txtbg,
              txtpntsize, txtpos, txtangle,
              image, draw_info);
        }
        else { /* figure drawing modes */
          if(radius == 0){ /* draw a pixel */
            t2i_drawpixel(x,y,image,fg);
          }
          else {
            t2i_drawsolidcircle(x,y,image,fg,radius);
          }
        }
      } /* lc in range condition */
    } /* loop over all input file lines */
  } /* while loop on numarg */
  

  /*
    write image.
  */
      (void) strcpy(image->filename,out);
      WriteImage(image_info,image);
  /*
    Free resources.
  */
  DestroyImageInfo(image_info);
  DestroyImage(image);
  exit(EXIT_SUCCESS);
}


/* figure out how to use the DrawImage mechanism 
  
  char  tbuffer[200]; 
  int   i;
  
  txtangle=0.0;
  fg->red=MaxRGB;
  fg->opacity=0;
  bg->blue=MaxRGB;
  bg->opacity=0;
  for (i=1; i<=9; i++){
    (void) sprintf(tbuffer,"Position %d",i);
    t2i_drawtext(tbuffer, 150, i*50,
       "helvetica", 0,
       fg, bg,
       i*4, i, txtangle,
       image, draw_info);
    DrawImage(image,draw_info);
    draw_info->affine.tx=150;
    draw_info->affine.ty=50*i;
    draw_info->primitive="circle 0,0,0,3";
    DrawImage(image,draw_info); 
  }
  txtangle=45.0;
  for (i=1; i<=9; i++){
    (void) sprintf(tbuffer,"Position %d",i);
    t2i_drawtext(tbuffer, 350, i*50,
       "helvetica", 0,
       fg, bg,
       i*4, i, txtangle,
       image, draw_info);
    DrawImage(image,draw_info); 
    draw_info->primitive="circle 0,0,0,3";
    draw_info->affine.tx=350;
    draw_info->affine.ty=50*i;
    DrawImage(image,draw_info); 
  }
*/

