/*
# pgrm: mis2evt - produces the eigenvectors from the covariance matrix of 32x32
# pgrm:           or 128X128 binary characters stored in an MIS file.
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/param.h>
#include <defs.h>
#include <ihead.h>
#include <mis.h>
#include <mfs.h>

#define ES  	  32
#define ES4      128            /* always 4 ES          */
#define ES2     1024            /* always ES * ES       */

/* makes the covariance matrix of binary 32 x 32 mis images     */
/* from +1,-1 binary images a mean vector is subtracted         */
/* the code is a little more cryptic than would be the case for */
/* for grey level images since large efficiency gains are       */
/* available for binary data                                    */

/* speed is data dependent - faster for fewer true ink pixels   */

/* Covariance is R = (1/N) \sum_{i} (u_{i} - \mu) (u_{i} - \mu)^{T} 	*/
/*                 = (1/N) \sum_{i} u_{i}u_{i}^{T} - \mu \mu^{T} 	*/
/* the products are skipped as 1.1 = 1 and -1.1 = -1 and -1.-1 = 1 	*/
/* are reducible to equality tests andd initialization to -1 of all 	*/


/* EISPAK routines are called for the eigensolutions of the covariance 	*/
/* Any routines that produce eigenvectors in eigenvalue order will be 	*/
/* sufficient. The covariance is symmetric and positive definite 	*/

/* the code is something of a memory hog: there are three arrays 	*/
/* icov, cova, z that are all dimensioned ES2*ES2 each taking ~4Mb 	*/
/* of memory. It may be possible to "union" icov and z since they are 	*/
/* never used at the same time. This is Fortran's equivalant statement 	*/

int    imea[ES2]; 			/* global since ES too big 	*/
float  cova[ES2][ES2], mean[ES2]; 	/* get run time core dump 	*/

float w[ES2], d[ES2], e2[ES2], stdv[ES2];
float e[ES2], bd[ES2], e2s[ES2]; 
float w1[ES2], w2[ES2], w3[ES2], w4[ES2], w5[ES2];
int   ind[ES2];

union fandi 
{ 
   float z[ES2][ES2]; 
   int   icov[ES2][ES2]; 
}  both; 

main(argc, argv)
int argc; char **argv;
{
char  fn[MAXPATHLEN], *evtfile; MFS *names;
char  *allocate_image(), *pm1, *base, *bptr, a;
int   i, j, k, n, total, progress, writemis;
MIS   *inp, *out;
float *p, fotal, ub, lb, eps1;
int   ierr, m, idef, reqevts;
FILE  *fp;
float *sfctr, *scal_x, *scal_y;

   procargs(argc, argv, &names, &reqevts, &evtfile, &progress, &writemis);

   if (reqevts > ES2)
   {
      fprintf(stderr, "Incorrect usage: the number of eigenvectors cannot\n");
      fprintf(stderr, "possibly exceed the dimensionality of the images.\n"); 
      fprintf(stderr, "Continuing anyway...");
      reqevts = ES2;
   }

   for ( j = 0 ; j < ES2 ; j++ )
      for ( k = 0 ; k <= j ; k++ )
         both.icov[j][k] = 0;

      for ( j = 0 ; j < ES2 ; j++ )
         imea[j]    = 0;

   for ( i = 0, total = 0 ; i < names->num ; i++)
   {
      inp = readmisfile(names->values[i]);
      total += inp->ent_num;

      if (progress == TRUE)
         fprintf(stdout, "doing %4d chars of %s\r",
            inp->ent_num, names->values[i]), fflush(stdout);

      if ((inp->misd != 1))
         fatalerr(argv[0], names->values[i], "not binary data");
      if (!((inp->enth == ES ) && (inp->entw == ES )) &&
          !((inp->enth == ES4) && (inp->entw == ES4))  )
         fatalerr(argv[0], names->values[i],
            "not 32x32 nor 128x128 mis entries");

      if (inp->entw == ES4)
      {  
         norm_2nd_gen2(&out, &scal_x, &scal_y, inp);
         freemis(inp);   
         free(scal_x);
         free(scal_y); 
         shear_mis(&inp, &sfctr, out);  
         freemis(out); 
         free(sfctr);
         if (writemis == TRUE)
            sprintf(fn, "%s.32", names->values[i]),
            writemisfile(fn, inp);
      }  

      /* --------------------------------------------------------------- */
      /* expand the binary data by a factor of eight so that 1 bit is    */
      /* now represented as one char 1 or 0                              */
      pm1 = allocate_image(inp->misw, inp->mish, 8);
      bits2bytes(inp->data, pm1, inp->misw * inp->mish);
      /* --------------------------------------------------------------- */

      /* accumulate the mean vector of all chars of all misfiles */
      for ( n = 0, base = pm1 ; n < inp->ent_num ; n++)
         for ( j = 0 ; j < ES2 ; j++ )
            if (*base++)
               imea[j]++;


      /* all the time goes in this loop */
      /* the if statement makes the execution time data dependent  */
      /* the != is quicker than the == since more pixels are equal */

      /* calculate the correlation matrix of all chars of all misfiles */
      for ( n = 0, base = pm1 ; n < inp->ent_num ; n++, base += ES2 )
         for ( j = 0 ; j < ES2 ; j++ )
            for ( k = 0, a = base[j], bptr = base ; k <= j ; k++ )
               if (a != *bptr++)
                  both.icov[j][k]--;

      free(pm1);
      freemis(inp);
   }
   fotal = (float)total;

   /* finish of the mean calculation. prepare and then convert to floats */
      for ( j = 0 ; j < ES2 ; j++ )
         imea[j] <<= 1,
         imea[j]  -= total,
         mean[j]   = (float)imea[j] / fotal;

   /* subtract the outer product of the mean */
   /* this converts the correlation matrix to the true covariance */
   for ( j = 0 ; j < ES2 ; j++ )
      for ( k = 0 ; k <=  j ; k++ )
         both.icov[j][k] <<= 1,
         both.icov[j][k]  += total,
         cova[j][k]   = (float)both.icov[j][k] / fotal,
         cova[j][k]  -= mean[j] * mean[k];

   /* complete the symmetric half of the matrix */
   /* depending on the algorithm and its implementation for */
   /* the eigensolutions this may not be necessary */
   for ( j = 0 ; j < ES2 ; j++ )
      for ( k = 0 ; k < j ; k++ )
         cova[k][j] = cova[j][k];


   n = ES2;
   for ( i = 0 ; i < n ; i++ ) 		/* set up data for the unused 	*/
      stdv[i] = 1.0; 			/* second vector of the evt file*/

   if (progress == TRUE)
      fprintf(stdout, "\nCovariance computed from %d images\n", total);

   /* covariance done */
   /* start eigenvector calculation */

   tred1_(&n, &n, cova, d, e, e2);
   for(i = 0; i < n; i++)
      e2s[i] = e2[i];
   eps1 = 0.;
   m    = 1;
   idef = 0;
   if (progress == TRUE)
      fprintf(stdout, "Tridiagonalization done\n");


   ratcall_(&n, &eps1, d, e, e2, &m, w, ind, bd, &idef, &ierr);
   if (ierr != 0)
   {
       fprintf(stderr, "Abnormal return from ratcall: ");
       fprintf(stderr, "returned ierr = %d. w[0] = %f\n", ierr, w[0]);
       exit(-1);
   }

   lb = 0.002;  /* the number of eigenvalues is determined by this */
                /* in that only those above this will be made      */
                /* the original value was 0.1 */
 
   ub = w[0]+1.;
   bisect_(&n, &eps1, d, e, e2s, &lb, &ub, &n, &m, w, ind, &ierr, w1, w2);
   if (ierr)
   {
       fprintf(stderr, "Abnormal return from bisect: ");
       fprintf(stderr, "returned ierr = %d\n", ierr);
       exit(-2);
   }
   if (progress == TRUE)
      fprintf(stdout, "Bisection for tridiag matrix eigenvalues done\n");


   tinvit_(&n, &n, d, e, e2s, &m, w, ind, both.z, &ierr, w1, w2, w3, w4, w5);
   if (ierr)
   {
       fprintf(stderr, "Abnormal return from tinvit: ");
       fprintf(stderr, "returned ierr = %d\n",  ierr);
       exit(-3);
   }
   if (progress == TRUE)
      fprintf(stdout, "Tridiagonal Eigenvectors found\n");


   trbak1_(&n, &n, cova, e, &m, both.z);
   if (progress == TRUE)
      fprintf(stdout, "Back subst. done - covariance eigenvectors found.\n");


   if (reqevts > m)
   {
       fprintf(stdout, "Found less eigenvectors than requested\n");
       reqevts = m;
   }

   fprintf(stdout, "%d Eigenvectors found, writing %d,\n", m, reqevts);
   fprintf(stdout, "corresponding to highest eigenvalues\n");

   if ((fp = fopen(evtfile, "w")) == NULL)
      syserr(argv[0], "fopen", evtfile);

   fwrite(&reqevts, sizeof(int), 1, fp);/* num eigenvectors required 	*/
   fwrite(&n,       sizeof(int), 1, fp);/* dimensionality of images 	*/
    					/* is fixed here at ES2 = 1024	*/

   fwrite(mean, sizeof(float), n, fp);
   fwrite(stdv, sizeof(float), n, fp);

   /* eigenvectors are in reverse order so we cannot call the usual 	*/
   /* routine for writing a basis set. just do it here 			*/
   for ( p = (float *)both.z + n * (m - 1), i = 0 ; i < reqevts ; p -= n, i++ )
      fwrite(p, sizeof(float), n, fp);

   fclose(fp);
   freemfs(names);
   free(evtfile);
}



procargs(argc, argv, names, nevts, evtfile, progress, writemis)
int	  argc, *nevts, *progress, *writemis;
char	**argv, **evtfile; MFS **names;
{
extern int getopt(), optind;
int c;

   if (argc < 4)
      usage(argv);

   *writemis = FALSE;
   *progress = FALSE;

   while ((c = getopt(argc, argv, "nv")) != -1)
      switch (c)
      {  
          case 'n' : *writemis = TRUE; break;
          case 'v' : *progress = TRUE; break;
          default  :  usage(argv);
      }

   if (argc - optind != 3)
      usage(argv);

   sscanf(argv[optind++], "%d", nevts);
   *evtfile = strdup(argv[optind++]);
   *names = readmfsfile(argv[optind]);
}

usage(argv)
char **argv;
{
   fprintf(stderr, "Usage: %s:\n", argv[0]);
   fprintf(stderr, "\t-n  for 128x128 input write normed+sheared 32x32 intermediate misfiles\n");
   fprintf(stderr, "\t-v  be verbose - notify completion of each image\n");
   fprintf(stderr, "\t    nrequiredevts evtfile mfs_of_misfiles\n");
   exit(1);
}
