/*****************************************************************************
   Major portions of this software are copyrighted by the Medical College
   of Wisconsin, 1994-2000, and are released under the Gnu General Public
   License, Version 2.  See the file README.Copyright for details.
******************************************************************************/

/*---------------------------------------------------------------------------*/
/*
  These routines estimate the probability density function (PDF) 
  corresponding to the distribution of gray-matter and white-matter
  voxel intensities.  The estimate is formed as the sum of three normal
  distributions, using the Simplex algorithm for non-linear optimization
  to estimate the unknown parameters.

  File:    estpdf.c
  Author:  B. Douglas Ward
  Date:    27 May 1999

  Mod:     Extensive restructuring of the code.
  Date:    28 January 2000

  Mod:     USE_QUIET (RWCox)
  Date:    16 Apr 2003
*/

#ifndef USE_QUIET  /* 16 Apr 2003 */
# define quiet 0
#endif


/*---------------------------------------------------------------------------*/
/*
  Include header files and forward declarations.
*/

#include "pdf.h"

float calc_error (float * vertex);


/*---------------------------------------------------------------------------*/
/*
  Global variables and constants.
*/

#define DIMENSION 9      /* number of parameters to be estimated */

pdf p;                   /* empirical pdf */


/*---------------------------------------------------------------------------*/
/*
  Include source code files.
*/

#include "randgen.c"
#include "pdf.c"
#include "Simplexx.c"


/*---------------------------------------------------------------------------*/
/*
  Perform initialization for estimation of PDF from short array. 
*/

void estpdf_short_initialize 
(
  int nxyz, 
  short * sfim,
  float * gpeak,             /* estimated peak of gray-matter distribution */
  float * wpeak              /* estimated peak of white-matter distribution */
)

{
  pdf ps;
  int gmax, wmax;
  int kk;
  int ok = 1;


  /*---- Initialize pdf's -----*/
  PDF_initialize (&p);
  PDF_initialize (&ps);


  /*----- Convert short array to pdf estimate -----*/
  PDF_short_to_pdf (nxyz, sfim, &p);
  PDF_sprint ("\nOriginal PDF:", p);


  /*----- Trim extreme values from pdf estimate -----*/
  PDF_trim (0.01, 0.99, &p);
  PDF_sprint ("\nTrimmed PDF:", p);


  /*----- Smooth the pdf estimate -----*/
  PDF_copy (p, &ps);
  PDF_smooth (&ps);
  PDF_sprint ("\nSmoothed PDF:", ps);


  /*----- Try to locate bimodality of the pdf -----*/
  ok = PDF_find_bimodal (ps, &gmax, &wmax);
  if (ok)
    {
      *gpeak = PDF_ibin_to_xvalue (ps, gmax);
      *wpeak = PDF_ibin_to_xvalue (ps, wmax);
    }
  else
    {
      printf ("Unable to find bimodal distribution \n");
      *gpeak = (2.0/3.0)*p.lower_bnd + (1.0/3.0)*p.upper_bnd;
      *wpeak = (1.0/3.0)*p.lower_bnd + (2.0/3.0)*p.upper_bnd;
    }


  if( !quiet ){
   printf ("\nInitial PDF estimates: \n");
   printf ("Lower Bnd = %8.3f   Upper Bnd  = %8.3f \n", 
	   p.lower_bnd, p.upper_bnd);
   printf ("Gray Peak = %8.3f   White Peak = %8.3f \n", *gpeak, *wpeak);
  }


  PDF_destroy (&ps);

}


/*---------------------------------------------------------------------------*/
/*
  Perform initialization for estimation of PDF from float array. 
*/

void estpdf_float_initialize 
(
  int nxyz, 
  float * ffim,
  int nbin,
  float * gpeak,             /* estimated peak of gray-matter distribution */
  float * wpeak              /* estimated peak of white-matter distribution */
)

{
  pdf ps;
  int gmax, wmax;
  int kk;
  int ok = 1;


  /*---- Initialize pdf's -----*/
  PDF_initialize (&p);
  PDF_initialize (&ps);


  /*----- Convert float array to pdf estimate -----*/
  PDF_float_to_pdf (nxyz, ffim, nbin, &p);
  PDF_sprint ("\nOriginal PDF:", p);


  /*----- Trim extreme values from pdf estimate -----*/
  PDF_trim (0.01, 0.99, &p);
  PDF_sprint ("\nTrimmed PDF:", p);


  /*----- Smooth the pdf estimate -----*/
  PDF_copy (p, &ps);
  PDF_smooth (&ps);
  PDF_sprint ("\nSmoothed PDF:", ps);


  /*----- Try to locate bimodality of the pdf -----*/
  ok = PDF_find_bimodal (ps, &gmax, &wmax);
  if (ok)
    {
      *gpeak = PDF_ibin_to_xvalue (ps, gmax);
      *wpeak = PDF_ibin_to_xvalue (ps, wmax);
    }
  else
    {
      printf ("Unable to find bimodal distribution \n");
      *gpeak = (2.0/3.0)*p.lower_bnd + (1.0/3.0)*p.upper_bnd;
      *wpeak = (1.0/3.0)*p.lower_bnd + (2.0/3.0)*p.upper_bnd;
    }


  if( !quiet ){
    printf ("\nInitial PDF estimates: \n");
    printf ("Lower Bnd = %8.3f   Upper Bnd  = %8.3f \n", 
	    p.lower_bnd, p.upper_bnd);
    printf ("Gray Peak = %8.3f   White Peak = %8.3f \n", *gpeak, *wpeak);
  }


  PDF_destroy (&ps);

}


/*---------------------------------------------------------------------------*/
/*
  Generate the initial guess for the parameter vector.
*/

void generate_initial_guess (float gpeak, float wpeak, float * parameters)
{
  float b;                   /* coefficient for background distribution */
  float bmean;               /* mean for background distribution */ 
  float bsigma;              /* std. dev. for background distribution */
  float g;                   /* coefficient for gray-matter distribution */
  float gmean;               /* mean for gray-matter distribution */
  float gsigma;              /* std. dev. for gray-matter distribution */
  float w;                   /* coefficient for white-matter distribution */
  float wmean;               /* mean for white-matter distribution */
  float wsigma;              /* std. dev. for white-matter distribution */


  /*----- Initialize distribution coefficients -----*/
  b = 0.75;
  g = 0.25;
  w = 0.25;


  /*----- Initialize distribution means -----*/
  bmean = p.lower_bnd;

  if ((gpeak > p.lower_bnd) && (gpeak < p.upper_bnd) && (gpeak < wpeak)) 
    gmean = gpeak;
  else
    gmean = p.lower_bnd;

  if ((wpeak > p.lower_bnd) && (wpeak < p.upper_bnd) && (wpeak > gpeak))
    wmean = wpeak;
  else
    wmean = p.upper_bnd;

  if ((gmean-bmean) < 0.25*(wmean-bmean))  gmean = bmean + 0.25*(wmean-bmean);
  if ((wmean-gmean) < 0.25*(wmean-bmean))  gmean = wmean - 0.25*(wmean-bmean);


  /*----- Initialize distribution standard deviations -----*/
  bsigma = 0.25 * (p.upper_bnd - p.lower_bnd);
  gsigma = 0.25 * (wmean - gmean);
  wsigma = 0.25 * (wmean - gmean);


  /*----- Set parameter vector -----*/
  parameters[0] = b;
  parameters[1] = bmean;
  parameters[2] = bsigma;
  parameters[3] = g;
  parameters[4] = gmean;
  parameters[5] = gsigma;
  parameters[6] = w;
  parameters[7] = wmean;
  parameters[8] = wsigma;

}


/*---------------------------------------------------------------------------*/
/*
  Write parameter vector.
*/

void write_parameter_vector (float * parameters)
{
  int i;

  printf ("Dimension = %d \n", DIMENSION);
  for (i = 0;  i < DIMENSION;  i++)
    printf ("parameter[%d] = %f \n", i, parameters[i]);
}


/*---------------------------------------------------------------------------*/
/*
  Normal probability density function.
*/

float normal (float x, float mean, float sigma)

{
  float z;

  z = (x - mean) / sigma;

  return ( (1.0/(sqrt(2.0*PI)*sigma)) * exp (-0.5 * z * z) );
}


/*---------------------------------------------------------------------------*/
/*
  Estimate the voxel intensity distribution as the sum of three normal PDF's.
*/

float estimate (float * parameters, float x)

{
  float b, bmean, bsigma, g, gmean, gsigma, w, wmean, wsigma;
  float z, fval;


  /*----- Initialize local variables -----*/
  b      = parameters[0];
  bmean  = parameters[1];
  bsigma = parameters[2];
  g      = parameters[3];
  gmean  = parameters[4];
  gsigma = parameters[5];
  w      = parameters[6];
  wmean  = parameters[7];
  wsigma = parameters[8];


  /*----- Calculate the sum of three normal PDF's -----*/ 
  fval  = b * normal (x, bmean, bsigma);
  fval += g * normal (x, gmean, gsigma);
  fval += w * normal (x, wmean, wsigma);


  return (fval);
  
}


/*---------------------------------------------------------------------------*/
/*
  Calculate the error sum of squares for the PDF estimate.
*/

float calc_error (float * vertex)

{
  const float BIG_NUMBER = 1.0e+10;  /* return when constraints are violated */

  float b, bmean, bsigma, g, gmean, gsigma, w, wmean, wsigma;  /* parameters */
  float deltah, deltam;          /* rough estimate of spread of distribution */

  int i;
  float t;
  float diff, sse;

  count += 1;


  /*----- Assign local variables -----*/
  b      = vertex[0];
  bmean  = vertex[1];
  bsigma = vertex[2];
  g      = vertex[3];
  gmean  = vertex[4];
  gsigma = vertex[5];
  w      = vertex[6];
  wmean  = vertex[7];
  wsigma = vertex[8];

  deltah = p.upper_bnd - p.lower_bnd;
  deltam = wmean - gmean;


  /*----- Apply constraints? -----*/
  if ((b < 0.05) || (b > 1.5))          return (BIG_NUMBER);
  if ((g < 0.05) || (g > 1.0))          return (BIG_NUMBER);
  if ((w < 0.05) || (w > 1.0))          return (BIG_NUMBER);
  if ((b+g+w < 1.0) || (b+g+w > 2.0))   return (BIG_NUMBER);

  if ((bmean < p.lower_bnd) || (bmean > p.upper_bnd))  return (BIG_NUMBER);
  if ((gmean < p.lower_bnd) || (gmean > p.upper_bnd))  return (BIG_NUMBER);
  if ((wmean < p.lower_bnd) || (wmean > p.upper_bnd))  return (BIG_NUMBER);
  if ((gmean < bmean)    || (gmean > wmean))     return (BIG_NUMBER);

  if ((gmean-bmean) < 0.10*(wmean-bmean))        return (BIG_NUMBER);
  if ((wmean-gmean) < 0.10*(wmean-bmean))        return (BIG_NUMBER);

  if ((bsigma < 0.01*deltah) || (bsigma > 0.5*deltah))  return (BIG_NUMBER);
  if ((gsigma < 0.01*deltam) || (gsigma > 0.5*deltam))  return (BIG_NUMBER);
  if ((wsigma < 0.01*deltam) || (wsigma > 0.5*deltam))  return (BIG_NUMBER);


  /*----- Not constrained, so calculate actual error sum of squares -----*/
  sse = 0.0;

  for (i = 0;  i < p.nbin;  i++)
    {
      t = PDF_ibin_to_xvalue (p, i);
      diff = p.prob[i] - estimate (vertex, t)*p.width;
      sse += diff * diff;
    }

  
  return (sse);
}


/*---------------------------------------------------------------------------*/
/*
  Write the parameter estimates.
*/

void output_pdf_results (float * vertex, float sse)
{
  float b, bmean, bsigma, g, gmean, gsigma, w, wmean, wsigma;


  /*----- Assign variables -----*/
  b      = vertex[0];
  bmean  = vertex[1];
  bsigma = vertex[2];
  g      = vertex[3];
  gmean  = vertex[4];
  gsigma = vertex[5];
  w      = vertex[6];
  wmean  = vertex[7];
  wsigma = vertex[8];

  if( !quiet ){
   printf ("\nProbability Density Function Estimates: \n");
   printf ("Background Coef      = %f \n", b);
   printf ("Background Mean      = %f \n", bmean);
   printf ("Background Std Dev   = %f \n", bsigma);
   printf ("Gray Matter Coef     = %f \n", g);
   printf ("Gray Matter Mean     = %f \n", gmean);
   printf ("Gray Matter Std Dev  = %f \n", gsigma);
   printf ("White Matter Coef    = %f \n", w);
   printf ("White Matter Mean    = %f \n", wmean);
   printf ("White Matter Std Dev = %f \n", wsigma);
   printf ("\nrmse = %f \n", sqrt (sse / p.nbin ));
  }

}


/*---------------------------------------------------------------------------*/
/*
  Estimate the PDF of the voxel intensities.
*/

void estpdf_short (int nxyz, short * sfim, float * parameters)
{
  float gpeak;               /* estimated peak of gray-matter distribution */
  float wpeak;               /* estimated peak of white-matter distribution */
  float sse;


  /*----- Progress report -----*/
  if( !quiet )
   printf ("\nEstimating PDF of voxel intensities \n");

  
  /*----- Initialization for PDF estimation -----*/
  estpdf_short_initialize (nxyz, sfim, &gpeak, &wpeak);


  generate_initial_guess (gpeak, wpeak, parameters);
 

  /*----- Get least squares estimate for PDF parameters -----*/
  simplex_optimization (parameters, &sse);


  /*----- Report PDF parameters -----*/
  output_pdf_results (parameters, sse);


  /*----- Free memory -----*/
  /*
  PDF_destroy (&p);
  */

  return;
}


/*---------------------------------------------------------------------------*/
/*
  Estimate the PDF of the voxel intensities.
*/

void estpdf_float (int nxyz, float * ffim, int nbin, float * parameters)
{
  float gpeak;               /* estimated peak of gray-matter distribution */
  float wpeak;               /* estimated peak of white-matter distribution */
  float sse;


  /*----- Progress report -----*/
  if( !quiet )
   printf ("\nEstimating PDF of voxel intensities \n");

  
  /*----- Initialization for PDF estimation -----*/
  estpdf_float_initialize (nxyz, ffim, nbin, &gpeak, &wpeak);


  /*----- Make initial estimate of the parameters from previous results -----*/
  generate_initial_guess (gpeak, wpeak, parameters);
 

  /*----- Get least squares estimate for PDF parameters -----*/
  simplex_optimization (parameters, &sse);


  /*----- Report PDF parameters -----*/
  output_pdf_results (parameters, sse);


  /*----- Free memory -----*/
  /*
  PDF_destroy (&p);
  */
 
  return ;
}


/*---------------------------------------------------------------------------*/
