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

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

int THD_is_zero( int npt , float *xx )  /* 20 May 2011 */
{
   int ii ;

   if( npt <= 0 || xx == NULL ) return 1 ;
   
   for( ii=0 ; ii < npt ; ii++ ) if( xx[ii] != 0.0f ) return 0 ;
   return 1 ;
}

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

int THD_is_constant( int npt , float *xx )  /* 20 May 2011 */
{
   int ii ; float val ;

   if( npt <= 1 || xx == NULL ) return 1 ;
   
   val = xx[0] ;
   for( ii=1 ; ii < npt ; ii++ ) if( xx[ii] != val ) return 0 ;
   return 1 ;
}

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

void THD_const_detrend( int npt, float *xx, float *xx0 ) /* 24 Aug 2001 */
{
   int ii ; float xbar ;

   if( npt < 2 || xx == NULL ) return ;

   xbar = 0.0 ;
   for( ii=0 ; ii < npt ; ii++ ) xbar += xx[ii] ;
   xbar /= npt ;
   for( ii=0 ; ii < npt ; ii++ ) xx[ii] -= xbar ;
   if( xx0 != NULL ) *xx0 = xbar ;
   return ;
}

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

void get_linear_trend( int npt, float *xx, float *f0, float *f1 )
{
   double t1,t3,t10 , x0,x1 ;
   int ii ;

   if( npt < 2 || xx == NULL || f0 == NULL || f1 == NULL ) return ;

   x0 = xx[0] ; x1 = 0.0 ;
   for( ii=1 ; ii < npt ; ii++ ){
      x0 += xx[ii] ;
      x1 += xx[ii] * ii ;
   }

   t1 = npt*x0; t3 = 1.0/npt; t10 = npt*npt;

   *f0 = (float)(2.0/(npt+1.0)*t3*(2.0*t1-3.0*x1-x0));
   *f1 = (float)(-6.0/(t10-1.0)*t3*(-x0-2.0*x1+t1));
   return ;
}

/*-------------------------------------------------------------------------
   Linear detrend a 1D float array in place.
   If xx0 != NULL and xx1 != NULL, then the trend removed is
      far[i] -= (*xx0) + (*xx1) * i, for i=0..npt-1
---------------------------------------------------------------------------*/

void THD_linear_detrend( int npt, float *far, float *xx0, float *xx1 )
{
   int ii ;
   float f0=0.0, f1=0.0;

   if( npt < 3 || far == NULL ) return ;

   get_linear_trend( npt , far , &f0 , &f1 ) ;

   far[0] -= f0 ;
   for( ii=1 ; ii < npt ; ii++ ) far[ii] -= (f0 + f1*ii) ;

   if( xx0 != NULL ) *xx0 = f0 ;
   if( xx1 != NULL ) *xx1 = f1 ;

   return ;
}

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

void THD_linear_detrend_complex( int npt , complex *cx )  /* 05 Mar 2007 */
{
   float *f ; int ii ;

   if( npt < 3 || cx == NULL ) return ;

   f = malloc(sizeof(float)*npt) ;
   for( ii=0 ; ii < npt ; ii++ ) f[ii] = cx[ii].r ;
   THD_linear_detrend( npt , f , NULL,NULL ) ;
   for( ii=0 ; ii < npt ; ii++ ){
     cx[ii].r = f[ii] ; f[ii] = cx[ii].i ;
   }
   THD_linear_detrend( npt , f , NULL,NULL ) ;
   for( ii=0 ; ii < npt ; ii++ ) cx[ii].i = f[ii] ;
   return ;
}

/*---------------------------------------------------------------------------
   Given x[0..npt-1], return f0,f1,f2 as the least squares coefficients to
     x[j] = f0 + f1*j + f2*j*j
-----------------------------------------------------------------------------*/

void get_quadratic_trend( int npt, float *xx, float *f0, float *f1, float *f2 )
{
   double x0,x1,x2 ; double N=npt ;
   int ii ;

   if( npt < 3 || xx == NULL || f0 == NULL || f1 == NULL || f2 == NULL ) return;

   x0 = xx[0] ; x1 = x2 = 0.0 ;
   for( ii=1 ; ii < npt ; ii++ ){
     x0 +=  xx[ii] ;
     x1 += (xx[ii] * ii) ;
     x2 += (xx[ii] * ii) * ii ;
   }

   *f0 = (  3.0*(3.0*N*N-3.0*N+2.0) * x0
          -18.0*(-1.0+2.0*N)        * x1
          +30.0                     * x2 ) / (N*(N+2.0)*(N+1.0)) ;

   *f1 = ( -18.0*(-1.0+2.0*N)              * x0
           +12.0*(-1.0+2.0*N)*(8.0*N-11.0) * x1 /((N-1.0)*(N-2.0))
           -180.0                          * x2 /(N-2.0)          )
        / (N*(N+2.0)*(N+1.0)) ;

   *f2 = ( 30.0  * x0
          -180.0 * x1 / (N-2.0)
          +180.0 * x2 / ((N-1.0)*(N-2.0)) ) / (N*(N+2.0)*(N+1.0))  ;
   return ;
}

/*-------------------------------------------------------------------------
   Quadratic detrend a 1D float array in place.
   If xx0 != NULL, xx1 != NULL, xx2 != NULL, then the trend removed is
      far[i] -= (*xx0) + (*xx1) * i + (*xx2)*(i*i) , for i=0..npt-1
---------------------------------------------------------------------------*/

void THD_quadratic_detrend( int npt, float *far,
                            float *xx0, float *xx1, float *xx2 )
{
   int ii ;
   float f0 , f1 , f2 ;

   if( npt < 4 || far == NULL ) return ;

   get_quadratic_trend( npt , far , &f0 , &f1 , &f2 ) ;

   far[0] -= f0 ;
   for( ii=1 ; ii < npt ; ii++ ) far[ii] -= ( (f2*ii + f1)*ii + f0 ) ;

   if( xx0 != NULL ) *xx0 = f0 ;
   if( xx1 != NULL ) *xx1 = f1 ;
   if( xx2 != NULL ) *xx2 = f2 ;

   return ;
}

/*------------------------------------------------------------------------------*/
/*! Cubic detrend a float array in place. */

void THD_cubic_detrend( int npt , float *far )  /* 15 Nov 1999 */
{
   int ii ;
   float g0,g1,g2,g3 , f0,f1,f2,f3 , t1,t2,t5,t8 , t95,t56,t22,t25,txx ;

   if( npt < 5 || far == NULL ) return ;

   t8 = npt*npt ; t2 = npt-1.0 ; t5 = t2*(npt-2.0) ;
   t95 = 0.05*t5*(npt-3.0) ;
   t56 = 0.16666667*t5 ;
   t22 = 0.5*t2 ;
   t25 = 1.5*t2 ;
   txx = 0.6*t8-1.5*npt+1.1 ;

   g0=g1=g2=g3=0.0 ;
   for( ii=0 ; ii < npt ; ii++ ){
      t1 = ii*ii ;
      f1 = ii - t22 ;
      f2 = t1 - t2*ii + t56 ;
      f3 = t1*(ii - t25) + txx*ii - t95 ;

      g0 += far[ii] ;
      g1 += far[ii] * f1 ;
      g2 += far[ii] * f2 ;
      g3 += far[ii] * f3 ;
   }
   g0 *= (1.0/npt) ;
   g1 *= 12.0/(npt*(t8-1.0)) ;
   g2 *= 180.0/(npt*(t8-1.0)*(t8-4.0)) ;
   g3 *= 2800.0/(npt*(t8-1.0)*(t8-4.0)*(t8-9.0)) ;

   for( ii=0 ; ii < npt ; ii++ ){
      t1 = ii*ii ;
      f1 = ii- t22 ;
      f2 = t1 - t2*ii + t56 ;
      f3 = t1*(ii - t25) + txx*ii - t95 ;

      far[ii] -= ( g0 + g1*f1 + g2*f2 + g3*f3 ) ;
   }

   return ;
}

/*-------------------------------------------------------------------------
   Make a vector have L2 norm 1
---------------------------------------------------------------------------*/

float THD_normalize( int npt , float *far )
{
   int ii ;
   float fac ;

   if( npt <= 0 || far == NULL ) return 0.0f ;

   fac = 0.0f ;
   for( ii=0 ; ii < npt ; ii++ ) fac += far[ii]*far[ii] ;
   if( fac <= 1.e-20f ) return 0.0f ;
   fac = 1.0f / sqrtf(fac) ;
   for( ii=0 ; ii < npt ; ii++ ) far[ii] *= fac ;
   return fac ;
}

/*-------------------------------------------------------------------------
   Make a vector have RMS value = 1
---------------------------------------------------------------------------*/

void THD_normRMS( int npt , float *far )
{
   int ii ;
   float fac ;

   if( npt <= 0 || far == NULL ) return ;

   fac = 0.0f ;
   for( ii=0 ; ii < npt ; ii++ ) fac += far[ii]*far[ii] ;
   if( fac == 0.0f ) return ;
   fac = 1.0f / sqrtf(fac/npt) ;
   for( ii=0 ; ii < npt ; ii++ ) far[ii] *= fac ;
   return ;
}

/*-----------------------------------------------------------------------*/
/* Make a vector have max abs 1 [26 Mar 2008]. */

void THD_normmax( int npt , float *far )
{
   int ii ;
   float fac , val ;

   if( npt <= 0 || far == NULL ) return ;
   fac = 0.0f ;
   for( ii=0 ; ii < npt ; ii++ ){ val = fabsf(far[ii]); fac = MAX(fac,val); }
   if( fac == 0.0f ) return ;
   fac = 1.0f / fac ;
   for( ii=0 ; ii < npt ; ii++ ) far[ii] *= fac ;
   return ;
}

/*-----------------------------------------------------------------------*/
/* Make a vector have max sum |far| = 1 [26 Mar 2008]. */

void THD_normL1( int npt , float *far )
{
   int ii ;
   float fac , val ;

   if( npt <= 0 || far == NULL ) return ;
   fac = 0.0f ;
   for( ii=0 ; ii < npt ; ii++ ){ val = fabsf(far[ii]); fac += val; }
   if( fac == 0.0f ) return ;
   fac = 1.0f / fac ;
   for( ii=0 ; ii < npt ; ii++ ) far[ii] *= fac ;
   return ;
}

/*-----------------------------------------------------------------------*/
/*! Detrend a vector with a given polort level, plus some others, using
    least squares regression.
     - npt    = length of vector
     - far    = vector of data (will be modified)
     - polort = polynomial order
     - nort   = number of extra orts in ort[] (can be 0)
     - ort    = array of extra time series to detrend:
                 ort[j][i] for j=0..nort-1, i=0..npt-1
     - fit    = array of length nref=polort+nort+1 to hold parameters of fit
                 (can be NULL)
-------------------------------------------------------------------------*/

void THD_generic_detrend_LSQ( int npt, float *far ,
                              int polort, int nort, float **ort , float *fit )
{
   int ii,jj , nref ;
   float **ref , *qfit , xmid , xfac , val ;

ENTRY("THD_generic_detrend_LSQ") ;

   /* check inputs */

   if( npt <= 1 || far == NULL ){
     ERROR_message("THD_generic_detrend_LSQ :: bad inputs"); EXRETURN;
   }
   if( nort > 0 ){
     if( ort == NULL ){
       ERROR_message("THD_generic_detrend_LSQ :: ort == NULL"); EXRETURN;
     }
     for( jj=0 ; jj < nort ; jj++ ){
       if( ort[jj] == NULL || THD_is_zero(npt,ort[jj]) ){
         ERROR_message("THD_generic_detrend_LSQ :: ort[%d] is invalid",jj) ;
         EXRETURN ;
       }
     }
   }
   if( polort <  0 ) polort = -1 ;
   if( nort   <  0 ) nort   =  0 ;

   nref = polort+1+nort ;
   if( nref <= 0 || nref >= npt-1 ) EXRETURN ;

   /* assemble all reference vectors */

   ref  = (float **) malloc( sizeof(float *) * nref ) ;
   xmid = 0.5*(npt-1) ; xfac = 1.0 / xmid ;
   for( jj=0 ; jj <= polort ; jj++ ){
     ref[jj] = (float *) malloc( sizeof(float) * npt ) ;
     for( ii=0 ; ii < npt ; ii++ )
       ref[jj][ii] = (float)Plegendre(xfac*(ii-xmid),jj) ;
   }
   for( jj=0 ; jj < nort ; jj++ )   /* user supplied refs */
     ref[polort+1+jj] = ort[jj] ;

   qfit = lsqfit( npt , far , NULL , nref , ref ) ;     /* actual fitting */

   if( qfit != NULL ){                                  /* good */
     for( ii=0 ; ii < npt ; ii++ ){
       val = far[ii] ;
       for( jj=0 ; jj < nref ; jj++ ) val -= qfit[jj] * ref[jj][ii] ;
       far[ii] = val ;
     }
     if( fit != NULL ){ for( ii=0 ; ii < nref ; ii++ ) fit[ii] = qfit[ii] ; }
     free(qfit) ;
   } else {
     ERROR_message("THD_generic_detrend_LSQ: fit fails - no detrending!") ;
     if( fit != NULL ) memset(fit,0,sizeof(float)*nref) ;
   }

   for( jj=0 ; jj <= polort ; jj++ ) free(ref[jj]) ;
   free(ref) ; EXRETURN ;
}

/*-----------------------------------------------------------------------*/
/*! Detrend a vector with a given polort level, plus some others, using
    L1 regression.
     - npt    = length of vector
                 (if < 0, then abs(npt) is used, and the fit isn't subtracted)
     - far    = vector of data (will be modified)
     - polort = polynomial order
     - nort   = number of extra orts in ort[] (can be 0)
     - ort    = array of extra time series to detrend:
                 ort[j][i] for j=0..nort-1, i=0..npt-1
     - fit    = array of length nref=polort+nort+1 to hold parameters of fit
                 (can be NULL)
-------------------------------------------------------------------------*/

void THD_generic_detrend_L1( int npt, float *far ,
                             int polort, int nort, float **ort , float *fit )
{
   int ii,jj , nref , nosub=0 ;
   float **ref , *qfit , xmid , xfac , val ;

ENTRY("THD_generic_detrend_L1") ;

   /* check inputs */

   if( npt < -1 ){ nosub = 1 ; npt = -npt ; }  /* 02 Jan 2013 */

   if( npt <= 1 || far == NULL ){
     ERROR_message("THD_generic_detrend_L1 :: bad inputs"); EXRETURN;
   }
   if( nort > 0 ){
     if( ort == NULL ){
       ERROR_message("THD_generic_detrend_LSQ :: ort == NULL"); EXRETURN;
     }
     for( jj=0 ; jj < nort ; jj++ ){
       if( ort[jj] == NULL || THD_is_zero(npt,ort[jj]) ){
         ERROR_message("THD_generic_detrend_L1 :: ort[%d] is invalid",jj) ;
         EXRETURN ;
       }
     }
   }
   if( polort <  0 ) polort = -1 ;
   if( nort   <  0 ) nort   =  0 ;

   nref = polort+1+nort ;
   if( nref <= 0 || nref >= npt-1 ) EXRETURN ;

   /* assemble all reference vectors */

   ref  = (float **) malloc( sizeof(float *) * nref ) ;
   xmid = 0.5*(npt-1) ; xfac = 1.0 / xmid ;
   for( jj=0 ; jj <= polort ; jj++ ){
     ref[jj] = (float *) malloc( sizeof(float) * npt ) ;
     for( ii=0 ; ii < npt ; ii++ )
       ref[jj][ii] = (float)Plegendre(xfac*(ii-xmid),jj) ;
   }
   for( jj=0 ; jj < nort ; jj++ )   /* user supplied refs */
     ref[polort+1+jj] = ort[jj] ;

   qfit = (float *)malloc(sizeof(float)*nref) ;
   val = cl1_solve( npt , nref , far , ref , qfit , 0 ) ;

   if( val >= 0.0f ){                                  /* good */
     if( !nosub ){
       for( ii=0 ; ii < npt ; ii++ ){
         val = far[ii] ;
         for( jj=0 ; jj < nref ; jj++ ) val -= qfit[jj] * ref[jj][ii] ;
         far[ii] = val ;
       }
     }
     if( fit != NULL ){ for( ii=0 ; ii < nref ; ii++ ) fit[ii] = qfit[ii] ; }
   } else {
     ERROR_message("THD_generic_detrend_L1: fit fails - no detrending!") ;
     if( fit != NULL ) memset(fit,0,sizeof(float)*nref) ;
   }
   free(qfit) ;

   for( jj=0 ; jj <= polort ; jj++ ) free(ref[jj]) ;
   free(ref) ; EXRETURN ;
}

/*-----------------------------------------------------------------------*/
/* Add the trend back into far; fit must not be NULL!
-------------------------------------------------------------------------*/

void THD_generic_retrend( int npt , float *far ,
                          int polort, int nort, float **ort , float *fit )
{
   int ii,jj , nref ;
   float **ref , xmid , xfac , val ;

ENTRY("THD_generic_retrend") ;

   /* check inputs */

   if( npt <= 1 || far == NULL ){
     ERROR_message("THD_generic_retrend :: bad inputs"); EXRETURN;
   }
   if( nort > 0 ){
     if( ort == NULL ){
       ERROR_message("THD_generic_detrend_LSQ :: ort == NULL"); EXRETURN;
     }
     for( jj=0 ; jj < nort ; jj++ ){
       if( ort[jj] == NULL || THD_is_zero(npt,ort[jj]) ){
         ERROR_message("THD_generic_retrend :: ort[%d] is invalid",jj) ;
         EXRETURN ;
       }
     }
   }
   if( polort <  0 ) polort = -1 ;
   if( nort   <  0 ) nort   =  0 ;

   nref = polort+1+nort ;
   if( nref == 0 || nref >= npt-1 ) EXRETURN ;

   /* assemble all reference vectors */

   ref  = (float **) malloc( sizeof(float *) * nref ) ;
   xmid = 0.5*(npt-1) ; xfac = 1.0 / xmid ;
   for( jj=0 ; jj <= polort ; jj++ ){
     ref[jj] = (float *) malloc( sizeof(float) * npt ) ;
     switch( jj ){
       case 0:
         for( ii=0 ; ii < npt ; ii++ ) ref[jj][ii] = 1.0 ;
       break ;

       case 1:
         for( ii=0 ; ii < npt ; ii++ ) ref[jj][ii] = xfac*(ii-xmid) ;
       break ;

       case 2:
         for( ii=0 ; ii < npt ; ii++ ){
           val = xfac*(ii-xmid) ; ref[jj][ii] = val*val ;
         }
       break ;

       case 3:
         for( ii=0 ; ii < npt ; ii++ ){
           val = xfac*(ii-xmid) ; ref[jj][ii] = val*val*val ;
         }
       break ;

       default:
         for( ii=0 ; ii < npt ; ii++ ){
           val = xfac*(ii-xmid) ; ref[jj][ii] = pow(val,(double)(jj)) ;
         }
       break ;
     }
   }
   for( jj=0 ; jj < nort ; jj++ )   /* user supplied refs */
     ref[polort+1+jj] = ort[jj] ;

   for( ii=0 ; ii < npt ; ii++ ){
     val = far[ii] ;
     for( jj=0 ; jj < nref ; jj++ ) val += fit[jj] * ref[jj][ii] ;
     far[ii] = val ;
   }

   for( jj=0 ; jj <= polort ; jj++ ) free(ref[jj]) ;
   free(ref) ; EXRETURN ;
}

/*----------------------------------------------------------------------------*/
/*! Returns nref reference functions. */

float ** THD_build_polyref( int nref , int nvals )  /* 20 Sep 2007 */
{
   int jj,iv,kk ;
   float **ref ;
   double fac , x ;

ENTRY("THD_build_polyref") ;

   if( nref < 1 || nvals <= nref ){
     ERROR_message("THD_build_polyref: nref=%d  nvals=%d",nref,nvals) ;
     RETURN(NULL) ;
   }

   fac = 2.0 / (nvals-1.0) ;
   ref = (float **)malloc(sizeof(float *)*nref) ;
   for( jj=0 ; jj < nref ; jj++ ){
     ref[jj] = (float *) malloc( sizeof(float) * nvals ) ;
     for( kk=0 ; kk < nvals ; kk++ ){
       x = fac * kk - 1.0 ; ref[jj][kk] = (float)Plegendre( x , jj ) ;
     }
   }

   RETURN(ref) ;
}

/*----------------------------------------------------------------------------*/
/*! Returns 2*corder+3 reference functions. */

float ** THD_build_trigref( int corder , int nvals )
{
   int nref=2*corder+3 , jj,iv,kk ;
   float **ref , tm,fac,fq ;

ENTRY("THD_build_trigref") ;

   if( corder < 0 || nvals <= nref ){
     ERROR_message("THD_build_trigref: corder=%d  nvals=%d",corder,nvals) ;
     RETURN(NULL) ;
   }

   ref = (float **)malloc(sizeof(float *)*nref) ;
   for( jj=0 ; jj < nref ; jj++ )
     ref[jj] = (float *) malloc( sizeof(float) * nvals ) ;

   /* ref[0]: r(t) = 1 */
   for( iv=0 ; iv < nvals ; iv++ ) ref[0][iv] = 1.0f ;

   /* ref[1]: r(t) = t - tmid */
   tm = 0.5f * (nvals-1.0f) ; fac = 2.0f / nvals ;
   for( iv=0 ; iv < nvals ; iv++ ) ref[1][iv] = (iv-tm)*fac ;

   /* ref[2]: r(t) = (t-tmid)**2 */
   fac = fac*fac ;
   for( iv=0 ; iv < nvals ; iv++ ) ref[2][iv] = (iv-tm)*(iv-tm)*fac ;

   /* higher order ref[jj]: */
   for( jj=3,kk=1 ; kk <= corder ; kk++ ){
     fq = (2.0*PI*kk)/nvals ;

     /* r(t) = sin(2*PI*k*t/N) */
     for( iv=0 ; iv < nvals ; iv++ ) ref[jj][iv] = sin(fq*iv) ;
     jj++ ;

     /* r(t) = cos(2*PI*k*t/N) */
     for( iv=0 ; iv < nvals ; iv++ ) ref[jj][iv] = cos(fq*iv) ;
     jj++ ;
   }

   RETURN(ref) ;
}

/*----------------------------------------------------------------------------*/
 #undef  INMASK                                     /* ZSS: Changed from    */ 
 #define INMASK(i) ((mask == NULL || mask[i]))      /*       mask != NULL   */
/*----------------------------------------------------------------------------*/
/* Fits each voxel time series to a linear model:

                 nref-1
     z[i][t] = SUM     ref[q][t] * fit[q][i]
                 q=0

   where
    - i = voxel index      0..DSET_NVOX(dset)-1
    - t = time index       0..DSET_NVALS(dset)-1
    - q = reference index  0..nref-1
    - Fitting is done in the L-p sense, where p=meth (1 or 2).
    - The dataset itself is not modified.
    _ Return value is the nref fit images, plus 1 extra image that is
      the MAD of the residuals for meth=1 and the standard deviation for meth=2.
    - In float format, of course.
    - If NULL is returned, something bad happened.  Be afraid.
    - Also see function THD_medmad_bricks() for something simpler & similar.
------------------------------------------------------------------------------*/

MRI_IMARR * THD_time_fit_dataset( THD_3dim_dataset *dset ,
                                  int nref , float **ref , int meth , byte *mask )
{
   int ii , nvox,nval , qq,tt ;
   float *far , *fit , *var , val ;
   MRI_IMARR *imar ; MRI_IMAGE *qim ; float **fitar ;

ENTRY("THD_time_fit_dataset") ;

   if( !ISVALID_DSET(dset) ||
       nref < 1 || nref >= DSET_NVALS(dset) || ref == NULL ) RETURN(NULL) ;
   DSET_load(dset) ; if( !DSET_LOADED(dset) ) RETURN(NULL) ;

   /* construct output images */

   INIT_IMARR(imar) ;
   fitar = (float **)malloc(sizeof(float *)*nref) ;
   for( qq=0 ; qq < nref ; qq++ ){
     qim = mri_new_conforming( DSET_BRICK(dset,0) , MRI_float ) ;
     fitar[qq] = MRI_FLOAT_PTR(qim) ;
     ADDTO_IMARR(imar,qim) ;
   }
   qim = mri_new_conforming( DSET_BRICK(dset,0) , MRI_float ) ;
   var = MRI_FLOAT_PTR(qim) ; ADDTO_IMARR(imar,qim) ;

   nvox = DSET_NVOX(dset) ; nval = DSET_NVALS(dset) ;
   far  = (float *)malloc(sizeof(float)*nval) ;
   fit  = (float *)malloc(sizeof(float)*nref) ;
   for( ii=0 ; ii < nvox ; ii++ ){
     if( !INMASK(ii) ) continue ;
     qq = THD_extract_array( ii , dset , 0 , far ) ;  /* get data */
     if( qq == 0 ){
       switch(meth){                                  /* get fit */
         default:
         case 2: THD_generic_detrend_LSQ( nval, far, -1, nref,ref, fit ); break;
         case 1: THD_generic_detrend_L1 ( nval, far, -1, nref,ref, fit ); break;
       }
       for( qq=0 ; qq < nref ; qq++ ) fitar[qq][ii] = fit[qq] ; /* save fit */

       /* at this point, far[] contains the residuals */

       switch(meth){                    /* get stdev or MAD */
         default:
         case 2:{
           float mm,vv ;
           for( mm=0.0,tt=0 ; tt < nval ; tt++ ) mm += far[tt] ;
           mm /= nval ;
           for( vv=0.0,tt=0 ; tt < nval ; tt++ ) vv += (far[tt]-mm)*(far[tt]-mm) ;
           var[ii] = sqrtf( vv/(nval-1.0) ) ;
         }
         break ;

         case 1:{
           for( tt=0 ; tt < nval ; tt++ ) far[tt] = fabsf(far[tt]) ;
           var[ii] = qmed_float( nval , far ) ;
         }
         break ;
       }
     }
   }

   free(fit); free(far); free(fitar); RETURN(imar);
}

/*----------------------------------------------------------------------------*/
/* Get the time series data array at voxel #ii, detrended by the fit
   from THD_time_fit_dataset().  If scl != 0, also scale the result
   by the reciprocal of the stdev or MAD (last sub-brick in imar).
------------------------------------------------------------------------------*/

void THD_extract_detrended_array( THD_3dim_dataset *dset ,
                                  int nref, float **ref, MRI_IMARR *imar,
                                  int ii, int scl, float *far )
{
   int tt , nval , qq ;
   float val , **fitar , *var ;
   MRI_IMAGE *qim ;

ENTRY("THD_extract_detrended_array") ;

   if( !ISVALID_DSET(dset) ||
       nref < 1            || ref == NULL                ||
       imar == NULL        || IMARR_COUNT(imar) < nref+1 ||
       ii < 0              || ii >= DSET_NVOX(dset)      || far == NULL ) EXRETURN ;

   qq = THD_extract_array( ii , dset , 0 , far ) ;  /* get data */
   if( qq < 0 ) EXRETURN ;
   nval = DSET_NVALS(dset) ;

   fitar = (float **)malloc(sizeof(float *)*nref) ;
   for( qq=0 ; qq < nref ; qq++ ){
     qim = IMARR_SUBIM(imar,qq) ; fitar[qq] = MRI_FLOAT_PTR(qim) ;
   }
   qim = IMARR_SUBIM(imar,nref) ; var = MRI_FLOAT_PTR(qim) ;

   for( tt=0 ; tt < nval ; tt++ ){  /* get residuals */
     val = far[tt] ;
     for( qq=0 ; qq < nref ; qq++ ) val -= ref[qq][tt] * fitar[qq][ii] ;
     far[tt] = val ;
   }

   if( scl && var[ii] > 0.0f ){
     val = 1.0f / var[ii] ;
     for( tt=0 ; tt < nval ; tt++ ) far[tt] *= val ;
   }
   
   /* ZSS: Need to free fitar */
   free(fitar); fitar=NULL;
   EXRETURN ;
}

/*----------------------------------------------------------------------------*/
/* Detrend a dataset:
    - dset  = input time series dataset (nvals long)
    - nref  = number of ref vectors
    - ref   = reference vectors [0..nref-1][0..nvals-1]
    - meth  = 1 or 2 (for L_p fitting, where p=meth)
    - scl   = if 1, scale data by reciprocal of MAD or stdev (meth=1 or 2)
    - mask  = if not NULL, byte mask; voxel outside the mask are set to 0
    - imar  = if not NULL, coefficient images from THD_time_fit_dataset()

  Output is in float format, of course.  If NULL is returned, something bad
  transpired, and your mother will get an e-mail explaining how stupid
  you were.
------------------------------------------------------------------------------*/

THD_3dim_dataset * THD_detrend_dataset( THD_3dim_dataset *dset ,
                                        int nref , float **ref ,
                                        int meth , int scl ,
                                        byte *mask , MRI_IMARR **imar )
{
   MRI_IMARR *qmar ;
   int ii,jj,kk , nvals,nvox , iv ;
   float *var ;
   THD_3dim_dataset *newset ;

ENTRY("THD_detrend_dataset") ;

   if( !ISVALID_DSET(dset) ) RETURN(NULL) ;
   nvals = DSET_NVALS(dset) ; nvox = DSET_NVOX(dset) ;

   qmar = THD_time_fit_dataset( dset , nref,ref , meth , mask ) ;
   if( qmar == NULL ) RETURN(NULL) ;

   newset = EDIT_empty_copy(dset) ;
   for( iv=0 ; iv < nvals ; iv++ ){
     EDIT_substitute_brick( newset , iv , MRI_float , NULL ) ;
     EDIT_BRICK_FACTOR( newset , iv , 0.0f ) ;  /* 04 Jun 2007 */
   }

   var = (float *)malloc(sizeof(float)*nvals) ;
   for( ii=0 ; ii < nvox ; ii++ ){
     if( mask == NULL || mask[ii] )
       THD_extract_detrended_array( dset , nref,ref , qmar , ii,scl , var ) ;
     else
       memset(var,0,sizeof(float)*nvals) ;
     THD_insert_series( ii , newset , nvals , MRI_float , var , 0 ) ;
   }

   free(var) ;
   if( imar != NULL ) *imar = qmar ;
   else               DESTROY_IMARR(qmar) ;

   RETURN(newset) ;
}

/*----------------------------------------------------------------------------*/
/* Can be used as a sort-of inverse to THD_detrend_dataset() */

int THD_retrend_dataset( THD_3dim_dataset *dset ,
                         int nref , float **ref ,
                         int scl , byte *mask , MRI_IMARR *imar )
{
   int ii,qq,tt , nvals,nvox ;
   MRI_IMAGE *qim ; float **fitar , *far , fac , *var , val ;

ENTRY("THD_retrend_dataset") ;

   if( !ISVALID_DSET(dset) ||
       nref < 1            || ref == NULL ||
       imar == NULL        || IMARR_COUNT(imar) <= nref ) RETURN(0) ;

   nvals = DSET_NVALS(dset) ; nvox = DSET_NVOX(dset) ;

   fitar = (float **)malloc(sizeof(float *)*nref) ;
   for( qq=0 ; qq < nref ; qq++ ){
     qim = IMARR_SUBIM(imar,qq) ; fitar[qq] = MRI_FLOAT_PTR(qim) ;
   }
   qim = IMARR_SUBIM(imar,nref) ; var = MRI_FLOAT_PTR(qim) ;

   far  = (float *)malloc(sizeof(float)*nvals) ;
   for( ii=0 ; ii < nvox ; ii++ ){
     if( mask != NULL && !mask[ii] ) continue ;
     qq = THD_extract_array( ii , dset , 0 , far ) ;  /* get data */
     if( qq < 0 ) continue ;
     fac = (scl) ? var[ii] : 1.0f ;
     for( tt=0 ; tt < nvals ; tt++ ){  /* add fit back in */
       val = far[tt] * fac ;
       for( qq=0 ; qq < nref ; qq++ ) val += ref[qq][tt] * fitar[qq][ii] ;
       far[tt] = val ;
     }
     THD_insert_series( ii , dset , nvals , MRI_float , far , 0 ) ;
   }

   free(far) ; free(fitar) ; RETURN(1) ;
}
