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

/*---------------------------------------------------------------------------*/
/*
  This file contains routines for hierarchical clustering of user specified 
  parametric data.


  File:    StatClust.c
  Author:  B. Douglas Ward
  Date:    08 October 1999

  Mod:     Replaced C "pow" function, significantly improving execution speed.
  Date:    11 October 1999

  Mod:     Replaced dataset interface code with call to THD_open_dataset.
           Restructured code for initializing hierarchical clustering.
  Date:    19 October 1999

  Mod:     At each output cluster agglomeration step, print to the screen
           which clusters are to be combined, and their distance.
  Date:    05 September 2000

  Mod:     Corrected error in sort_clusters routine.
  Date:    30 April 2001

*/

/*---------------------------------------------------------------------------*/
/*
  Structure declarations 
*/


struct cluster;
struct voxel;


typedef struct cluster
{
  float * centroid;
  int num_voxels;
  struct voxel * voxel_ptr;
  float nearest_dist;
  struct cluster * nearest_cluster;
  struct cluster * next_cluster;
} cluster;


typedef struct voxel
{
  int index;
  struct voxel * next_voxel;
} voxel;


/*---------------------------------------------------------------------------*/
/*
  Create a new voxel having the given index number.
*/

voxel * new_voxel (int index)
{
  voxel * voxel_ptr = NULL;
  
  voxel_ptr = (voxel *) malloc (sizeof(voxel));
  MTEST (voxel_ptr);

  voxel_ptr->index = index;
  voxel_ptr->next_voxel = NULL;
  return (voxel_ptr);
}


/*---------------------------------------------------------------------------*/
/*
  Print the index number for this voxel.
*/

void print_voxel (voxel * voxel_ptr)
{
  if (voxel_ptr != NULL)
    printf ("%d ", voxel_ptr->index);
}


/*---------------------------------------------------------------------------*/
/*
  Print all voxels in the linked list.
*/

void print_all_voxels (voxel * voxel_ptr)
{
  while (voxel_ptr != NULL)
    {
      print_voxel (voxel_ptr);
      voxel_ptr = voxel_ptr->next_voxel;
    }
  printf ("\n");
}


/*---------------------------------------------------------------------------*/
/*
  Create an empty cluster.
*/
  
cluster * initialize_cluster ()
{
  cluster * clust_ptr = NULL;

  clust_ptr = (cluster *) malloc (sizeof(cluster));
  MTEST (clust_ptr);

  clust_ptr->next_cluster = NULL;
  clust_ptr->num_voxels = 0;
  clust_ptr->voxel_ptr = NULL;
  clust_ptr->centroid = NULL;
  clust_ptr->nearest_dist = 0.0;
  clust_ptr->nearest_cluster = NULL;
  return (clust_ptr);
  
}


/*---------------------------------------------------------------------------*/
/*
  Print the contents of one cluster.
*/

void print_cluster (cluster * clust_ptr, char * str, matrix s)
{
  int i;
  vector v, sv;
  vector_initialize (&v);
  vector_initialize (&sv);

  printf ("Cluster %s \n", str);

  if (clust_ptr->voxel_ptr != NULL)
    {
      printf ("# Voxels = %d \n", clust_ptr->num_voxels);

      /*
      printf ("Voxels: ");
      
      print_all_voxels (clust_ptr->voxel_ptr);
      */
    }

  if (clust_ptr->centroid != NULL)
    {
      printf ("Centroid: \n");
      array_to_vector (SC_dimension, clust_ptr->centroid, &v);
      vector_multiply (s, v, &sv);
      vector_print (sv);
    }

  /*
  printf ("Nearest cluster distance = %f \n", clust_ptr->nearest_dist);
  */

  vector_destroy (&v);
  vector_destroy (&sv);
}


/*---------------------------------------------------------------------------*/
/*
  Print the contents of all clusters in the linked list.
*/

void print_all_clusters (cluster * clust_ptr, matrix s)
{
  int iclust = 0;
  char str[30];

  while (clust_ptr != NULL)
    {
      iclust++;
      sprintf (str, "#%d", iclust);
      print_cluster (clust_ptr, str, s);
      clust_ptr = clust_ptr->next_cluster;
    }

}


/*---------------------------------------------------------------------------*/
/*
  Save voxel contents of this cluster into byte array (sub-brick).
*/

void save_cluster (cluster * clust_ptr, byte iclust, byte * bar)
{
  int i;
  voxel * voxel_ptr = NULL;


  voxel_ptr = clust_ptr->voxel_ptr;


  while (voxel_ptr != NULL)
    {
      bar[voxel_ptr->index] = iclust;
      voxel_ptr = voxel_ptr->next_voxel;
    }

}


/*---------------------------------------------------------------------------*/
/*
  Save voxel contents of all clusters into byte array (sub-brick).
*/

void save_all_clusters (cluster * clust_ptr, byte * bar)
{
  byte iclust = 0;

  while (clust_ptr != NULL)
    {
      iclust++;
      save_cluster (clust_ptr, iclust, bar);
      clust_ptr = clust_ptr->next_cluster;
    }

}


/*---------------------------------------------------------------------------*/
/*
  Calculate the distance between two clusters as the Euclidean distance
  between their centroids.
*/

float cluster_distance (cluster * aclust, cluster * bclust)
{
  float sumsqr;
  float delta;
  int i;

  sumsqr = 0.0;
  for (i = 0;  i < SC_dimension;  i++)
    {
      delta = aclust->centroid[i] - bclust->centroid[i];
      sumsqr += delta * delta;
    }

  return (sqrt(sumsqr));
  
}


/*---------------------------------------------------------------------------*/
/*
  Find the cluster which is nearest to new_cluster.
  Set the nearest_dist and nearest_cluster structure elements accordingly.
*/
  
void find_nearest_cluster (cluster * new_cluster, cluster * head_cluster)
{
  const float MAX_DIST = 1.0e+30;

  cluster * clust_ptr = NULL;
  float dist;


  /*----- Initialize nearest cluster elements -----*/
  new_cluster->nearest_dist = MAX_DIST;
  new_cluster->nearest_cluster = NULL;


  clust_ptr = head_cluster;

  while (clust_ptr != NULL)
    {
      if (clust_ptr != new_cluster)
	{
	  dist = cluster_distance (new_cluster, clust_ptr);

	  if (dist < new_cluster->nearest_dist)
	    {
	      new_cluster->nearest_dist = dist;
	      new_cluster->nearest_cluster = clust_ptr;
	    }

	  if (dist < clust_ptr->nearest_dist)
	    {
	      clust_ptr->nearest_dist = dist;
	      clust_ptr->nearest_cluster = new_cluster;
	    }
	}

      clust_ptr = clust_ptr->next_cluster;
    }
}


/*---------------------------------------------------------------------------*/
/*
  Add a new cluster to the linked list of clusters.
*/

void add_cluster (cluster * new_cluster, cluster * head_cluster)
{

  new_cluster->next_cluster = head_cluster;

  find_nearest_cluster (new_cluster, head_cluster);

}


/*---------------------------------------------------------------------------*/
/*
  Create a new cluster containing a single voxel, and add to list of clusters.
*/

cluster * new_cluster (int index, float * centroid, cluster * head_clust)
{
  cluster * clust_ptr = NULL;
  voxel * voxel_ptr = NULL;

  clust_ptr = initialize_cluster ();

  clust_ptr->num_voxels = 1;
  clust_ptr->voxel_ptr = new_voxel(index);
  clust_ptr->centroid = centroid;

  add_cluster (clust_ptr, head_clust);

  return (clust_ptr);
  
}


/*---------------------------------------------------------------------------*/
/*
  Deallocate memory for this cluster (excluding list of voxels).
*/

void delete_cluster (cluster * clust_ptr)
{
  if (clust_ptr != NULL)
    {
      clust_ptr->voxel_ptr = NULL;

      if (clust_ptr->centroid != NULL)
	{
	  free (clust_ptr->centroid);
	  clust_ptr->centroid = NULL;
	}

      free (clust_ptr);
    }
}


/*---------------------------------------------------------------------------*/
/*
  Deallocate memory for this cluster.

  Note:  This routine does not actually delete the list of voxels.
*/

void destroy_cluster (cluster * clust_ptr)
{
  if (clust_ptr != NULL)
    {
      if (clust_ptr->voxel_ptr != NULL)
	free (clust_ptr->voxel_ptr);
  
      delete_cluster (clust_ptr);
    }
}


/*---------------------------------------------------------------------------*/
/*
  Remove two clusters from linked list of clusters.
  Reset cluster pointers, and recalculate nearest cluster distances 
  where needed.  Finally, delete the two clusters from memory.
*/

cluster * remove_clusters (cluster * aclust, cluster * bclust, 
			   cluster * head_clust)
{
  cluster * clust_ptr = NULL;
  cluster * next_clust = NULL;
  

  while ((head_clust != NULL) && 
	 ((head_clust == aclust) || (head_clust == bclust)))
    head_clust = head_clust->next_cluster;


  if (head_clust != NULL)
    {

      clust_ptr = head_clust;
      next_clust = clust_ptr->next_cluster;
      while (next_clust != NULL)
	{
	  if ((next_clust == aclust) || (next_clust == bclust))
	    clust_ptr->next_cluster = next_clust->next_cluster;
	  else
	    clust_ptr = next_clust;
	  
	  next_clust = clust_ptr->next_cluster;
	}


      clust_ptr = head_clust;
      while (clust_ptr != NULL)
	{
	  if ((clust_ptr->nearest_cluster == aclust) 
	      || (clust_ptr->nearest_cluster == bclust))
	    {
	      find_nearest_cluster (clust_ptr, head_clust);
	    }
	  clust_ptr = clust_ptr->next_cluster;
	}
    }


  delete_cluster (aclust);
  delete_cluster (bclust);

  return (head_clust);
}


/*---------------------------------------------------------------------------*/
/*
  Merge two clusters into one new cluster.
*/

cluster * merge_clusters (cluster * aclust, cluster * bclust)
{
  cluster * abclust = NULL;
  voxel * voxel_ptr = NULL;
  int na, nb;
  int i;

  abclust = initialize_cluster ();

  na = aclust->num_voxels;
  nb = bclust->num_voxels;
  abclust->num_voxels = na + nb;

  abclust->centroid = (float *) malloc (sizeof(float) * SC_dimension);
  MTEST (abclust->centroid);

  for (i = 0;  i < SC_dimension;  i++)
    abclust->centroid[i] 
      = (na*aclust->centroid[i] + nb*bclust->centroid[i]) / (na+nb);

  abclust->voxel_ptr = aclust->voxel_ptr;

  voxel_ptr = abclust->voxel_ptr;
  while (voxel_ptr->next_voxel != NULL)
    voxel_ptr = voxel_ptr->next_voxel;
  voxel_ptr->next_voxel = bclust->voxel_ptr;
  
  return (abclust);
}


/*---------------------------------------------------------------------------*/
/*
  Consolidate two clusters.
*/
  
cluster * consolidate_clusters (cluster * aclust, cluster * bclust, 
				cluster * head_clust)
{
  cluster * abclust = NULL;


  /*----- Merge two clusters into one new cluster -----*/
  abclust = merge_clusters (aclust, bclust);


  /*----- Remove the two original clusters from the linked list -----*/
  head_clust = remove_clusters (aclust, bclust, head_clust);


  /*----- Add the merged cluster to the linked list -----*/
  add_cluster (abclust, head_clust);
  

  /*----- Merged cluster is now at the top of the list -----*/
  return (abclust);
}


/*---------------------------------------------------------------------------*/
/*
  Agglomerate clusters by merging the two clusters which are closest together.
*/

cluster * agglomerate_clusters (cluster * head_clust, int print_flag)
{
  const float MAX_DIST = 1.0e+30;

  cluster * clust_ptr = NULL;
  cluster * aclust    = NULL;
  cluster * bclust    = NULL;
  float min_dist;


  /*----- Find the two clusters which are closest together -----*/
  min_dist = MAX_DIST;
  clust_ptr = head_clust;
  while (clust_ptr != NULL)
    {
      if (clust_ptr->nearest_dist < min_dist)
	{
	  min_dist = clust_ptr->nearest_dist;
	  aclust = clust_ptr;
	  bclust = clust_ptr->nearest_cluster;
	} 
      clust_ptr = clust_ptr->next_cluster;
    }


  /*----- Identify clusters which are to be merged -----*/
  if (print_flag)
    {
      int iclust, iaclust=0, ibclust=0;

      clust_ptr = head_clust;
      iclust = 0;
      while (clust_ptr != NULL)
	{
	  iclust++;
	  if (aclust == clust_ptr)  iaclust = iclust;
	  if (bclust == clust_ptr)  ibclust = iclust;
	  clust_ptr = clust_ptr->next_cluster;
	}
      
      printf ("Merging cluster #%d and cluster #%d \n", iaclust, ibclust);
      printf ("Distance = %f \n", min_dist);
    }


  /*----- Merge these two clusters -----*/
  head_clust = consolidate_clusters (aclust, bclust, head_clust);


  return (head_clust);
}


/*---------------------------------------------------------------------------*/
/*
  Sort clusters in order of size.
*/

cluster * sort_clusters (cluster * head_clust)
{
  cluster * i  = NULL; 
  cluster * ip = NULL; 
  cluster * m  = NULL;
  cluster * mp = NULL;
  cluster * j  = NULL;
  cluster * jp = NULL;
  cluster * guard = NULL;


  /*----- Create guard cluster in case head cluster must be replaced -----*/
  guard = initialize_cluster();
  guard->next_cluster = head_clust;
  ip = guard;

  while (ip->next_cluster != NULL)
    {
      /*----- Initialize search for next largest cluster -----*/
      i = ip->next_cluster;  /* current top of list */
      mp = ip;               /* cluster pointing to next largest cluster */
      m = i;                 /* next largest cluster */
      jp = i;

      /*----- Search through list for next largest cluster -----*/
      while (jp->next_cluster != NULL)
	{
	  j = jp->next_cluster;
	  if (j->num_voxels > m->num_voxels)
	    {
	      mp = jp;
	      m = j;
	    }
	  jp = j;
	}

      /*----- Now move next largest cluster to top of list -----*/
      if (m != i)
	{
	  ip->next_cluster = m;
	  mp->next_cluster = m->next_cluster;
	  m->next_cluster = i;
	  i = m;
	}

      /*----- Move down the list -----*/
      ip = i;
	
    }

  
  /*----- Replace head cluster -----*/
  head_clust = guard->next_cluster;
  delete_cluster (guard);

  return (head_clust);
}


/*---------------------------------------------------------------------------*/
/*
  Calculate covariance matrix for input parameters.
  Return square root of covariance matrix, and its inverse.
*/

void calc_covariance 
(
  matrix * s,                /* square root of covariance matrix */
  matrix * sinv              /* inverse of square root of covariance matrix */
)

{
  int ivox;                  /* voxel indices */
  int ip, jp;                /* parameter indices */
  int ok;                    /* Boolean for successful matrix calc. */

  vector mean;               /* mean parameter vector */ 
  matrix covar;              /* variance-covariance matrix */
  matrix cinv;               /* inverse of covariance matrix */

  char message[80];          /* error message */


  /*----- Initialize vectors and matrices -----*/
  vector_initialize (&mean);
  matrix_initialize (&covar);
  matrix_initialize (&cinv);


  /*----- Allocate space for mean and covariance matrices -----*/
  vector_create (SC_dimension, &mean);
  matrix_create (SC_dimension, SC_dimension, &covar);


  /*----- Calculate parameter sums and products  -----*/
  for (ivox = 0;  ivox < SC_nvox;  ivox++)
    for (ip = 0;  ip < SC_dimension;  ip++)
      {
	mean.elts[ip] += SC_parameters[ip][ivox];
	for (jp = 0;  jp < SC_dimension;  jp++)
	  if ((ip == jp) || (SC_statdist == 2))
	    covar.elts[ip][jp] += 
	      SC_parameters[ip][ivox] * SC_parameters[jp][ivox];
      }


  /*----- Calculate the mean parameter vector -----*/
  for (ip = 0;  ip < SC_dimension;  ip++)
    mean.elts[ip] = mean.elts[ip] / SC_nvox;
  if (SC_verb)  
    vector_sprint ("Mean parameter vector: ", mean);
      

  /*----- Calculate the covariance matrix -----*/
  for (ip = 0;  ip < SC_dimension;  ip++)
    for (jp = 0;  jp < SC_dimension;  jp++)
      if ((ip == jp) || (SC_statdist == 2)) 
	covar.elts[ip][jp] = (covar.elts[ip][jp] 
			      - SC_nvox * mean.elts[ip] * mean.elts[jp])
	  / (SC_nvox - 1);
  if (SC_verb)
    if (SC_statdist == 1)
      matrix_sprint ("Parameter variance (diagonal) matrix: ", covar);
    else
      matrix_sprint ("Parameter covariance matrix: ", covar);

  /*----- Note:  The following sequence of calculations is necessary 
    in order to generate an error message in the event of 
    perfectly correlated input parameters -----*/

  /*----- Calculate inverse of covariance matrix -----*/
  ok = matrix_inverse (covar, &cinv);
  if (! ok)  
    SC_error 
      ("Unable to calculate inverse of covariance matrix");
  
  /*----- Calculate square root of inverse covariance matrix -----*/
  ok = matrix_sqrt (cinv, sinv);
  if (! ok)  
    SC_error 
      ("Unable to calculate square root of inverse of covariance matrix");
  
  /*----- Calculate square root of covariance matrix -----*/
  ok = matrix_inverse (*sinv, s);
  if (! ok)  
    SC_error 
      ("Unable to calculate square root of covariance matrix");
  

  /*----- Deallocate memory -----*/
  vector_destroy (&mean);
  matrix_destroy (&covar);
  matrix_destroy (&cinv);

  
}


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







