/*****************************************************************************
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.
******************************************************************************/
/*---------------------------------------------------------------------------*/
/*
This program estimates 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.
The Simplex algorithm is adapted from: A Jump Start Course in C++ Programming
File: estpdf.c
Author: B. Douglas Ward
Date: 27 May 1999
*/
/*---------------------------------------------------------------------------*/
/*
Include source code.
*/
#include "randgen.c"
/*---------------------------------------------------------------------------*/
/*
Global variables and constants.
*/
#define DIMENSION 9 /* number of parameters to be estimated */
int * hist_data; /* histogram of voxel gray-scale intensities */
int hist_min; /* minimum histogram bin value; this is set to
exclude the large population near zero */
int hist_max; /* maximum histogram bin value; this is set to
exclude voxels with very high intensities */
int gpeak; /* estimated peak of gray-matter distribution */
int wpeak; /* estimated peak of white-matter distribution */
int numpts; /* number of voxels within the histogram limits */
int count = 0; /* count of sse evaluations */
int number_restarts = 0; /* count of simplex algorithm restarts */
/*---------------------------------------------------------------------------*/
/*
Set up the histogram and generate some of the initial parameter estimates.
This routine was adapted from: program 3dstrip.c
*/
#undef MAX
#define MAX(a,b) (((a)<(b)) ? (b) : (a))
#undef MIN
#define MIN(a,b) (((a)>(b)) ? (b) : (a))
#define NPMAX 128
void find_base_value( int nxyz , short * sfim )
{
float bper = 50.0 , bmin = 1 ;
float tper = 98.0;
int ii , kk , nbin , sval , sum , nbot , a,b,c , npeak,ntop , nvox ;
int * fbin ;
int kmin[NPMAX] , kmax[NPMAX] ;
int nmin , nmax ;
/*-- make histogram of shorts --*/
fbin = (int *) malloc( sizeof(int) * 32768 ) ;
for( kk=0 ; kk < 32768 ; kk++ ) fbin[kk] = 0 ;
nvox = 0 ;
for( ii=0 ; ii < nxyz ; ii++ ){
kk = sfim[ii] ; if( kk >= 0 ){ fbin[kk]++ ; nvox++ ; }
}
/*-- find largest value --*/
for( kk=32767 ; kk > 0 ; kk-- ) if( fbin[kk] > 0 ) break ;
if( kk == 0 ){fprintf(stderr,"** find_base_value: All voxels are zero!\n");exit(1);}
nbin = kk+1 ;
/*-- find bper point in cumulative distribution --*/
sval = 0.01 * bper * nvox ;
sum = 0 ;
for( kk=0 ; kk < nbin ; kk++ ){
sum += fbin[kk] ; if( sum >= sval ) break ;
}
nbot = kk ; if( nbot == 0 ) nbot = 1 ; if( bmin > nbot ) nbot = bmin ;
if( nbot >= nbin-9 ){
fprintf(stderr,"** find_base_value: Base point on histogram too high\n");
exit(1);
}
hist_min = nbot;
/*-- find tper point in cumulative distribution --*/
sval = 0.01 * tper * nvox ;
sum = 0 ;
for( kk=0 ; kk < nbin ; kk++ ){
sum += fbin[kk] ; if( sum >= sval ) break ;
}
hist_max = kk ;
/*----- Save original histogram data -----*/
hist_data = (int *) malloc( sizeof(int) * nbin ) ;
for (kk = 0; kk < nbin; kk++)
hist_data[kk] = fbin[kk];
/*-- smooth histogram --*/
b = fbin[nbot-1] ; c = fbin[nbot] ;
for( kk=nbot ; kk < nbin ; kk++ ){
a = b ; b = c ; c = fbin[kk+1] ; fbin[kk] = 0.25*(a+c+2*b) ;
}
/*-- find minima and maxima above bper point --*/
nmin = nmax = 0 ;
for( kk=nbot+1 ; kk < nbin ; kk++ ){
if( fbin[kk] < fbin[kk-1] && fbin[kk] < fbin[kk+1] && nmin < NPMAX ){
kmin[nmin++] = kk ;
} else if( fbin[kk] > fbin[kk-1] && fbin[kk] > fbin[kk+1] && nmax < NPMAX ){
kmax[nmax++] = kk ;
}
}
/*-- find the largest two maxima --*/
if( nmax == 0 ){
fprintf(stderr,"** find_base_value: No histogram maxima above base point\n");
exit(1);
}
if( nmax == 1 ){
npeak = kmax[0] ; ntop = 0 ;
} else {
int f1,f2 , k1,k2 , fk , klow,kup ;
k1 = 0 ; f1 = fbin[kmax[0]] ;
k2 = 1 ; f2 = fbin[kmax[1]] ;
if( f1 < f2 ){
k1 = 1 ; f1 = fbin[kmax[1]] ;
k2 = 0 ; f2 = fbin[kmax[0]] ;
}
for( kk=2 ; kk < nmax ; kk++ ){
fk = fbin[kmax[kk]] ;
if( fk > f1 ){
f2 = f1 ; k2 = k1 ;
f1 = fk ; k1 = kk ;
} else if( fk > f2 ){
f2 = fk ; k2 = kk ;
}
}
npeak = MIN( kmax[k1] , kmax[k2] ) ; /* smaller bin of the 2 top peaks */
/* find valley between 2 peaks */
ntop = MAX( kmax[k1] , kmax[k2] ) ;
gpeak = npeak;
wpeak = ntop;
fk = fbin[ntop] ; klow = ntop ;
for( kk=ntop-1 ; kk >= npeak ; kk-- ){
if( fbin[kk] < fk ){ fk = fbin[kk] ; klow = kk ; }
}
fk = MAX( 0.10*fk , 0.05*fbin[ntop] ) ;
kup = MIN( nbin-1 , ntop+3*(ntop-klow+2) ) ;
for( kk=ntop+1 ; kk <= kup ; kk++ ) if( fbin[kk] < fk ) break ;
ntop = kk ;
}
for( kk=npeak-1 ; kk > 0 ; kk-- )
if( fbin[kk] < fbin[kk-1] && fbin[kk] < fbin[kk+1] ) break ;
if (kk > hist_min) hist_min = kk ;
if( ntop == 0 )
{
ntop = npeak + (npeak-kk) ;
gpeak = npeak;
wpeak = ntop;
}
free (fbin);
return ;
}
/*---------------------------------------------------------------------------*/
/*
Perform initialization for estimation of PDF.
*/
Boolean estpdf_initialize ()
{
int nx, ny, nz, nxy, nxyz, ixyz; /* voxel counters */
int n; /* histogram bin index */
short * sfim; /* pointer to anat data */
/*----- Initialize local variables -----*/
if (anat == NULL) return (FALSE);
nx = DSET_NX(anat); ny = DSET_NY(anat); nz = DSET_NZ(anat);
nxy = nx*ny; nxyz = nxy*nz;
sfim = (short *) DSET_BRICK_ARRAY(anat,0) ;
if (sfim == NULL) return (FALSE);
/*----- Set up histogram, and get initial parameter estimates -----*/
find_base_value (nxyz, sfim);
/*----- Count number of voxels inside histogram intensity limits -----*/
numpts = 0;
for (n = hist_min; n < hist_max; n++)
numpts += hist_data[n];
/*----- Initialize random number generator -----*/
srand48 (1234567);
return (TRUE);
}
/*---------------------------------------------------------------------------*/
/*
Generate the initial guess for the parameter vector.
*/
void generate_initial_guess (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 = hist_min;
if ((gpeak > hist_min) && (gpeak < hist_max) && (gpeak < wpeak))
gmean = gpeak;
else
gmean = hist_min;
if ((wpeak > hist_min) && (wpeak < hist_max) && (wpeak > gpeak))
wmean = wpeak;
else
wmean = hist_max;
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 * (hist_max - hist_min);
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_sse (float * vertex)
{
const float delt = 1.0; /* histogram bin size */
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 = hist_max - hist_min;
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 < hist_min) || (bmean > hist_max)) return (BIG_NUMBER);
if ((gmean < hist_min) || (gmean > hist_max)) return (BIG_NUMBER);
if ((wmean < hist_min) || (wmean > hist_max)) 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 = hist_min; i < hist_max; i++)
{
t = i * delt;
diff = ((float) hist_data[i])/numpts - estimate (vertex, t);
sse += diff * diff;
}
return (sse);
}
/*---------------------------------------------------------------------------*/
void allocate_arrays (float *** simplex, float ** centroid,
float ** response, float ** step_size,
float ** test1, float ** test2)
{
int i;
*centroid = (float *) malloc (sizeof(float) * DIMENSION);
*response = (float *) malloc (sizeof(float) * (DIMENSION+1));
*step_size = (float *) malloc (sizeof(float) * DIMENSION);
*test1 = (float *) malloc (sizeof(float) * DIMENSION);
*test2 = (float *) malloc (sizeof(float) * DIMENSION);
*simplex = (float **) malloc (sizeof(float *) * (DIMENSION+1));
for (i = 0; i < DIMENSION+1; i++)
(*simplex)[i] = (float *) malloc (sizeof(float) * DIMENSION);
}
/*---------------------------------------------------------------------------*/
void deallocate_arrays (float *** simplex, float ** centroid,
float ** response, float ** step_size,
float ** test1, float ** test2)
{
int i;
free (*centroid); *centroid = NULL;
free (*response); *response = NULL;
free (*step_size); *step_size = NULL;
free (*test1); *test1 = NULL;
free (*test2); *test2 = NULL;
for (i = 0; i < DIMENSION+1; i++)
{
free ((*simplex)[i]);
(*simplex)[i] = NULL;
}
free (*simplex); *simplex = NULL;
}
/*---------------------------------------------------------------------------*/
void eval_vertices (float * response, int * worst, int * next, int * best)
{
int i;
/* initialize values */
*worst = 0;
*best = 0;
/* find the best and worst */
for (i = 1; i < DIMENSION+1; i++)
{
if (response[i] > response[*worst])
*worst = i;
if (response[i] < response[*best])
*best = i;
}
/* find the next worst index */
if (*worst == 0)
*next = 1;
else
*next = 0;
for (i = 0; i < DIMENSION+1; i++)
if ((i != *worst) && (response[i] > response[*next]))
*next = i;
}
/*---------------------------------------------------------------------------*/
void restart (float ** simplex, float * response,
float * step_size)
{
const float STEP_FACTOR = 0.9;
int i, j;
int worst, next, best;
float minval, maxval;
/* find the current best vertex */
eval_vertices (response, &worst, &next, &best);
/* set the first vertex to the current best */
for (i = 0; i < DIMENSION; i++)
simplex[0][i] = simplex[best][i];
/* decrease step size */
for (i = 0; i < DIMENSION; i++)
step_size[i] *= STEP_FACTOR;
/* set up remaining vertices of simplex using new step size */
for (i = 1; i < DIMENSION+1; i++)
for (j = 0; j < DIMENSION; j++)
{
minval = simplex[0][j] - step_size[j];
maxval = simplex[0][j] + step_size[j];
simplex[i][j] = rand_uniform (minval, maxval);
}
/* initialize response for each vector */
for (i = 0; i < DIMENSION+1; i++)
response[i] = calc_sse (simplex[i]);
}
/*---------------------------------------------------------------------------*/
/*
Calculate the centroid of the simplex, ignoring the worst vertex.
*/
void calc_centroid (float ** simplex, int worst, float * centroid)
{
int i, j;
for (i = 0; i < DIMENSION; i++)
{
centroid[i] = 0.0;
/* add each vertex, except the worst */
for (j = 0; j < DIMENSION+1; j++)
if (j != worst)
centroid[i] += simplex[j][i];
}
/* divide by the number of vertices */
for (i = 0; i < DIMENSION; i++)
centroid[i] /= DIMENSION;
}
/*---------------------------------------------------------------------------*/
/*
Calculate the reflection of the worst vertex about the centroid.
*/
void calc_reflection (float ** simplex, float * centroid,
int worst, float coef, float * vertex)
{
int i;
for (i = 0; i < DIMENSION; i++)
vertex[i] = centroid[i] + coef*(centroid[i] - simplex[worst][i]);
}
/*---------------------------------------------------------------------------*/
/*
Replace a vertex of the simplex.
*/
void replace (float ** simplex, float * response,
int index, float * vertex, float resp)
{
int i;
for (i = 0; i < DIMENSION; i++)
simplex[index][i] = vertex[i];
response[index] = resp;
}
/*---------------------------------------------------------------------------*/
/*
Calculate goodness of fit.
*/
float calc_good_fit (float * response)
{
int i;
float avg, sd, tmp;
/* average the response values */
avg = 0.0;
for (i = 0; i < DIMENSION+1; i++)
avg += response[i];
avg /= DIMENSION+1;
/* compute standard deviation of response */
sd = 0.0;
for (i = 0; i < DIMENSION+1; i++)
{
tmp = response[i] - avg;
sd += tmp*tmp;
}
sd /= DIMENSION;
return (sqrt(sd));
}
/*---------------------------------------------------------------------------*/
/*
Perform initialization for the Simplex algorithm.
*/
void simplex_initialize (float * parameters, float ** simplex,
float * response, float * step_size)
{
int i, j;
int worst, next, best;
float resp;
float minval, maxval;
for (j = 0; j < DIMENSION; j++)
{
simplex[0][j] = parameters[j];
step_size[j] = 0.5 * parameters[j];
}
for (i = 1; i < DIMENSION+1; i++)
for (j = 0; j < DIMENSION; j++)
{
minval = simplex[0][j] - step_size[j];
maxval = simplex[0][j] + step_size[j];
simplex[i][j] = rand_uniform (minval, maxval);
}
for (i = 0; i < DIMENSION+1; i++)
response[i] = calc_sse (simplex[i]);
for (i = 1; i < 500; i++)
{
for (j = 0; j < DIMENSION; j++)
{
minval = simplex[0][j] - step_size[j];
maxval = simplex[0][j] + step_size[j];
parameters[j] = rand_uniform (minval, maxval);
}
resp = calc_sse (parameters);
eval_vertices (response, &worst, &next, &best);
if (resp < response[worst])
replace (simplex, response, worst, parameters, resp);
}
}
/*---------------------------------------------------------------------------*/
/*
Use Simplex algorithm to estimate the voxel intensity distribution.
The Simplex algorithm is adapted from: A Jump Start Course in C++ Programming
*/
void simplex_optimization (float * parameters, float * sse)
{
const int MAX_ITERATIONS = 100;
const int MAX_RESTARTS = 25;
const float EXPANSION_COEF = 2.0;
const float REFLECTION_COEF = 1.0;
const float CONTRACTION_COEF = 0.5;
const float TOLERANCE = 1.0e-10;
float ** simplex = NULL;
float * centroid = NULL;
float * response = NULL;
float * step_size = NULL;
float * test1 = NULL;
float * test2 = NULL;
float resp1, resp2;
int i, worst, best, next;
int num_iter, num_restarts;
int done;
float fit;
allocate_arrays (&simplex, ¢roid, &response, &step_size, &test1, &test2);
simplex_initialize (parameters, simplex, response, step_size);
/* start loop to do simplex optimization */
num_iter = 0;
num_restarts = 0;
done = 0;
while (!done)
{
/* find the worst vertex and compute centroid of remaining simplex,
discarding the worst vertex */
eval_vertices (response, &worst, &next, &best);
calc_centroid (simplex, worst, centroid);
/* reflect the worst point through the centroid */
calc_reflection (simplex, centroid, worst,
REFLECTION_COEF, test1);
resp1 = calc_sse (test1);
/* test the reflection against the best vertex and expand it if the
reflection is better. if not, keep the reflection */
if (resp1 < response[best])
{
/* try expanding */
calc_reflection (simplex, centroid, worst, EXPANSION_COEF, test2);
resp2 = calc_sse (test2);
if (resp2 <= resp1) /* keep expansion */
replace (simplex, response, worst, test2, resp2);
else /* keep reflection */
replace (simplex, response, worst, test1, resp1);
}
else if (resp1 < response[next])
{
/* new response is between the best and next worst
so keep reflection */
replace (simplex, response, worst, test1, resp1);
}
else
{
/* try contraction */
if (resp1 >= response[worst])
calc_reflection (simplex, centroid, worst,
-CONTRACTION_COEF, test2);
else
calc_reflection (simplex, centroid, worst,
CONTRACTION_COEF, test2);
resp2 = calc_sse (test2);
/* test the contracted response against the worst response */
if (resp2 > response[worst])
{
/* new contracted response is worse, so decrease step size
and restart */
num_iter = 0;
num_restarts += 1;
restart (simplex, response, step_size);
}
else /* keep contraction */
replace (simplex, response, worst, test2, resp2);
}
/* test to determine when to stop.
first, check the number of iterations */
num_iter += 1; /* increment iteration counter */
if (num_iter >= MAX_ITERATIONS)
{
/* restart with smaller steps */
num_iter = 0;
num_restarts += 1;
restart (simplex, response, step_size);
}
/* limit the number of restarts */
if (num_restarts == MAX_RESTARTS) done = 1;
/* compare relative standard deviation of vertex responses
against a defined tolerance limit */
fit = calc_good_fit (response);
if (fit <= TOLERANCE) done = 1;
/* if done, copy the best solution to the output array */
if (done)
{
eval_vertices (response, &worst, &next, &best);
for (i = 0; i < DIMENSION; i++)
parameters[i] = simplex[best][i];
*sse = response[best];
}
} /* while (!done) */
number_restarts = num_restarts;
deallocate_arrays (&simplex, ¢roid, &response, &step_size,
&test1, &test2);
}
/*---------------------------------------------------------------------------*/
/*
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];
printf ("hist_min = %d hist_max = %d \n", hist_min, hist_max);
printf ("gpeak = %d wpeak = %d \n", gpeak, wpeak);
printf ("rmse = %f \n", sqrt (sse / (hist_max - hist_min) ) );
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);
}
/*---------------------------------------------------------------------------*/
/*
Estimate the PDF of the voxel intensities.
*/
Boolean estpdf (float * parameters)
{
float sse;
Boolean ok;
/*----- Progress report -----*/
if (! quiet) printf ("\nEstimating PDF of voxel intensities \n");
/*----- Initialization for PDF estimation -----*/
ok = estpdf_initialize ();
if (! ok) return (FALSE);
generate_initial_guess (parameters);
/*----- Get least squares estimate for PDF parameters -----*/
simplex_optimization (parameters, &sse);
/*----- Report PDF parameters -----*/
if (! quiet) output_pdf_results (parameters, sse);
/*----- Free memory -----*/
free (hist_data); hist_data = NULL;
return (TRUE);
}
/*---------------------------------------------------------------------------*/
syntax highlighted by Code2HTML, v. 0.9.1