/******************************************************************************/
/* feature:     extract features from image sequence                          */
/******************************************************************************/
/*
/*    input file:  image sequence of signature samples
                   file containing authenticity of image sequence (0 = forgery; 1 = authentic)
                      0/1 followed by newlines.

      input file:  If used in batch processing mode, the image is only 1 frame, 
                   and the target result can be specified with t=
*/


#include "VisXV4.h"          /* VisX structure include file     */
#include "Vutil.h"           /* VisX utility header files       */
VXparam_t par[] =  
{
{ "if=",    0, " input file  : sequence of signatures"},
{ "tf=",    0, " target output file"},
{ "of=",    0,   " output file.  Input file to NN program  "},
{ "-d",     0,  " DEBUG mode "},
{ "n=",     0,  " Size of set (# of frames.)  default=1 "},
{ "t=",     0,  " Target if n=1 and no file specified "},
{ "-h",     0,  " put the vnet header into output file"},
{  0,       0,   0}
};
#define  IVAL   par[0].val
#define  TVAL   par[1].val
#define  OVAL   par[2].val
#define  DEBUG  par[3].val
#define  NVAL   par[4].val
#define  TARVAL par[5].val
#define  HEADVAL par[6].val

#define AUTH  1
#define FORGE 0

/*D is the dimension of the feature vector*/
#define D 98

/*MAXSTROKES limits the number of strokes found,  for large signatures, there are many strokes, 
  and the feature vector will get too large for current computational limitations */
#define MAXSTROKES 15

typedef struct features{
   unsigned int height;
   unsigned int width;
   unsigned int area;
   struct com {
      float x;
      float y;
   }com;
   unsigned int edgepts;
   unsigned int crosspts;
   unsigned int loops; 
   float length[MAXSTROKES];
   int bot_x[MAXSTROKES];
   int bot_y[MAXSTROKES];
   int top_x[MAXSTROKES];
   int top_y[MAXSTROKES];
   float slope[MAXSTROKES];
   int grid[12][8];
} features;


VisXfile_t *VXin;            /* input file structure            */
FILE *ofile ,                /* output file structure           */
     *tfile;                 /* target file structure           */
VisXelem_t *vxlist,*VXpt;    /* VisX data structure             */
VisXimage_t im;
VisXimage_t tm;
features f;

void inline extract_height(void);
void inline extract_width(void);
void inline extract_area_and_com(void);
void inline extract_slope();
void inline extract_edge_and_cross(void);
void inline write_features(FILE*);
void inline write_targets(int,FILE*);
void print_features();
void write_tfile_header(FILE*, int, int, int);
void extract_strokes();
void label_stroke_up(int,int,int);
void label_stroke_down(int,int,int);

main(argc, argv)
int argc;
char *argv[];
{
   int         t,n,i,j,k;                 /* index counters                          */
   VXparse(&argc, &argv, par);    /* parse the command line                  */
   VXin  = VXopen(IVAL, 0);       /* open input file                         */
   ofile = fopen(OVAL, "w");       /* open the output file                   */
   if(TVAL)
      tfile = fopen(TVAL, "r");       /* open target file                       */
   if(NVAL)
      n = atoi(NVAL);
   else
      n = 1;

   if(TARVAL)
      t = atoi(TARVAL);
   else
      t = 99;

   if(HEADVAL)
      write_tfile_header(ofile, D, 1, n);
 
   while ((vxlist = VXreadframe(VXin) )  != VXNIL)
   {
      VXsetimage(&im, vxlist, VXin);
      VXembedimage(&tm,&im,1,1,1,1); /* image structure with border  */
/********************* Application specific section ***************************/

      /*initialize grid*/
      for(i = 0; i < 12; i++)
         for(j = 0; j < 8; j++)
            f.grid[i][j] = -1;
      /* extract features, and fill features structure f */

      /*Global Features*/
      extract_height();
      extract_width();
      extract_area_and_com();
/*
      extract_slope();
*/
      extract_edge_and_cross();

      /*strokes features */
      extract_strokes();

      /** Write to files and update **/
      if(DEBUG)
         print_features();

      write_features(ofile);

      if((TVAL != NULL) | (TARVAL != NULL)) {
         if(!TARVAL) {
            fscanf(tfile,"%d",t);
         }
         write_targets(t,ofile);
      } else {
         fprintf(ofile, "\n");
      }

      VXdellist(vxlist);
      /******************************/

/******************* End of the Application specific section ******************/
   }                                         /* end of every frame section   */
   VXclose(VXin);                            /* close files                  */
   fclose(ofile);
   fclose(tfile);
   exit(0);
}


/*Since the input images are in a bounding box, computing height is simple*/
void inline extract_height()
{
   f.height = im.yhi - im.ylo + 1;
}

/*since the input images are in a bounding box, computing width is simple*/
void inline extract_width()
{
   f.width = im.xhi - im.xlo + 1;
}

/*Go through the image and calculate the area, and center of mass*/
void inline extract_area_and_com()
{
   int i,j,xc,yc,a;
   a = 0;
   xc = 0;
   yc = 0;
   for(i = im.ylo; i <= im.yhi; i++) {
      for(j = im.xlo; j <= im.xhi; j++) {
         if(im.u[i][j] != 0) 
         {
            if(f.grid[j*11/im.xhi][i*7/im.yhi] != -1)
               f.grid[j*11/im.xhi][i*7/im.yhi]++;
            else
               f.grid[j*11/im.xhi][i*7/im.yhi] = 1;
            a++;
            xc += j;
            yc += i;
         }
      }
   }
   f.area = a;
   f.com.x = ((float) xc)/((float)a) ;
   f.com.y = ((float) yc)/((float)a) ;
}

/*extract edge and cross points*/
/*edge points = EP = points that have only 1 8-neighbor */
/*cross points = points that have 3 or more 8-neighbors */
/*closed loops = CL = 1 + (EL-EP)/2 */
/*EL = sum over all cross points (#8-neighbors for current cross point - 2) */
void inline extract_edge_and_cross()
{
   int i,j,ep,cp,el,neighbors;
   ep = 0;
   cp = 0;
   el = 0;
   for(i = im.ylo; i <= im.yhi; i++) {
      for(j = im.xlo; j <= im.xhi; j++) {
         if(tm.u[i][j] != 0) {
            neighbors = 0;
            if(tm.u[i+1][j+1] != 0) neighbors++;
            if(tm.u[i+1][j] != 0) neighbors++;
            if(tm.u[i+1][j-1] != 0) neighbors++;
            if(tm.u[i][j+1] != 0) neighbors++;
            if(tm.u[i][j-1] != 0) neighbors++;
            if(tm.u[i-1][j+1] != 0) neighbors++;
            if(tm.u[i-1][j] != 0) neighbors++;
            if(tm.u[i-1][j-1] != 0) neighbors++;
            if(neighbors == 1) ep++;
            if(neighbors >= 3) {
               cp++;
               el += neighbors-2;
            }
         }
      }
   }
   f.edgepts = ep;
   f.crosspts = cp;
   f.loops = 1 + ((el - ep)/2);
}

/* DEBUG routine for displaying contents of a features struct */
void print_features() {
  int i;
  printf("Features\n");
  printf("heightxwidth = %d x %d\n",f.height,f.width);
  printf("Area = %d\n",f.area);
  printf("Center of mass = %f x %f\n", f.com.x, f.com.y); 
  printf("Edge pts: %d\nCross pts: %d\nClosed Loops:%d\n",f.edgepts, f.crosspts, f.loops);
  for(i = 0; i<MAXSTROKES; i++) {
     printf("Len:%f  Slope:%f\nbot: %d x %d\ntop: %d x %d\n",f.length[i],f.slope[i],f.top_x[i],f.top_y[i],
                                                           f.bot_x[i],f.bot_y[i]);
  }
}


/*Once we found the strokes, extract the features associated with it*/
void extract_stroke_features(int l) {
   int label,i,j;
   int *bot_x, *bot_y, *top_x, *top_y;
   float *slope, *length;
   double dx,dy;
   slope = (float*) malloc((l-1) * sizeof(float));
   length = (float*) malloc((l-1) * sizeof(float));
   bot_x = (int*) malloc((l-1) * sizeof(int));
   bot_y = (int*) malloc((l-1) * sizeof(int));
   top_x = (int*) malloc((l-1) * sizeof(int));
   top_y = (int*) malloc((l-1) * sizeof(int));
   for(i = 0; i < l-1; i++)
      bot_x[i] = -1;

   /*find the highest and the lowest pixel in the stroke*/
   for(i = im.ylo; i <= im.yhi; i++) {
      for(j = im.xlo; j <= im.xhi; j++) {
         label = tm.u[i][j];
         if(label != 0) {
            if(bot_x[label-2] == -1) {
               bot_x[label-2] = j;
               bot_y[label-2] = i;
            }
            top_x[label-2] = j;
            top_y[label-2] = i;
         }
      }
   }
   
   /*find the slope of the line connecting the top and bottom points, and its length*/
   for(i = 0; i < l-1; i++) {
      dy = top_y[i] - bot_y[i];
      dx = top_x[i] - bot_x[i];
      slope[i] = (dx == 0)? 0:(float) dy/dx;
      length[i] = (float) pow(((dy*dy) + (dx*dx)),.5); 
   }


   /*fill in feature vector.  If we have less strokes than MAXSTROKES, fill in zeros */
   for(i = 0; i < l-1; i++) {
      if(i >= MAXSTROKES)
         break;
      f.length[i] = length[i];
      f.bot_x[i] = bot_x[i];
      f.bot_y[i] = bot_y[i];
      f.top_x[i] = top_x[i];
      f.top_y[i] = top_y[i];
      f.slope[i] = slope[i];
   }
   for(;i < MAXSTROKES; i++) {
      f.length[i] = 0;
      f.bot_x[i] = 0;
      f.bot_y[i] = 0;
      f.top_x[i] = 0;
      f.top_y[i] = 0;
      f.slope[i] = 0;
   }
}
   
/*to extract strokes, scan the image for an un-labeled pixel.  Then
mark the pixel, and the stroke going up and down for one complete stroke */
void extract_strokes() {
   int i,j,label;
   label = 2;
   for(j = im.xlo; j <= im.xhi; j++) {
      for(i = im.ylo; i <= im.yhi; i++) {
         if(tm.u[i][j] == 1) {
            label_stroke_up(label, j, i);
            label_stroke_down(label, j, i);
            label++;
         }
      }
   } 
   extract_stroke_features(label-1);
}

/*A pixel is part of the current up-stroke if it is above the current pixel (or immediately right of it) */
void label_stroke_up(int l, int x, int y) {
   tm.u[y][x] = l;
/*printf("L=%d x=%d y=%d\n",l,x,y);*/
   if(tm.u[y][x+1] == 1) {
      label_stroke_up(l,x+1,y);
   } else if(tm.u[y+1][x-1] == 1) {
      label_stroke_up(l,x-1,y+1);
   } else if(tm.u[y+1][x] == 1) {
      label_stroke_up(l,x,y+1);
   } else if(tm.u[y+1][x+1] == 1) {
      label_stroke_up(l,x+1,y+1);
   }
}

/*A pixel is part of the current down-stroke if it is below the current pixel*/
void label_stroke_down(int l, int x, int y) {
   tm.u[y][x] = l;
/*printf("L=%d x=%d y=%d\n",l,x,y);*/
   if(tm.u[y][x-1] == 1) {
      label_stroke_down(l,x-1,y);
   } else if(tm.u[y-1][x-1] == 1) {
      label_stroke_down(l,x-1,y-1);
   } else if(tm.u[y-1][x] == 1) {
      label_stroke_down(l,x,y-1);
   } else if(tm.u[y-1][x+1] == 1) {
      label_stroke_down(l,x+1,y-1);
   }
}

/*utilities for writing the feature vector information*/
#define write_strokes(of,i) {fprintf(of,"%f %d %d %d %d %f ",f.length[i],f.top_x[i],f.top_y[i],f.bot_x[i],f.bot_y[i],f.slope[i]);}

void write_features(FILE* out_file) {
   int i,j;
   fprintf(out_file, "%d %d %d %f %f %d %d %d ", f.height, f.width, f.area,
                                                  f.com.x, f.com.y,
                                                  f.edgepts, f.crosspts, 
                                                  f.loops);
   /*for(i = 0; i < MAXSTROKES; i++)
      write_strokes(out_file,i);*/
   for(i = 0; i < 12; i++)
      for(j = 0; j < 8; j++)
         fprintf(out_file,"%d ",f.grid[i][j]);
}

void write_targets(int t, FILE* out_file) {
   fprintf(out_file, "%d\n", t);
}

void write_tfile_header(FILE* out_file, int d, int sf, int n) {
   fprintf(out_file, "%d %d %d\n", d, sf, n);
}
