#include "mrilib.h" #define SPEARMAN 1 #define QUADRANT 2 #define PEARSON 3 #define KTAUB 4 #define COVAR 5 #define YCOEF 6 #define PARTIAL 7 #undef MYatanh #define MYatanh(x) ( ((x)<-0.999329f) ? -4.0f \ :((x)>+0.999329f) ? +4.0f : atanhf(x) ) /*----------------------------------------------------------------------------*/ void usage_3dTcorrelate(int detail) { printf( "\n" "Usage: 3dTcorrelate [options] xset yset ~1~\n" "\n" "Computes the correlation coefficient between corresponding voxel\n" "time series in two input 3D+time datasets 'xset' and 'yset', and\n" "stores the output in a new 1 sub-brick dataset.\n" "\n" "--------\n" "Options: ~1~\n" "--------\n" " -pearson = Correlation is the normal Pearson (product moment)\n" " correlation coefficient [this is the default method].\n" " -spearman = Correlation is the Spearman (rank) correlation\n" " coefficient.\n" " -quadrant = Correlation is the quadrant correlation coefficient.\n" " -ktaub = Correlation is Kendall's tau_b coefficient.\n" " ++ For 'continuous' or finely-discretized data, tau_b\n" " and rank correlation are nearly equivalent.\n" " -covariance = Covariance instead of correlation. That would be \n" " the Pearson correlation without scaling by the product\n" " of the standard deviations.\n" " -partial z = Partial Pearson's Correlation of X & Y, adjusting for Z \n" " Supply dataset z to be taken into account after '-partial'.\n" " ** EXPERIMENTAL **\n" " -ycoef = Least squares coefficient that best fits y(t) to x(t),\n" " after detrending. That is, if yd(t) is the detrended\n" " y(t) and xd(t) is the detrended x(t), then the ycoef\n" " value is from the OLSQ fit to xd(t) = ycoef * y(t) + error.\n" "\n" " -Fisher = Apply the 'Fisher' (inverse hyperbolic tangent = arctanh)\n" " transformation to (correlation) results.\n" " ++ It does NOT make sense to use this with '-ktaub', but if\n" " you want to do it, the program will not stop you.\n" " ++ This option does not apply to '-covariance' or '-ycoef'.\n" "\n" " -polort m = Remove polynomial trend of order 'm', for m=-1..9.\n" " [default is m=1; removal is by least squares].\n" " Using m=-1 means no detrending; this is only useful\n" " for data/information that has been pre-processed.\n" "\n" " -ort r.1D = Also detrend using the columns of the 1D file 'r.1D'.\n" " Only one -ort option can be given. If you want to use\n" " more than one, create a temporary file using 1dcat.\n" "\n" " -autoclip = Clip off low-intensity regions in the two datasets,\n" " -automask = so that the correlation is only computed between\n" " high-intensity (presumably brain) voxels. The\n" " intensity level is determined the same way that\n" " 3dClipLevel works.\n" " ** At present, this program does not have a '-mask'\n" " option. Maybe someday?\n" "\n" " -zcensor = Omit (censor out) any time points where the xset\n" " volume is all zero OR where the yset volume is all\n" " zero (in mask). Please note that using -zcensor\n" " with any detrending is unlikely to be useful.\n" " ** That is, you should use '-polort -1' with this\n" " option, and NOT use '-ort'.\n" " * In fact, using '-zcensor' will set polort = -1,\n" " and if you insist on using detrending, you will\n" " have to put the '-polort' option AFTER '-zcensor.\n" " ** Since correlation is calculated from the sum\n" " of the point-by-point products xset(t)*yset(t),\n" " why censor out points where xset or yset is 0?\n" " Because the denominator of correlation is from\n" " the sum of xset(t)*xset(t) and yset(t)*yset(t)\n" " and unless the t-points where the datasets are\n" " censored are BOTH zero at the same time, the\n" " denominator will be incorrect.\n" " ** [RWCox - Dec 2019, day of Our Lady of Guadalupe]\n" " [for P Molfese and E Finn]\n" "\n" " -prefix p = Save output into dataset with prefix 'p'\n" " [default prefix is 'Tcorr'].\n" "\n" "------\n" "Notes: ~1~\n" "------\n" "* The output dataset is functional bucket type, with just one\n" " sub-brick, stored in floating point format.\n" "\n" "* Because both time series are detrended prior to correlation,\n" " the results will not be identical to using FIM or FIM+ to\n" " calculate correlations (whose ideal vector is not detrended).\n" "\n" "* Also see 3dTcorr1D if you want to correlate each voxel time series\n" " in a dataset xset with a single 1D time series file, instead of\n" " separately with time series from another 3D+time dataset.\n" "\n" "* https://en.wikipedia.org/wiki/Correlation\n" "* https://en.wikipedia.org/wiki/Pearson_product-moment_correlation_coefficient\n" "* https://en.wikipedia.org/wiki/Spearman%%27s_rank_correlation_coefficient\n" "* https://en.wikipedia.org/wiki/Kendall_tau_rank_correlation_coefficient\n" "* https://en.wikipedia.org/wiki/Partial_correlation\n" "\n" "-- RWCox - Aug 2001++\n" ) ; PRINT_COMPILE_DATE ; return; } /*----------------------------------------------------------------------------*/ float THD_ycoef( int npt , float *xx , float *yy ) /* 20 Feb 2014 */ { float coef[1] = {0.0f} ; if( THD_is_constant(npt,xx) || THD_is_constant(npt,yy) ) return 0.0f ; THD_generic_detrend_LSQ(npt,xx,-1,1,&yy,coef) ; return coef[0] ; } /*----------------------------------------------------------------------------*/ int main( int argc , char *argv[] ) { THD_3dim_dataset *xset , *yset , *cset, *pset=NULL ; int nopt=1 , method=PEARSON , do_autoclip=0 ; int nvox , nvals , ii , polort=1 ; MRI_IMAGE *xsim , *ysim , *psim; float *xsar , *ysar , *car , *psar=NULL; char *prefix = "Tcorr" ; char *psetFile = NULL ; byte *mmm=NULL ; MRI_IMAGE *im_ort=NULL ; /* 13 Mar 2003 */ int nort=0 ; float **fort=NULL ; int do_atanh = 0 ; /* 12 Jan 2018 */ int do_zcens=0 , nzcens=0 , ngood , *zcens=NULL ; /* 12 Dec 2019 */ float *xcens=NULL , *ycens=NULL , *pcens=NULL ; char blab[128] , alab[192] ; /* 31 Aug 2021 */ /*----*/ if (argc == 1) { usage_3dTcorrelate(1); exit(0); } /* Bob's help shortcut */ mainENTRY("3dTCorrelate main"); machdep(); AFNI_logger("3dTcorrelate",argc,argv); PRINT_VERSION("3dTcorrelate") ; THD_check_AFNI_version("3dTcorrelate") ; /*-- option processing --*/ while( nopt < argc && argv[nopt][0] == '-' ){ if (strcasecmp(argv[nopt], "-h") == 0 || strcasecmp(argv[nopt], "-help") == 0) { usage_3dTcorrelate(strlen(argv[nopt]) > 3 ? 2:1); exit(0); } if( strcasecmp(argv[nopt],"-zcensor") == 0 ){ /* 12 Dec 2019 */ do_zcens = 1 ; if( polort >= 0 ){ INFO_message("-zcensor sets polort = -1") ; polort = -1 ; } nopt++ ; continue ; } if( strcasecmp(argv[nopt],"-ort") == 0 ){ /* 13 Mar 2003 */ if( im_ort != NULL ){ ERROR_exit("Can't have multiple -ort options!") ; } im_ort = mri_read_1D( argv[++nopt] ) ; if( im_ort == NULL ){ ERROR_exit("Can't read 1D file %s",argv[nopt]) ; } nort = im_ort->ny ; fort = (float **) malloc( sizeof(float *)*nort ) ; for( ii=0 ; ii < nort ; ii++ ) fort[ii] = MRI_FLOAT_PTR(im_ort) + (ii*im_ort->nx) ; nopt++ ; continue ; } if( strcasecmp(argv[nopt],"-autoclip") == 0 || strcasecmp(argv[nopt],"-automask") == 0 ){ do_autoclip = 1 ; nopt++ ; continue ; } if( strcasecmp(argv[nopt],"-pearson") == 0 ){ method = PEARSON ; nopt++ ; continue ; } if( strcasecmp(argv[nopt],"-partial") == 0 ) { method = PARTIAL ; nopt++ ; //name the covariate dataset psetFile = argv[nopt]; INFO_message("Performing Partial Correlation with: %s\n", psetFile); pset = THD_open_dataset( argv[nopt++] ) ; if( pset == NULL ) { ERROR_exit("-partial Can't open dataset %s",argv[nopt]) ; } continue ; } if( strcasecmp(argv[nopt],"-covariance") == 0 ){ method = COVAR ; nopt++ ; continue ; } if( strcasecmp(argv[nopt],"-ycoef") == 0 ){ /* 20 Feb 2014 */ method = YCOEF ; nopt++ ; continue ; } if( strcasecmp(argv[nopt],"-spearman") == 0 ){ method = SPEARMAN ; nopt++ ; continue ; } if( strcasecmp(argv[nopt],"-quadrant") == 0 ){ method = QUADRANT ; nopt++ ; continue ; } if( strcasecmp(argv[nopt],"-ktaub") == 0 ){ method = KTAUB ; nopt++ ; continue ; } if( strcasecmp(argv[nopt],"-fisher") == 0 ){ /* 12 Jan 2018 */ do_atanh = 1 ; nopt++ ; continue ; } if( strcasecmp(argv[nopt],"-prefix") == 0 ){ prefix = argv[++nopt] ; if( !THD_filename_ok(prefix) ){ ERROR_exit("Illegal value after -prefix") ; } nopt++ ; continue ; } if( strcasecmp(argv[nopt],"-polort") == 0 ){ char *cpt ; int val = strtod(argv[++nopt],&cpt) ; if( *cpt != '\0' || val < -1 || val > 9 ){ ERROR_exit("Illegal value '%s' after -polort",argv[nopt]) ; } polort = val ; if( polort >=0 && do_zcens ) WARNING_message("-polort %d is combined with -zcensor",polort) ; nopt++ ; continue ; } ERROR_message("Illegal option %s\n", argv[nopt]); suggest_best_prog_option(argv[0], argv[nopt]); exit(1); } if (argc < 2) { ERROR_message("Too few options, use -help for details"); exit(1); } if( do_atanh && (method == COVAR || method == YCOEF) ){ /* 12 Jan 2018 */ WARNING_message("-Fisher doesn't apply to your chosen method") ; do_atanh = 0 ; } /*-- open datasets, check for legality --*/ if( nopt+1 >= argc ){ ERROR_exit("Need 2 input datasets on command line") ; } xset = THD_open_dataset( argv[nopt] ) ; if( xset == NULL ){ ERROR_exit("Can't open input dataset %s",argv[nopt]) ; } if( DSET_NVALS(xset) < 2 ){ ERROR_exit("Input dataset %s does not have multiple time points",argv[nopt]) ; } yset = THD_open_dataset( argv[++nopt] ) ; if( yset == NULL ){ ERROR_exit("Can't open input dataset %s",argv[nopt]) ; } if( DSET_NVALS(yset) != DSET_NVALS(xset) ){ ERROR_exit("Input dataset %s is different length than %s\n",argv[nopt],argv[nopt-1]) ; } if( DSET_NVOX(yset) != DSET_NVOX(xset) ){ ERROR_exit("Input dataset %s is different size than %s\n", argv[nopt],argv[nopt-1]) ; } if( im_ort != NULL && im_ort->nx < DSET_NVALS(xset) ){ ERROR_exit("Input datasets are longer than -ort file") ; } if( !EQUIV_GRIDS(xset,yset) ) WARNING_message("Grid mismatch between input datasets!") ; DSET_load(xset) ; CHECK_LOAD_ERROR(xset) ; DSET_load(yset) ; CHECK_LOAD_ERROR(yset) ; if ( method == PARTIAL ) { //pset if( DSET_NVALS(pset) != DSET_NVALS(xset) ) { //assume that if it doesn't match xset, then it doesn't match yset ERROR_exit("-parital input dataset %s is different length than %s\n", psetFile,argv[nopt-1]) ; } if( !EQUIV_GRIDS(xset,pset) ) WARNING_message("Grid mismatch between input datasets & partial dataset!") ; DSET_load(pset) ; CHECK_LOAD_ERROR(pset) ; } /*-- compute mask array, if desired --*/ nvox = DSET_NVOX(xset) ; ngood = nvals = DSET_NVALS(xset) ; if( do_autoclip ){ byte *xmm , *ymm ; xmm = THD_automask( xset ) ; ymm = THD_automask( yset ) ; mmm = (byte *) malloc(sizeof(byte)*nvox) ; for( ii=0 ; ii < nvox ; ii++ ) mmm[ii] = ( xmm[ii] && ymm[ii] ) ; free(xmm) ; free(ymm) ; ii = THD_countmask( nvox , mmm ) ; INFO_message("%d voxels survive -autoclip",ii) ; if( ii == 0 ) exit(1) ; } /*-- compute censoring, if desired [12 Dec 2019] --*/ if( do_zcens ){ /* make list of time points to keep */ zcens = (int *)calloc(sizeof(int),nvals) ; for( ii=0 ; ii < nvals ; ii++ ){ if( mri_nonzero_count_inmask(DSET_BRICK(xset,ii),mmm) > 0 && /* keep only */ mri_nonzero_count_inmask(DSET_BRICK(yset,ii),mmm) > 0 ){ /* if BOTH */ zcens[ii] = 1; nzcens++; /* have some nonzero data */ } } if( nzcens == 0 ) ERROR_exit("-zcensor keeps 0 data volumes to process, out of %d input :(",nvals) ; else if( nzcens == 1 ) ERROR_exit("-zcensor keeps only 1 data volume to process, out of %d input :(",nvals) ; else if( nzcens == nvals ) INFO_message("-zcensor keeps ALL %d data volumes to process",nvals) ; else INFO_message("-zcensor omits %d data volumes, and keeps %d",nvals-nzcens,nzcens) ; ngood = nzcens ; /* number of good time points */ if( ngood < nvals ){ xcens = (float *)malloc(sizeof(float)*nzcens) ; ycens = (float *)malloc(sizeof(float)*nzcens) ; pcens = (float *)malloc(sizeof(float)*nzcens) ; if( polort >= 0 || nort > 0 ) WARNING_message("Using -zcensor with detrending is dangerous!") ; } else { do_zcens = 0 ; } } /*-- create output dataset --*/ cset = EDIT_empty_copy( xset ) ; EDIT_dset_items( cset , ADN_prefix , prefix , ADN_nvals , 1 , ADN_ntt , 0 , /* no time axis */ ADN_type , HEAD_FUNC_TYPE , ADN_func_type , FUNC_BUCK_TYPE , ADN_none ) ; if( THD_deathcon() && THD_is_file(DSET_HEADNAME(cset)) ){ ERROR_exit("Output dataset %s already exists\n",DSET_HEADNAME(cset)) ; } EDIT_BRICK_FACTOR(cset,0,0.0) ; /* to be safe */ switch( method ){ /* looks nice */ default: /* to have labels */ case PEARSON: strcpy(blab,"PearCorr") ; if( do_atanh ){ /* 31 Aug 2021 */ EDIT_BRICK_TO_FIZT(cset,0) ; /* Fisher makes r ~ N(0,1) ? */ } else { EDIT_BRICK_TO_FICO(cset,0,ngood,1,polort+1+nort) ; /* stat params */ } break ; case SPEARMAN: strcpy(blab,"SpmnCorr" ) ; break ; case QUADRANT: strcpy(blab,"QuadCorr" ) ; break ; case KTAUB: strcpy(blab,"TaubCorr" ) ; break ; case PARTIAL: strcpy(blab,"PartialCorr") ; break ; case COVAR: strcpy(blab,"Covariance" ) ; break ; case YCOEF: strcpy(blab,"Ycoef" ) ; break ; } if( do_atanh ){ sprintf(alab,"atanh_%s",blab) ; } else { strcpy(alab,blab) ; } EDIT_BRICK_LABEL(cset,0,alab) ; EDIT_substitute_brick( cset , 0 , MRI_float , NULL ) ; /* make array */ car = DSET_ARRAY(cset,0) ; /* get array */ /* include prior history [9 May 2019 rickr] */ tross_Copy_History( xset , cset ); tross_Make_History( "3dTcorrelate" , argc,argv , cset ) ; /* loop over voxels, correlate */ /* INFO_message("have %d voxels to work with, %d values/time series\n",nvox,ngood);*/ for( ii=0 ; ii < nvox ; ii++ ){ if( mmm != NULL && mmm[ii] == 0 ){ /* the simple case */ car[ii] = 0.0 ; continue ; } /* get time series */ xsim = THD_extract_series(ii,xset,0) ; xsar = MRI_FLOAT_PTR(xsim) ; ysim = THD_extract_series(ii,yset,0) ; ysar = MRI_FLOAT_PTR(ysim) ; if ( method == PARTIAL ) { psim = THD_extract_series(ii,pset,0); psar = MRI_FLOAT_PTR(psim); } (void)THD_generic_detrend_LSQ( nvals,xsar, polort, nort,fort,NULL ) ; /* 13 Mar 2003 */ (void)THD_generic_detrend_LSQ( nvals,ysar, polort, nort,fort,NULL ) ; if( do_zcens ){ /* 12 Dec 2019 */ int jj , kk ; for( kk=jj=0 ; jj < nvals ; jj++ ){ if( zcens[jj] ){ xcens[kk] = xsar[jj] ; ycens[kk] = ysar[jj] ; if( psar != NULL ) pcens[kk] = psar[jj] ; kk++; } } } else { xcens = xsar ; ycens = ysar ; pcens = psar ; } /* DAT = Do Arc Tanh = Sir Ronald Aylmer Fisher's transformation */ #undef DAT #define DAT if(do_atanh)car[ii]=MYatanh(car[ii]) switch( method ){ /* correlate */ default: case PEARSON: car[ii] = THD_pearson_corr ( ngood,xcens,ycens ); DAT; break; case SPEARMAN: car[ii] = THD_spearman_corr( ngood,xcens,ycens ); DAT; break; case QUADRANT: car[ii] = THD_quadrant_corr( ngood,xcens,ycens ); DAT; break; case KTAUB: car[ii] = THD_ktaub_corr ( ngood,xcens,ycens ); DAT; break; case COVAR: car[ii] = THD_covariance ( ngood,xcens,ycens ); break; case YCOEF: car[ii] = THD_ycoef ( ngood,xcens,ycens ); break; case PARTIAL: car[ii] = THD_pearson_partial_corr( ngood,xcens,ycens,pcens ); DAT; break; } mri_free(xsim) ; mri_free(ysim) ; /* toss time series */ } /* end of loop over voxels */ /* toss the other trash */ DSET_unload(xset); DSET_unload(yset); if( mmm != NULL ) free(mmm); if( do_zcens ){ if( xcens != NULL ) free(xcens) ; /* 12 Dec 2019 */ if( ycens != NULL ) free(ycens) ; if( pcens != NULL ) free(pcens) ; } /* e finito */ DSET_write(cset) ; INFO_message("Wrote dataset: %s",DSET_BRIKNAME(cset)) ; exit(0) ; }