/*****************************************************************************
   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.
******************************************************************************/

#include "mrilib.h"

/*----------------------------------------------------------------
   find clusters of points !=0; return an array of clusters
   [the input array fim is destroyed in this
    computation; it may be recreated by MCW_cluster_to_vol]

   Nov 1995: fim array may now be short, byte, or float,
             signified by new ftype argument.

   Coordinates of voxels in clusters are now stored as 3 separate
   short integers, to correct error due to ambiguity in
   identification of clusters.
   BDW  06 March 1997
------------------------------------------------------------------*/

MCW_cluster_array * MCW_find_clusters(
                       int nx, int ny, int nz,
                       float dx, float dy, float dz,
                       int ftype , void * fim ,
                       float max_dist )
{
   MCW_cluster_array * clust_arr ;
   MCW_cluster       * clust , * mask ;
   int ii,jj,kk ,  nxy,nxyz , ijk=0 , ijk_last , mnum ;
   int icl , jma , ijkcl , ijkma , did_one ;
   float fimv=0.0 ;
   short * sfar=NULL ;
   float * ffar=NULL ;
   byte  * bfar=NULL ;
   short ic, jc, kc;
   short im, jm, km;

ENTRY("MCW_find_clusters") ;

   if( fim == NULL ) RETURN(NULL) ;

   switch( ftype ){
      default: RETURN(NULL) ;
      case MRI_short:  sfar = (short *) fim ; break ;
      case MRI_byte :  bfar = (byte  *) fim ; break ;
      case MRI_float:  ffar = (float *) fim ; break ;
   }

   /*--- make a cluster that is a mask of points closer than max_dist ---*/

   mask = MCW_build_mask (dx, dy, dz, max_dist);
   if (mask == NULL)
   {
      fprintf (stderr, "Unable to build mask in MCW_find_clusters");
      RETURN(NULL) ;
   }

   nxy = nx*ny ; nxyz = nxy * nz ;

   mnum = mask->num_pt ;

   /*--- scan through array, find next nonzero point, build a cluster, ... ---*/

   INIT_CLARR(clust_arr) ;

   ijk_last = 0 ;  /* starting point for scan */
   do {
      switch( ftype ){
         case MRI_short:
            for( ijk=ijk_last ; ijk < nxyz ; ijk++ ) if( sfar[ijk] != 0 ) break ;
            if( ijk < nxyz ){
               fimv = sfar[ijk] ; sfar[ijk] = 0 ;  /* save found point */
            }
         break ;

         case MRI_byte:
            for( ijk=ijk_last ; ijk < nxyz ; ijk++ ) if( bfar[ijk] != 0 ) break ;
            if( ijk < nxyz ){
               fimv = bfar[ijk] ; bfar[ijk] = 0 ;  /* save found point */
            }
         break ;

         case MRI_float:
            for( ijk=ijk_last ; ijk < nxyz ; ijk++ ) if( ffar[ijk] != 0.0 ) break ;
            if( ijk < nxyz ){
               fimv = ffar[ijk] ; ffar[ijk] = 0.0 ;  /* save found point */
            }
         break ;
      }
      if( ijk == nxyz ) break ;  /* didn't find any nonzero points! */

#ifdef CLUST_DEBUG
printf("  starting cluster at ijk=%d\n",ijk) ;
#endif

      ijk_last = ijk+1 ;                    /* start scanning here next time */

      INIT_CLUSTER(clust) ;                    /* make a new (empty) cluster */
      IJK_TO_THREE(ijk,ic,jc,kc,nx,nxy) ;
      ADDTO_CLUSTER( clust , ic, jc, kc, fimv ) ;           /* start it off! */

      /*--
        for each point in cluster:
           check points offset by the mask for nonzero entries in fim
           enter those into cluster
           continue until end of cluster is reached
             (note that cluster is expanding as we progress)
      --*/

      switch( ftype ){
         case MRI_short:  /* clust->num_pt increases with each ADDTO_CLUSTER */
            for( icl=0 ; icl < clust->num_pt ; icl++ ){
             ic = clust->i[icl];                  /* check around this point */
             jc = clust->j[icl];
             kc = clust->k[icl];

             for( jma=0 ; jma < mnum ; jma++ ){             /* scanning mask */
                  im = ic + mask->i[jma];      /* offsets from (ic,jc,kc) pt */
                  jm = jc + mask->j[jma];
                  km = kc + mask->k[jma];
                  if( im < 0 || im >= nx ||        /* outside the 3D volume? */
                      jm < 0 || jm >= ny || km < 0 || km >= nz ) continue ;

                  ijkma = THREE_TO_IJK (im, jm, km, nx, nxy);    /* 3D index */

                  /* is ijkma:  a previous point?
                                outside the box?
                                not "active"?    -- if any of these, skip it */

                  if( ijkma < ijk_last || ijkma >= nxyz || sfar[ijkma] == 0 ) continue ;

                  ADDTO_CLUSTER( clust , im, jm, km, sfar[ijkma] ) ; /* new! */
                  sfar[ijkma] = 0 ;                    /* mark it as used up */
               }
            }
         break ;

         case MRI_byte:
            for( icl=0 ; icl < clust->num_pt ; icl++ ){
              ic = clust->i[icl];
              jc = clust->j[icl];
              kc = clust->k[icl];

               for( jma=0 ; jma < mnum ; jma++ ){
                 im = ic + mask->i[jma];
                 jm = jc + mask->j[jma];
                 km = kc + mask->k[jma];
                  if( im < 0 || im >= nx ||
                      jm < 0 || jm >= ny || km < 0 || km >= nz ) continue ;

                  ijkma = THREE_TO_IJK (im, jm, km, nx, nxy);
                  if( ijkma < ijk_last || ijkma >= nxyz || bfar[ijkma] == 0 ) continue ;

                  ADDTO_CLUSTER( clust , im, jm, km, bfar[ijkma] ) ;
                  bfar[ijkma] = 0 ;
               }
            }
         break ;

         case MRI_float:
            for( icl=0 ; icl < clust->num_pt ; icl++ ){
              ic = clust->i[icl];
              jc = clust->j[icl];
              kc = clust->k[icl];

               for( jma=0 ; jma < mnum ; jma++ ){
                 im = ic + mask->i[jma];
                 jm = jc + mask->j[jma];
                 km = kc + mask->k[jma];
                 if( im < 0 || im >= nx ||
                     jm < 0 || jm >= ny || km < 0 || km >= nz ) continue ;

                 ijkma = THREE_TO_IJK (im, jm, km, nx, nxy);
                  if( ijkma < ijk_last || ijkma >= nxyz || ffar[ijkma] == 0.0 ) continue ;

                 ADDTO_CLUSTER( clust , im, jm, km, ffar[ijkma] ) ;
                  ffar[ijkma] = 0.0 ;
               }
            }
         break ;
      }

      /* Cluster 'clust' is complete, add it to the cluster array */

      ADDTO_CLARR(clust_arr,clust) ;
   } while( 1 ) ;

   /* the mask is now obsolete */

   KILL_CLUSTER(mask) ;

   /* Didn't find anything at all? Weird. */

   if( clust_arr->num_clu <= 0 ){ DESTROY_CLARR(clust_arr) ; }

   RETURN(clust_arr) ;
}

/*---------------------------------------------------------------
  Write the points stored in a cluster back into a volume

   Coordinates of voxels in clusters are now stored as 3 separate
   short integers, to correct error due to ambiguity in
   identification of clusters.
   BDW  06 March 1997
-----------------------------------------------------------------*/

void MCW_cluster_to_vol( int nx , int ny , int nz ,
                         int ftype , void *fim , MCW_cluster *clust )
{
   int icl, ijk ;
   int nxy ;
   short *sfar ;
   float *ffar ;
   byte  *bfar ;

ENTRY("MCW_cluster_to_vol") ;

   if( fim == NULL || clust == NULL ) EXRETURN ;

   nxy = nx * ny;

   switch( ftype ){
      case MRI_short:
         sfar = (short *) fim ;
         for( icl=0 ; icl < clust->num_pt ; icl++ )
         {
          ijk = THREE_TO_IJK (clust->i[icl], clust->j[icl], clust->k[icl],
                              nx, nxy);
          sfar[ijk] = clust->mag[icl] ;
         }
      EXRETURN ;

      case MRI_byte:
         bfar = (byte *) fim ;
         for( icl=0 ; icl < clust->num_pt ; icl++ )
         {
            ijk = THREE_TO_IJK (clust->i[icl], clust->j[icl], clust->k[icl],
                                nx, nxy);
            bfar[ijk] = clust->mag[icl] ;
         }
      EXRETURN ;

      case MRI_float:
         ffar = (float *) fim ;
         for( icl=0 ; icl < clust->num_pt ; icl++ )
         {
          ijk = THREE_TO_IJK (clust->i[icl], clust->j[icl], clust->k[icl],
                              nx, nxy);
          ffar[ijk] = clust->mag[icl] ;
         }
      EXRETURN ;
   }

   EXRETURN ;  /* should not be reached */
}

/*-------------------------------------------------------------------*/
/* Put the values from the dataset at the cluster locations into
   the 'mag' component of the cluster, and zero out the dataset
   at those location.  Can use MCW_cluster_to_vol() to restore later.
   This pair of functions is used in Clusterize for Flash-ing.
---------------------------------------------------------------------*/

void MCW_vol_to_cluster( int nx , int ny , int nz ,
                         int ftype , void *fim , MCW_cluster *clust )
{
   int icl, ijk ;
   int nxy ;
   short *sfar ;
   float *ffar ;
   byte  *bfar ;

ENTRY("MCW_vol_to_cluster") ;

   if( fim == NULL || clust == NULL ) EXRETURN ;

   nxy = nx * ny;

   switch( ftype ){
      case MRI_short:
         sfar = (short *) fim ;
         for( icl=0 ; icl < clust->num_pt ; icl++ )
         {
          ijk = THREE_TO_IJK (clust->i[icl], clust->j[icl], clust->k[icl],
                              nx, nxy);
          clust->mag[icl] = sfar[ijk] ; sfar[ijk] = 0 ;
         }
      EXRETURN ;

      case MRI_byte:
         bfar = (byte *) fim ;
         for( icl=0 ; icl < clust->num_pt ; icl++ )
         {
            ijk = THREE_TO_IJK (clust->i[icl], clust->j[icl], clust->k[icl],
                                nx, nxy);
            clust->mag[icl] = bfar[ijk] ; bfar[ijk] = 0 ;
         }
      EXRETURN ;

      case MRI_float:
         ffar = (float *) fim ;
         for( icl=0 ; icl < clust->num_pt ; icl++ )
         {
          ijk = THREE_TO_IJK (clust->i[icl], clust->j[icl], clust->k[icl],
                              nx, nxy);
          clust->mag[icl] = ffar[ijk] ; ffar[ijk] = 0.0f ;
         }
      EXRETURN ;
   }

   EXRETURN ;  /* should not be reached */
}


/*----------------------------------------------------------------------------
  Erosion and dilation of 3d clusters.

  Purpose:  Sometimes a cluster consists of several main bodies connected by
            a narrow path.  The objective is to sever the connecting path,
            while keeping the main bodies intact, thereby producing separate
            clusters.

  Method:   Erosion is applied to eliminate the outer layer of voxels.  This
            should cut the connecting path.  The original main bodies are
            then restored by dilating the result.

  Author:   B. Douglas Ward

  Date:     16 June 1998


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

void MCW_erode_clusters
(
  int nx, int ny, int nz,           /* dimensions of volume fim */
  float dx, float dy, float dz,     /* voxel dimensions */
  int ftype,                        /* data type */
  void * fim,                       /* volume data */
  float max_dist,                   /* voxel connectivity radius */
  float pv,                         /* fraction pv of voxels within max_dist must
                                       be in the cluster */
  int dilate                        /* boolean for perform dilation phase */
)

{
  MCW_cluster * mask = NULL;        /* mask determines nbhd membership */
  int minimum;                      /* minimum number of voxels in nbhd */
  int count;                        /* count of voxels in neighborhood */
  int nxy, nxyz;                    /* numbers of voxels */
  int ijk, iv, jv, kv;              /* voxel indices */
  int ijkm, im, jm, km;             /* voxel indices */
  int imask, nmask;                 /* mask indices */
  short * sfar=NULL;                /* pointer to short data */
  byte  * bfar=NULL;                /* pointer to byte data */
  float * ffar=NULL;                /* pointer to float data */
  float * efim = NULL;              /* copy of eroded voxels */

ENTRY("MCW_erode_clusters") ;


  /*----- Just in case -----*/
  if ( fim == NULL )  EXRETURN;


  /*----- Initialize local variables -----*/
  nxy = nx * ny;   nxyz = nxy * nz;


  /*----- Set pointer to input data -----*/
  switch (ftype)
    {
    default:  EXRETURN;
    case MRI_short:  sfar = (short *) fim;  break;
    case MRI_byte :  bfar = (byte  *) fim;  break;
    case MRI_float:  ffar = (float *) fim;  break;
    }


  /*----- Initialization for copy of eroded voxels -----*/
  efim = (float *) malloc (sizeof(float) * nxyz);
  if (efim == NULL)
    {
      fprintf (stderr, "Unable to allocate memory in MCW_erode_clusters");
      EXRETURN;
    }
  for (ijk = 0;  ijk < nxyz;  ijk++)
    efim[ijk] = 0.0;


  /*--- Make a cluster that is a mask of points closer than max_dist ---*/
  mask = MCW_build_mask (dx, dy, dz, max_dist);
  if (mask == NULL)
    {
      fprintf (stderr, "Unable to build mask in MCW_erode_clusters");
      EXRETURN;
    }


  /*----- Calculate minimum number of voxels in nbhd. for non-erosion -----*/
  nmask = mask->num_pt ;
  minimum = floor(pv*nmask + 0.99);
  if (minimum <= 0)  EXRETURN;     /*----- Nothing will be eroded -----*/


  /*----- Step 1:  Identify voxels to be eroded -----*/
  switch (ftype)
    {
    case MRI_short:
      for (ijk = 0;  ijk < nxyz;  ijk++)
	{	
	  if (sfar[ijk] == 0)  continue;
	  IJK_TO_THREE (ijk, iv, jv, kv, nx, nxy);

	  /*----- Count number of active voxels in the neighborhood -----*/
	  count = 0;
	  for (imask = 0;  imask < nmask;  imask++)
	    {
	      im = iv + mask->i[imask];
	      jm = jv + mask->j[imask];
	      km = kv + mask->k[imask];
	      if ( im < 0 || jm < 0 || km < 0 ||
		   im >= nx || jm >= ny || km >= nz )  continue;
	      ijkm = THREE_TO_IJK (im, jm, km, nx, nxy);
	      if (sfar[ijkm] != 0)   count++;
	    }

	  /*----- Record voxel to be eroded -----*/
	  if (count < minimum)  efim[ijk] = (float) sfar[ijk];
	}
      break;

    case MRI_byte:
      for (ijk = 0;  ijk < nxyz;  ijk++)
	{	
	  if (bfar[ijk] == 0)  continue;
	  IJK_TO_THREE (ijk, iv, jv, kv, nx, nxy);

	  /*----- Count number of active voxels in the neighborhood -----*/
	  count = 0;
	  for (imask = 0;  imask < nmask;  imask++)
	    {
	      im = iv + mask->i[imask];
	      jm = jv + mask->j[imask];
	      km = kv + mask->k[imask];
	      if ( im < 0 || jm < 0 || km < 0 ||
		   im >= nx || jm >= ny || km >= nz )  continue;
	      ijkm = THREE_TO_IJK (im, jm, km, nx, nxy);
	      if (bfar[ijkm] != 0)   count++;
	    }

	  /*----- Record voxel to be eroded -----*/
	  if (count < minimum)  efim[ijk] = (float) bfar[ijk];
	}
      break;

    case MRI_float:
      for (ijk = 0;  ijk < nxyz;  ijk++)
	{	
	  if (ffar[ijk] == 0.0)  continue;
	  IJK_TO_THREE (ijk, iv, jv, kv, nx, nxy);

	  /*----- Count number of active voxels in the neighborhood -----*/
	  count = 0;
	  for (imask = 0;  imask < nmask;  imask++)
	    {
	      im = iv + mask->i[imask];
	      jm = jv + mask->j[imask];
	      km = kv + mask->k[imask];
	      if ( im < 0 || jm < 0 || km < 0 ||
		   im >= nx || jm >= ny || km >= nz )  continue;
	      ijkm = THREE_TO_IJK (im, jm, km, nx, nxy);
	      if (ffar[ijkm] != 0.0)   count++;
	    }

	  /*----- Record voxel to be eroded -----*/
	  if (count < minimum)  efim[ijk] = ffar[ijk];
	}
      break;

    }


  /*----- Step 2:  Erode voxels -----*/
  switch (ftype)
    {
    case MRI_short:
      for (ijk = 0;  ijk < nxyz;  ijk++)
	if (efim[ijk] != 0.0)  sfar[ijk] = 0;
      break;

    case MRI_byte:
      for (ijk = 0;  ijk < nxyz;  ijk++)
	if (efim[ijk] != 0.0)  bfar[ijk] = 0;
      break;

    case MRI_float:
      for (ijk = 0;  ijk < nxyz;  ijk++)
	if (efim[ijk] != 0.0)  ffar[ijk] = 0.0;
      break;
    }


  /*----- Proceed with dilation phase? -----*/
  if (dilate)
    {


      /*----- Step 3:  Identify voxels to be restored -----*/
      switch (ftype)
	{
	case MRI_short:
	  for (ijk = 0;  ijk < nxyz;  ijk++)
	    {	
	      if (efim[ijk] == 0.0)  continue;
	      IJK_TO_THREE (ijk, iv, jv, kv, nx, nxy);
	
	      /*---- Determine if any active voxels in the neighborhood ----*/
	      for (imask = 0;  imask < nmask;  imask++)
		{
		  im = iv + mask->i[imask];
		  jm = jv + mask->j[imask];
		  km = kv + mask->k[imask];
		  if ( im <  0  || jm <  0  || km <  0 ||
		       im >= nx || jm >= ny || km >= nz  )  continue;
		  ijkm = THREE_TO_IJK (im, jm, km, nx, nxy);
		  if (sfar[ijkm] != 0)  break;
		}
	
	      /*----- Reset voxel not to be restored -----*/
	      if (imask == nmask)  efim[ijk] = 0.0;
	    }
	  break;
	
	case MRI_byte:
	  for (ijk = 0;  ijk < nxyz;  ijk++)
	    {	
	      if (efim[ijk] == 0.0)  continue;
	      IJK_TO_THREE (ijk, iv, jv, kv, nx, nxy);
	
	      /*---- Determine if any active voxels in the neighborhood ----*/
	      for (imask = 0;  imask < nmask;  imask++)
		{
		  im = iv + mask->i[imask];
		  jm = jv + mask->j[imask];
		  km = kv + mask->k[imask];
		  if ( im < 0 || jm < 0 || km < 0 ||
		       im >= nx || jm >= ny || km >= nz )  continue;
		  ijkm = THREE_TO_IJK (im, jm, km, nx, nxy);
		  if (bfar[ijkm] != 0)  break;
		}
	
	      /*----- Reset voxel not to be restored -----*/
	      if (imask == nmask)  efim[ijk] = 0.0;
	    }
	  break;
	
	case MRI_float:
	  for (ijk = 0;  ijk < nxyz;  ijk++)
	    {	
	      if (efim[ijk] == 0.0)  continue;
	      IJK_TO_THREE (ijk, iv, jv, kv, nx, nxy);
	
	      /*---- Determine if any active voxels in the neighborhood ----*/
	      for (imask = 0;  imask < nmask;  imask++)
		{
		  im = iv + mask->i[imask];
		  jm = jv + mask->j[imask];
		  km = kv + mask->k[imask];
		  if ( im < 0 || jm < 0 || km < 0 ||
		       im >= nx || jm >= ny || km >= nz )  continue;
		  ijkm = THREE_TO_IJK (im, jm, km, nx, nxy);
		  if (ffar[ijkm] != 0.0)  break;
		}
	
	      /*----- Reset voxel not to be restored -----*/
	      if (imask == nmask)  efim[ijk] = 0.0;
	    }
	  break;
	}


      /*----- Step 4:  Restore voxels -----*/
      switch (ftype)
	{
	case MRI_short:
	  for (ijk = 0;  ijk < nxyz;  ijk++)
	    if (efim[ijk] != 0.0)  sfar[ijk] = (short) efim[ijk];
	    break;
	
	case MRI_byte:
	  for (ijk = 0;  ijk < nxyz;  ijk++)
	    if (efim[ijk] != 0.0)  bfar[ijk] = (byte) efim[ijk];
	    break;

	case MRI_float:
	  for (ijk = 0;  ijk < nxyz;  ijk++)
	    if (efim[ijk] != 0.0)  ffar[ijk] = efim[ijk];
	  break;
	}

    }   /*  if (dilate)  */


  /*----- Release memory -----*/
  KILL_CLUSTER(mask) ;
  free (efim);   efim = NULL;
  EXRETURN ;
}
