MRI_IMAGE * DES_get_psinv( int ntim , int nref , float **ref ) { MRI_IMAGE *refim , *psinv ; float *refar , *jar ; int ii , jj ; refim = mri_new(ntim,nref,MRI_float) ; refar = MRI_FLOAT_PTR(refim) ; for( jj=0 ; jj < nref ; jj++ ){ jar = refar + jj*ntim ; for( ii=0 ; ii < ntim ; ii++ ) jar[ii] = ref[jj][ii] ; } mri_matrix_psinv_svd(1) ; psinv = mri_matrix_psinv(refim,NULL,0.0f) ; mri_free(refim) ; return psinv ; }
int main( int argc , char *argv[] ) { THD_3dim_dataset *inset=NULL ; byte *mask=NULL ; int mask_nx=0,mask_ny=0,mask_nz=0 , automask=0 , masknum=0 ; int iarg=1 , verb=1 , ntype=0 , nev,kk,ii,nxyz,nt ; float na,nb,nc , dx,dy,dz ; MRI_IMARR *imar=NULL ; int *ivox ; MRI_IMAGE *pim ; int do_vmean=0 , do_vnorm=0 , sval_itop=0 ; int polort=-1 ; float *ev ; MRI_IMARR *ortar ; MRI_IMAGE *ortim ; int nyort=0 ; float bpass_L=0.0f , bpass_H=0.0f , dtime ; int do_bpass=0 ; if( argc < 2 || strcmp(argv[1],"-help") == 0 ){ printf( "Usage: 3dmaskSVD [options] inputdataset\n" "Author: Zhark the Gloriously Singular\n" "\n" "* Computes the principal singular vector of the time series\n" " vectors extracted from the input dataset over the input mask.\n" " ++ You can use the '-sval' option to change which singular\n" " vectors are output.\n" "* The sign of the output vector is chosen so that the average\n" " of arctanh(correlation coefficient) over all input data\n" " vectors (from the mask) is positive.\n" "* The output vector is normalized: the sum of its components\n" " squared is 1.\n" "* You probably want to use 3dDetrend (or something similar) first,\n" " to get rid of annoying artifacts, such as motion, breathing,\n" " dark matter interactions with the brain, etc.\n" " ++ If you are lazy scum like Zhark, you might be able to get\n" " away with using the '-polort' option.\n" " ++ In particular, if your data time series has a nonzero mean,\n" " then you probably want at least '-polort 0' to remove the\n" " mean, otherwise you'll pretty much just get a constant\n" " time series as the principal singular vector!\n" "* An alternative to this program would be 3dmaskdump followed\n" " by 1dsvd, which could give you all the singular vectors you\n" " could ever want, and much more -- enough to confuse you for days.\n" " ++ In particular, although you COULD input a 1D file into\n" " 3dmaskSVD, the 1dsvd program would make much more sense.\n" "* This program will be pretty slow if there are over about 2000\n" " voxels in the mask. It could be made more efficient for\n" " such cases, but you'll have to give Zhark some 'incentive'.\n" "* Result vector goes to stdout. Redirect per your pleasures and needs.\n" "* Also see program 3dLocalSVD if you want to compute the principal\n" " singular time series vector from a neighborhood of EACH voxel.\n" " ++ (Which is a pretty slow operation!)\n" "* http://en.wikipedia.org/wiki/Singular_value_decomposition\n" "\n" "-------\n" "Options:\n" "-------\n" " -vnorm = L2 normalize all time series before SVD [recommended!]\n" " -sval a = output singular vectors 0 .. a [default a=0 = first one only]\n" " -mask mset = define the mask [default is entire dataset == slow!]\n" " -automask = you'll have to guess what this option does\n" " -polort p = if you are lazy and didn't run 3dDetrend (like Zhark)\n" " -bpass L H = bandpass [mutually exclusive with -polort]\n" " -ort xx.1D = time series to remove from the data before SVD-ization\n" " ++ You can give more than 1 '-ort' option\n" " ++ 'xx.1D' can contain more than 1 column\n" " -input ddd = alternative way to give the input dataset name\n" "\n" "-------\n" "Example:\n" "-------\n" " You have a mask dataset with discrete values 1, 2, ... 77 indicating\n" " some ROIs; you want to get the SVD from each ROI's time series separately,\n" " and then put these into 1 big 77 column .1D file. You can do this using\n" " a csh shell script like the one below:\n" "\n" " # Compute the individual SVD vectors\n" " foreach mm ( `count 1 77` )\n" " 3dmaskSVD -vnorm -mask mymask+orig\"<${mm}..${mm}>\" epi+orig > qvec${mm}.1D\n" " end\n" " # Glue them together into 1 big file, then delete the individual files\n" " 1dcat qvec*.1D > allvec.1D\n" " /bin/rm -f qvec*.1D\n" " # Plot the results to a JPEG file, then compute their correlation matrix\n" " 1dplot -one -nopush -jpg allvec.jpg allvec.1D\n" " 1ddot -terse allvec.1D > allvec_COR.1D\n" "\n" " [[ If you use the bash shell, you'll have to figure out the syntax ]]\n" " [[ yourself. Zhark has no sympathy for you bash shell infidels, and ]]\n" " [[ considers you only slightly better than those lowly Emacs users. ]]\n" " [[ And do NOT ever even mention 'nedit' in Zhark's august presence! ]]\n" ) ; PRINT_COMPILE_DATE ; exit(0) ; } /*---- official startup ---*/ PRINT_VERSION("3dmaskSVD"); mainENTRY("3dmaskSVD main"); machdep(); AFNI_logger("3dmaskSVD",argc,argv); AUTHOR("Zhark the Singular"); /*---- loop over options ----*/ INIT_IMARR(ortar) ; mpv_sign_meth = AFNI_yesenv("AFNI_3dmaskSVD_meansign") ; while( iarg < argc && argv[iarg][0] == '-' ){ if( strcasecmp(argv[iarg],"-bpass") == 0 ){ if( iarg+2 >= argc ) ERROR_exit("need 2 args after -bpass") ; bpass_L = (float)strtod(argv[++iarg],NULL) ; bpass_H = (float)strtod(argv[++iarg],NULL) ; if( bpass_L < 0.0f || bpass_H <= bpass_L ) ERROR_exit("Illegal values after -bpass: %g %g",bpass_L,bpass_H) ; iarg++ ; continue ; } if( strcmp(argv[iarg],"-ort") == 0 ){ /* 01 Oct 2009 */ int nx,ny ; if( ++iarg >= argc ) ERROR_exit("Need argument after '-ort'") ; ortim = mri_read_1D( argv[iarg] ) ; if( ortim == NULL ) ERROR_exit("-ort '%s': Can't read 1D file",argv[iarg]) ; nx = ortim->nx ; ny = ortim->ny ; if( nx == 1 && ny > 1 ){ MRI_IMAGE *tim=mri_transpose(ortim); mri_free(ortim); ortim = tim; ny = 1; } mri_add_name(argv[iarg],ortim) ; ADDTO_IMARR(ortar,ortim) ; nyort += ny ; iarg++ ; continue ; } if( strcmp(argv[iarg],"-polort") == 0 ){ char *qpt ; if( ++iarg >= argc ) ERROR_exit("Need argument after '-polort'") ; polort = (int)strtod(argv[iarg],&qpt) ; if( *qpt != '\0' ) WARNING_message("Illegal non-numeric value after -polort") ; iarg++ ; continue ; } if( strcmp(argv[iarg],"-vnorm") == 0 ){ do_vnorm = 1 ; iarg++ ; continue ; } if( strcmp(argv[iarg],"-input") == 0 ){ if( inset != NULL ) ERROR_exit("Can't have two -input options") ; if( ++iarg >= argc ) ERROR_exit("Need argument after '-input'") ; inset = THD_open_dataset( argv[iarg] ) ; CHECK_OPEN_ERROR(inset,argv[iarg]) ; iarg++ ; continue ; } if( strcmp(argv[iarg],"-sval") == 0 ){ if( ++iarg >= argc ) ERROR_exit("Need argument after '-sval'") ; sval_itop = (int)strtod(argv[iarg],NULL) ; if( sval_itop < 0 ){ sval_itop = 0 ; WARNING_message("'-sval' reset to 0") ; } iarg++ ; continue ; } if( strcmp(argv[iarg],"-mask") == 0 ){ THD_3dim_dataset *mset ; int mmm ; if( ++iarg >= argc ) ERROR_exit("Need argument after '-mask'") ; if( mask != NULL || automask ) ERROR_exit("Can't have two mask inputs") ; mset = THD_open_dataset( argv[iarg] ) ; CHECK_OPEN_ERROR(mset,argv[iarg]) ; DSET_load(mset) ; CHECK_LOAD_ERROR(mset) ; mask_nx = DSET_NX(mset); mask_ny = DSET_NY(mset); mask_nz = DSET_NZ(mset); mask = THD_makemask( mset , 0 , 0.5f, 0.0f ) ; DSET_delete(mset) ; if( mask == NULL ) ERROR_exit("Can't make mask from dataset '%s'",argv[iarg]) ; masknum = mmm = THD_countmask( mask_nx*mask_ny*mask_nz , mask ) ; INFO_message("Number of voxels in mask = %d",mmm) ; if( mmm < 2 ) ERROR_exit("Mask is too small to process") ; iarg++ ; continue ; } if( strcmp(argv[iarg],"-automask") == 0 ){ if( mask != NULL ) ERROR_exit("Can't have two mask inputs!") ; automask = 1 ; iarg++ ; continue ; } ERROR_exit("Unknown option '%s'",argv[iarg]) ; } /*--- end of loop over options ---*/ /*---- deal with input dataset ----*/ if( inset == NULL ){ if( iarg >= argc ) ERROR_exit("No input dataset on command line?") ; inset = THD_open_dataset( argv[iarg] ) ; CHECK_OPEN_ERROR(inset,argv[iarg]) ; } nt = DSET_NVALS(inset) ; /* vector lengths */ if( nt < 9 ) ERROR_exit("Must have at least 9 values per voxel") ; if( polort+1 >= nt ) ERROR_exit("'-polort %d' too big for time series length = %d",polort,nt) ; DSET_load(inset) ; CHECK_LOAD_ERROR(inset) ; nxyz = DSET_NVOX(inset) ; DSET_UNMSEC(inset) ; dtime = DSET_TR(inset) ; if( dtime <= 0.0f ) dtime = 1.0f ; do_bpass = (bpass_L < bpass_H) ; if( do_bpass ){ kk = THD_bandpass_OK( nt , dtime , bpass_L , bpass_H , 1 ) ; if( kk <= 0 ) ERROR_exit("Can't continue since -bpass setup is illegal") ; polort = -1 ; } /*--- deal with the masking ---*/ if( mask != NULL ){ if( mask_nx != DSET_NX(inset) || mask_ny != DSET_NY(inset) || mask_nz != DSET_NZ(inset) ) ERROR_exit("-mask dataset grid doesn't match input dataset") ; } else if( automask ){ int mmm ; mask = THD_automask( inset ) ; if( mask == NULL ) ERROR_message("Can't create -automask from input dataset?") ; masknum = mmm = THD_countmask( DSET_NVOX(inset) , mask ) ; INFO_message("Number of voxels in automask = %d",mmm) ; if( mmm < 9 ) ERROR_exit("Automask is too small to process") ; } else { mask = (byte *)malloc(sizeof(byte)*nxyz) ; masknum = nxyz ; memset( mask , 1 , sizeof(byte)*nxyz ) ; INFO_message("Using all %d voxels in dataset",nxyz) ; } nev = MIN(nt,masknum) ; /* max possible number of eigenvalues */ if( sval_itop >= nev ){ sval_itop = nev-1 ; WARNING_message("'-sval' reset to '%d'",sval_itop) ; } mri_principal_vector_params( 0 , do_vnorm , sval_itop ) ; mri_principal_setev(nev) ; /*-- get data vectors --*/ ivox = (int *)malloc(sizeof(int)*masknum) ; for( kk=ii=0 ; ii < nxyz ; ii++ ) if( mask[ii] ) ivox[kk++] = ii ; INFO_message("Extracting data vectors") ; imar = THD_extract_many_series( masknum, ivox, inset ) ; DSET_unload(inset) ; if( imar == NULL ) ERROR_exit("Can't get data vector?!") ; /*-- detrending --*/ if( polort >= 0 || nyort > 0 || do_bpass ){ float **polref=NULL ; float *tsar ; int nort=IMARR_COUNT(ortar) , nref=0 ; if( polort >= 0 ){ /* polynomials */ nref = polort+1 ; polref = THD_build_polyref(nref,nt) ; } if( nort > 0 ){ /* other orts */ float *oar , *par ; int nx,ny , qq,tt ; for( kk=0 ; kk < nort ; kk++ ){ /* loop over input -ort files */ ortim = IMARR_SUBIM(ortar,kk) ; nx = ortim->nx ; ny = ortim->ny ; if( nx < nt ) ERROR_exit("-ort '%s' length %d shorter than dataset length %d" , ortim->name , nx , nt ) ; polref = (float **)realloc(polref,(nref+ny)*sizeof(float *)) ; oar = MRI_FLOAT_PTR(ortim) ; for( qq=0 ; qq < ny ; qq++,oar+=nx ){ par = polref[nref+qq] = (float *)malloc(sizeof(float)*nt) ; for( tt=0 ; tt < nt ; tt++ ) par[tt] = oar[tt] ; if( polort == 0 ) THD_const_detrend (nt,par,NULL) ; else if( polort > 0 ) THD_linear_detrend(nt,par,NULL,NULL) ; } nref += ny ; } DESTROY_IMARR(ortar) ; } if( !do_bpass ){ /* old style ort-ification */ MRI_IMAGE *imq , *imp ; float *qar ; INFO_message("Detrending data vectors") ; #if 1 imq = mri_new( nt , nref , MRI_float) ; qar = MRI_FLOAT_PTR(imq) ; for( kk=0 ; kk < nref ; kk++ ) memcpy( qar+kk*nt , polref[kk] , sizeof(float)*nt ) ; imp = mri_matrix_psinv( imq , NULL , 1.e-8 ) ; for( kk=0 ; kk < IMARR_COUNT(imar) ; kk++ ){ mri_matrix_detrend( IMARR_SUBIM(imar,kk) , imq , imp ) ; } mri_free(imp) ; mri_free(imq) ; #else for( kk=0 ; kk < IMARR_COUNT(imar) ; kk++ ){ tsar = MRI_FLOAT_PTR(IMARR_SUBIM(imar,kk)) ; THD_generic_detrend_LSQ( nt , tsar , -1 , nref , polref , NULL ) ; } #endif } else { /* bandpass plus (maybe) orts */ float **vec = (float **)malloc(sizeof(float *)*IMARR_COUNT(imar)) ; INFO_message("Bandpassing data vectors") ; for( kk=0 ; kk < IMARR_COUNT(imar) ; kk++ ) vec[kk] = MRI_FLOAT_PTR(IMARR_SUBIM(imar,kk)) ; (void)THD_bandpass_vectors( nt , IMARR_COUNT(imar) , vec , dtime , bpass_L , bpass_H , 2 , nref , polref ) ; free(vec) ; } for( kk=0 ; kk < nref; kk++ ) free(polref[kk]) ; free(polref) ; } /* end of detrendization */ /*--- the actual work ---*/ INFO_message("Computing SVD") ; pim = mri_principal_vector( imar ) ; DESTROY_IMARR(imar) ; if( pim == NULL ) ERROR_exit("SVD failure!?!") ; ev = mri_principal_getev() ; switch(sval_itop+1){ case 1: INFO_message("First singular value: %g",ev[0]) ; break ; case 2: INFO_message("First 2 singular values: %g %g",ev[0],ev[1]) ; break ; case 3: INFO_message("First 3 singular values: %g %g %g",ev[0],ev[1],ev[2]) ; break ; case 4: INFO_message("First 4 singular values: %g %g %g %g",ev[0],ev[1],ev[2],ev[3]) ; break ; default: case 5: INFO_message("First 5 singular values: %g %g %g %g %g",ev[0],ev[1],ev[2],ev[3],ev[4]) ; break ; } mri_write_1D(NULL,pim) ; exit(0) ; }
int main( int argc , char *argv[] ) { THD_3dim_dataset *yset=NULL , *aset=NULL , *mset=NULL , *wset=NULL ; MRI_IMAGE *fim=NULL, *qim,*tim, *pfim=NULL , *vim , *wim=NULL ; float *flar , *qar,*tar, *par=NULL , *var , *war=NULL ; MRI_IMARR *fimar=NULL ; MRI_IMAGE *aim , *yim ; float *aar , *yar ; int nt=0 , nxyz=0 , nvox=0 , nparam=0 , nqbase , polort=0 , ii,jj,kk,bb ; byte *mask=NULL ; int nmask=0 , iarg ; char *fname_out="-" ; /** equiv to stdout **/ float alpha=0.0f ; int nfir =0 ; float firwt[5]={0.09f,0.25f,0.32f,0.25f,0.09f} ; int nmed =0 ; int nwt =0 ; #define METHOD_C 3 #define METHOD_K 11 int method = METHOD_C ; /**--- help the pitiful user? ---**/ if( argc < 2 || strcmp(argv[1],"-help") == 0 ){ printf( "Usage: 3dInvFMRI [options]\n" "Program to compute stimulus time series, given a 3D+time dataset\n" "and an activation map (the inverse of the usual FMRI analysis problem).\n" "-------------------------------------------------------------------\n" "OPTIONS:\n" "\n" " -data yyy =\n" " *OR* = Defines input 3D+time dataset [a non-optional option].\n" " -input yyy =\n" "\n" " -map aaa = Defines activation map; 'aaa' should be a bucket dataset,\n" " each sub-brick of which defines the beta weight map for\n" " an unknown stimulus time series [also non-optional].\n" "\n" " -mapwt www = Defines a weighting factor to use for each element of\n" " the map. The dataset 'www' can have either 1 sub-brick,\n" " or the same number as in the -map dataset. In the\n" " first case, in each voxel, each sub-brick of the map\n" " gets the same weight in the least squares equations.\n" " [default: all weights are 1]\n" "\n" " -mask mmm = Defines a mask dataset, to restrict input voxels from\n" " -data and -map. [default: all voxels are used]\n" "\n" " -base fff = Each column of the 1D file 'fff' defines a baseline time\n" " series; these columns should be the same length as\n" " number of time points in 'yyy'. Multiple -base options\n" " can be given.\n" " -polort pp = Adds polynomials of order 'pp' to the baseline collection.\n" " The default baseline model is '-polort 0' (constant).\n" " To specify no baseline model at all, use '-polort -1'.\n" "\n" " -out vvv = Name of 1D output file will be 'vvv'.\n" " [default = '-', which is stdout; probably not good]\n" "\n" " -method M = Determines the method to use. 'M' is a single letter:\n" " -method C = least squares fit to data matrix Y [default]\n" " -method K = least squares fit to activation matrix A\n" "\n" " -alpha aa = Set the 'alpha' factor to 'aa'; alpha is used to penalize\n" " large values of the output vectors. Default is 0.\n" " A large-ish value for alpha would be 0.1.\n" "\n" " -fir5 = Smooth the results with a 5 point lowpass FIR filter.\n" " -median5 = Smooth the results with a 5 point median filter.\n" " [default: no smoothing; only 1 of these can be used]\n" "-------------------------------------------------------------------\n" "METHODS:\n" " Formulate the problem as\n" " Y = V A' + F C' + errors\n" " where Y = data matrix (N x M) [from -data]\n" " V = stimulus (N x p) [to -out]\n" " A = map matrix (M x p) [from -map]\n" " F = baseline matrix (N x q) [from -base and -polort]\n" " C = baseline weights (M x q) [not computed]\n" " N = time series length = length of -data file\n" " M = number of voxels in mask\n" " p = number of stimulus time series to estimate\n" " = number of parameters in -map file\n" " q = number of baseline parameters\n" " and ' = matrix transpose operator\n" " Next, define matrix Z (Y detrended relative to columns of F) by\n" " -1\n" " Z = [I - F(F'F) F'] Y\n" "-------------------------------------------------------------------\n" " The method C solution is given by\n" " -1\n" " V0 = Z A [A'A]\n" "\n" " This solution minimizes the sum of squares over the N*M elements\n" " of the matrix Y - V A' + F C' (N.B.: A' means A-transpose).\n" "-------------------------------------------------------------------\n" " The method K solution is given by\n" " -1 -1\n" " W = [Z Z'] Z A and then V = W [W'W]\n" "\n" " This solution minimizes the sum of squares of the difference between\n" " the A(V) predicted from V and the input A, where A(V) is given by\n" " -1\n" " A(V) = Z' V [V'V] = Z'W\n" "-------------------------------------------------------------------\n" " Technically, the solution is unidentfiable up to an arbitrary\n" " multiple of the columns of F (i.e., V = V0 + F G, where G is\n" " an arbitrary q x p matrix); the solution above is the solution\n" " that is orthogonal to the columns of F.\n" "\n" "-- RWCox - March 2006 - purely for experimental purposes!\n" ) ; printf("\n" "===================== EXAMPLE USAGE =====================================\n" "** Step 1: From a training dataset, generate activation map.\n" " The input dataset has 4 runs, each 108 time points long. 3dDeconvolve\n" " is used on the first 3 runs (time points 0..323) to generate the\n" " activation map. There are two visual stimuli (Complex and Simple).\n" "\n" " 3dDeconvolve -x1D xout_short_two.1D -input rall_vr+orig'[0..323]' \\\n" " -num_stimts 2 \\\n" " -stim_file 1 hrf_complex.1D -stim_label 1 Complex \\\n" " -stim_file 2 hrf_simple.1D -stim_label 2 Simple \\\n" " -concat '1D:0,108,216' \\\n" " -full_first -fout -tout \\\n" " -bucket func_ht2_short_two -cbucket cbuc_ht2_short_two\n" "\n" " N.B.: You may want to de-spike, smooth, and register the 3D+time\n" " dataset prior to the analysis (as usual). These steps are not\n" " shown here -- I'm presuming you know how to use AFNI already.\n" "\n" "** Step 2: Create a mask of highly activated voxels.\n" " The F statistic threshold is set to 30, corresponding to a voxel-wise\n" " p = 1e-12 = very significant. The mask is also lightly clustered, and\n" " restricted to brain voxels.\n" "\n" " 3dAutomask -prefix Amask rall_vr+orig\n" " 3dcalc -a 'func_ht2_short+orig[0]' -b Amask+orig -datum byte \\\n" " -nscale -expr 'step(a-30)*b' -prefix STmask300\n" " 3dmerge -dxyz=1 -1clust 1.1 5 -prefix STmask300c STmask300+orig\n" "\n" "** Step 3: Run 3dInvFMRI to estimate the stimulus functions in run #4.\n" " Run #4 is time points 324..431 of the 3D+time dataset (the -data\n" " input below). The -map input is the beta weights extracted from\n" " the -cbucket output of 3dDeconvolve.\n" "\n" " 3dInvFMRI -mask STmask300c+orig \\\n" " -data rall_vr+orig'[324..431]' \\\n" " -map cbuc_ht2_short_two+orig'[6..7]' \\\n" " -polort 1 -alpha 0.01 -median5 -method K \\\n" " -out ii300K_short_two.1D\n" "\n" " 3dInvFMRI -mask STmask300c+orig \\\n" " -data rall_vr+orig'[324..431]' \\\n" " -map cbuc_ht2_short_two+orig'[6..7]' \\\n" " -polort 1 -alpha 0.01 -median5 -method C \\\n" " -out ii300C_short_two.1D\n" "\n" "** Step 4: Plot the results, and get confused.\n" "\n" " 1dplot -ynames VV KK CC -xlabel Run#4 -ylabel ComplexStim \\\n" " hrf_complex.1D'{324..432}' \\\n" " ii300K_short_two.1D'[0]' \\\n" " ii300C_short_two.1D'[0]'\n" "\n" " 1dplot -ynames VV KK CC -xlabel Run#4 -ylabel SimpleStim \\\n" " hrf_simple.1D'{324..432}' \\\n" " ii300K_short_two.1D'[1]' \\\n" " ii300C_short_two.1D'[1]'\n" "\n" " N.B.: I've found that method K works better if MORE voxels are\n" " included in the mask (lower threshold) and method C if\n" " FEWER voxels are included. The above threshold gave 945\n" " voxels being used to determine the 2 output time series.\n" "=========================================================================\n" ) ; PRINT_COMPILE_DATE ; exit(0) ; } /**--- bureaucracy ---**/ mainENTRY("3dInvFMRI main"); machdep(); PRINT_VERSION("3dInvFMRI"); AUTHOR("Zhark"); AFNI_logger("3dInvFMRI",argc,argv) ; /**--- scan command line ---**/ iarg = 1 ; while( iarg < argc ){ if( strcmp(argv[iarg],"-method") == 0 ){ switch( argv[++iarg][0] ){ default: WARNING_message("Ignoring illegal -method '%s'",argv[iarg]) ; break ; case 'C': method = METHOD_C ; break ; case 'K': method = METHOD_K ; break ; } iarg++ ; continue ; } if( strcmp(argv[iarg],"-fir5") == 0 ){ if( nmed > 0 ) WARNING_message("Ignoring -fir5 in favor of -median5") ; else nfir = 5 ; iarg++ ; continue ; } if( strcmp(argv[iarg],"-median5") == 0 ){ if( nfir > 0 ) WARNING_message("Ignoring -median5 in favor of -fir5") ; else nmed = 5 ; iarg++ ; continue ; } if( strcmp(argv[iarg],"-alpha") == 0 ){ alpha = (float)strtod(argv[++iarg],NULL) ; if( alpha <= 0.0f ){ alpha = 0.0f ; WARNING_message("-alpha '%s' ignored!",argv[iarg]) ; } iarg++ ; continue ; } if( strcmp(argv[iarg],"-data") == 0 || strcmp(argv[iarg],"-input") == 0 ){ if( yset != NULL ) ERROR_exit("Can't input 2 3D+time datasets") ; yset = THD_open_dataset(argv[++iarg]) ; CHECK_OPEN_ERROR(yset,argv[iarg]) ; nt = DSET_NVALS(yset) ; if( nt < 2 ) ERROR_exit("Only 1 sub-brick in dataset %s",argv[iarg]) ; nxyz = DSET_NVOX(yset) ; iarg++ ; continue ; } if( strcmp(argv[iarg],"-map") == 0 ){ if( aset != NULL ) ERROR_exit("Can't input 2 -map datasets") ; aset = THD_open_dataset(argv[++iarg]) ; CHECK_OPEN_ERROR(aset,argv[iarg]) ; nparam = DSET_NVALS(aset) ; iarg++ ; continue ; } if( strcmp(argv[iarg],"-mapwt") == 0 ){ if( wset != NULL ) ERROR_exit("Can't input 2 -mapwt datasets") ; wset = THD_open_dataset(argv[++iarg]) ; CHECK_OPEN_ERROR(wset,argv[iarg]) ; iarg++ ; continue ; } if( strcmp(argv[iarg],"-mask") == 0 ){ if( mset != NULL ) ERROR_exit("Can't input 2 -mask datasets") ; mset = THD_open_dataset(argv[++iarg]) ; CHECK_OPEN_ERROR(mset,argv[iarg]) ; iarg++ ; continue ; } if( strcmp(argv[iarg],"-polort") == 0 ){ char *cpt ; polort = (int)strtod(argv[++iarg],&cpt) ; if( *cpt != '\0' ) WARNING_message("Illegal non-numeric value after -polort") ; iarg++ ; continue ; } if( strcmp(argv[iarg],"-out") == 0 ){ fname_out = strdup(argv[++iarg]) ; if( !THD_filename_ok(fname_out) ) ERROR_exit("Bad -out filename '%s'",fname_out) ; iarg++ ; continue ; } if( strcmp(argv[iarg],"-base") == 0 ){ if( fimar == NULL ) INIT_IMARR(fimar) ; qim = mri_read_1D( argv[++iarg] ) ; if( qim == NULL ) ERROR_exit("Can't read 1D file %s",argv[iarg]) ; ADDTO_IMARR(fimar,qim) ; iarg++ ; continue ; } ERROR_exit("Unrecognized option '%s'",argv[iarg]) ; } /**--- finish up processing options ---**/ if( yset == NULL ) ERROR_exit("No input 3D+time dataset?!") ; if( aset == NULL ) ERROR_exit("No input FMRI -map dataset?!") ; if( DSET_NVOX(aset) != nxyz ) ERROR_exit("Grid mismatch between -data and -map") ; INFO_message("Loading dataset for Y") ; DSET_load(yset); CHECK_LOAD_ERROR(yset) ; INFO_message("Loading dataset for A") ; DSET_load(aset); CHECK_LOAD_ERROR(aset) ; if( wset != NULL ){ if( DSET_NVOX(wset) != nxyz ) ERROR_exit("Grid mismatch between -data and -mapwt") ; nwt = DSET_NVALS(wset) ; if( nwt > 1 && nwt != nparam ) ERROR_exit("Wrong number of values=%d in -mapwt; should be 1 or %d", nwt , nparam ) ; INFO_message("Loading dataset for mapwt") ; DSET_load(wset); CHECK_LOAD_ERROR(wset) ; } if( mset != NULL ){ if( DSET_NVOX(mset) != nxyz ) ERROR_exit("Grid mismatch between -data and -mask") ; INFO_message("Loading dataset for mask") ; DSET_load(mset); CHECK_LOAD_ERROR(mset) ; mask = THD_makemask( mset , 0 , 1.0f,-1.0f ); DSET_delete(mset); nmask = THD_countmask( nxyz , mask ) ; if( nmask < 3 ){ WARNING_message("Mask has %d voxels -- ignoring!",nmask) ; free(mask) ; mask = NULL ; nmask = 0 ; } } nvox = (nmask > 0) ? nmask : nxyz ; INFO_message("N = time series length = %d",nt ) ; INFO_message("M = number of voxels = %d",nvox ) ; INFO_message("p = number of params = %d",nparam) ; /**--- set up baseline funcs in one array ---*/ nqbase = (polort >= 0 ) ? polort+1 : 0 ; if( fimar != NULL ){ for( kk=0 ; kk < IMARR_COUNT(fimar) ; kk++ ){ qim = IMARR_SUBIMAGE(fimar,kk) ; if( qim != NULL && qim->nx != nt ) WARNING_message("-base #%d length=%d; data length=%d",kk+1,qim->nx,nt) ; nqbase += qim->ny ; } } INFO_message("q = number of baselines = %d",nqbase) ; #undef F #define F(i,j) flar[(i)+(j)*nt] /* nt X nqbase */ if( nqbase > 0 ){ fim = mri_new( nt , nqbase , MRI_float ) ; /* F matrix */ flar = MRI_FLOAT_PTR(fim) ; bb = 0 ; if( polort >= 0 ){ /** load polynomial baseline **/ double a = 2.0/(nt-1.0) ; for( jj=0 ; jj <= polort ; jj++ ){ for( ii=0 ; ii < nt ; ii++ ) F(ii,jj) = (float)Plegendre( a*ii-1.0 , jj ) ; } bb = polort+1 ; } #undef Q #define Q(i,j) qar[(i)+(j)*qim->nx] /* qim->nx X qim->ny */ if( fimar != NULL ){ /** load -base baseline columns **/ for( kk=0 ; kk < IMARR_COUNT(fimar) ; kk++ ){ qim = IMARR_SUBIMAGE(fimar,kk) ; qar = MRI_FLOAT_PTR(qim) ; for( jj=0 ; jj < qim->ny ; jj++ ){ for( ii=0 ; ii < nt ; ii++ ) F(ii,bb+jj) = (ii < qim->nx) ? Q(ii,jj) : 0.0f ; } bb += qim->ny ; } DESTROY_IMARR(fimar) ; fimar=NULL ; } /* remove mean from each column after first? */ if( polort >= 0 && nqbase > 1 ){ float sum ; for( jj=1 ; jj < nqbase ; jj++ ){ sum = 0.0f ; for( ii=0 ; ii < nt ; ii++ ) sum += F(ii,jj) ; sum /= nt ; for( ii=0 ; ii < nt ; ii++ ) F(ii,jj) -= sum ; } } /* compute pseudo-inverse of baseline matrix, so we can project it out from the data time series */ /* -1 */ /* (F'F) F' matrix */ INFO_message("Computing pseudo-inverse of baseline matrix F") ; pfim = mri_matrix_psinv(fim,NULL,0.0f) ; par = MRI_FLOAT_PTR(pfim) ; #undef P #define P(i,j) par[(i)+(j)*nqbase] /* nqbase X nt */ #if 0 qim = mri_matrix_transpose(pfim) ; /** save to disk? **/ mri_write_1D( "Fpsinv.1D" , qim ) ; mri_free(qim) ; #endif } /**--- set up map image into aim/aar = A matrix ---**/ #undef GOOD #define GOOD(i) (mask==NULL || mask[i]) #undef A #define A(i,j) aar[(i)+(j)*nvox] /* nvox X nparam */ INFO_message("Loading map matrix A") ; aim = mri_new( nvox , nparam , MRI_float ); aar = MRI_FLOAT_PTR(aim); for( jj=0 ; jj < nparam ; jj++ ){ for( ii=kk=0 ; ii < nxyz ; ii++ ){ if( GOOD(ii) ){ A(kk,jj) = THD_get_voxel(aset,ii,jj); kk++; } }} DSET_unload(aset) ; /**--- set up map weight into wim/war ---**/ #undef WT #define WT(i,j) war[(i)+(j)*nvox] /* nvox X nparam */ if( wset != NULL ){ int numneg=0 , numpos=0 ; float fac ; INFO_message("Loading map weight matrix") ; wim = mri_new( nvox , nwt , MRI_float ) ; war = MRI_FLOAT_PTR(wim) ; for( jj=0 ; jj < nwt ; jj++ ){ for( ii=kk=0 ; ii < nxyz ; ii++ ){ if( GOOD(ii) ){ WT(kk,jj) = THD_get_voxel(wset,ii,jj); if( WT(kk,jj) > 0.0f ){ numpos++; WT(kk,jj) = sqrt(WT(kk,jj)); } else if( WT(kk,jj) < 0.0f ){ numneg++; WT(kk,jj) = 0.0f; } kk++; } }} DSET_unload(wset) ; if( numpos <= nparam ) WARNING_message("Only %d positive weights found in -wtmap!",numpos) ; if( numneg > 0 ) WARNING_message("%d negative weights found in -wtmap!",numneg) ; for( jj=0 ; jj < nwt ; jj++ ){ fac = 0.0f ; for( kk=0 ; kk < nvox ; kk++ ) if( WT(kk,jj) > fac ) fac = WT(kk,jj) ; if( fac > 0.0f ){ fac = 1.0f / fac ; for( kk=0 ; kk < nvox ; kk++ ) WT(kk,jj) *= fac ; } } } /**--- set up data image into yim/yar = Y matrix ---**/ #undef Y #define Y(i,j) yar[(i)+(j)*nt] /* nt X nvox */ INFO_message("Loading data matrix Y") ; yim = mri_new( nt , nvox , MRI_float ); yar = MRI_FLOAT_PTR(yim); for( ii=0 ; ii < nt ; ii++ ){ for( jj=kk=0 ; jj < nxyz ; jj++ ){ if( GOOD(jj) ){ Y(ii,kk) = THD_get_voxel(yset,jj,ii); kk++; } }} DSET_unload(yset) ; /**--- project baseline out of data image = Z matrix ---**/ if( pfim != NULL ){ #undef T #define T(i,j) tar[(i)+(j)*nt] /* nt X nvox */ INFO_message("Projecting baseline out of Y") ; qim = mri_matrix_mult( pfim , yim ) ; /* nqbase X nvox */ tim = mri_matrix_mult( fim , qim ) ; /* nt X nvox */ tar = MRI_FLOAT_PTR(tim) ; /* Y projected onto baseline */ for( jj=0 ; jj < nvox ; jj++ ) for( ii=0 ; ii < nt ; ii++ ) Y(ii,jj) -= T(ii,jj) ; mri_free(tim); mri_free(qim); mri_free(pfim); mri_free(fim); } /***** At this point: matrix A is in aim, matrix Z is in yim. Solve for V into vim, using the chosen method *****/ switch( method ){ default: ERROR_exit("Illegal method code! WTF?") ; /* Huh? */ /*.....................................................................*/ case METHOD_C: /**--- compute pseudo-inverse of A map ---**/ INFO_message("Method C: Computing pseudo-inverse of A") ; if( wim != NULL ) WARNING_message("Ignoring -mapwt dataset") ; pfim = mri_matrix_psinv(aim,NULL,alpha) ; /* nparam X nvox */ if( pfim == NULL ) ERROR_exit("mri_matrix_psinv() fails") ; mri_free(aim) ; /**--- and apply to data to get results ---*/ INFO_message("Computing result V") ; vim = mri_matrix_multranB( yim , pfim ) ; /* nt x nparam */ mri_free(pfim) ; mri_free(yim) ; break ; /*.....................................................................*/ case METHOD_K: /**--- compute pseudo-inverse of transposed Z ---*/ INFO_message("Method K: Computing pseudo-inverse of Z'") ; if( nwt > 1 ){ WARNING_message("Ignoring -mapwt dataset: more than 1 sub-brick") ; nwt = 0 ; mri_free(wim) ; wim = NULL ; war = NULL ; } if( nwt == 1 ){ float fac ; for( kk=0 ; kk < nvox ; kk++ ){ fac = war[kk] ; for( ii=0 ; ii < nt ; ii++ ) Y(ii,kk) *= fac ; for( ii=0 ; ii < nparam ; ii++ ) A(kk,ii) *= fac ; } } tim = mri_matrix_transpose(yim) ; mri_free(yim) ; pfim = mri_matrix_psinv(tim,NULL,alpha) ; mri_free(tim) ; if( pfim == NULL ) ERROR_exit("mri_matrix_psinv() fails") ; INFO_message("Computing W") ; tim = mri_matrix_mult( pfim , aim ) ; mri_free(aim) ; mri_free(pfim) ; INFO_message("Computing result V") ; pfim = mri_matrix_psinv(tim,NULL,0.0f) ; mri_free(tim) ; vim = mri_matrix_transpose(pfim) ; mri_free(pfim); break ; } /* end of switch on method */ if( wim != NULL ) mri_free(wim) ; /**--- smooth? ---**/ if( nfir > 0 && vim->nx > nfir ){ INFO_message("FIR-5-ing result") ; var = MRI_FLOAT_PTR(vim) ; for( jj=0 ; jj < vim->ny ; jj++ ) linear_filter_reflect( nfir,firwt , vim->nx , var + (jj*vim->nx) ) ; } if( nmed > 0 && vim->nx > nmed ){ INFO_message("Median-5-ing result") ; var = MRI_FLOAT_PTR(vim) ; for( jj=0 ; jj < vim->ny ; jj++ ) median5_filter_reflect( vim->nx , var + (jj*vim->nx) ) ; } /**--- write results ---**/ INFO_message("Writing result to '%s'",fname_out) ; mri_write_1D( fname_out , vim ) ; exit(0) ; }
int THD_bandpass_vectors( int nlen , int nvec , float **vec , float dt , float fbot , float ftop , int qdet , int nort , float **ort ) { int nfft,nby2 , iv, jbot,jtop , ndof=0 ; register int jj ; float df , tapr ; register float *xar, *yar=NULL ; register complex *zar ; complex Zero={0.0f,0.0f} ; ENTRY("THD_bandpass_vectors") ; if( ftop > ICOR_MAX_FTOP && qdet < 0 && (nort <= 0 || ort == NULL) ) RETURN(ndof) ; /* 26 Feb 2010: do nothing at all? */ if( nlen < 9 || nvec < 1 || vec == NULL ){ ERROR_message("bad bandpass data?"); RETURN(ndof); } if( bpwrn && dt > 60.0f ) { WARNING_message("Your bandpass timestep (%f) is high.\n" " Make sure units are 'sec', not 'msec'.\n" " This warning will not be repeated." , dt); bpwrn = 0; } if( dt <= 0.0f ) dt = 1.0f ; if( fbot < 0.0f ) fbot = 0.0f ; if( ftop <= fbot ){ ERROR_message("Bad bandpass frequencies?"); RETURN(ndof); } if( nort >= nlen ){ ERROR_message("Too many bandpass orts?") ; RETURN(ndof); } /** setup for FFT **/ nfft = (nfft_fixed >= nlen) ? nfft_fixed : csfft_nextup_even(nlen) ; nby2 = nfft/2 ; df = 1.0f / (nfft * dt) ; /* frequency resolution */ jbot = (int)rint(fbot/df) ; /* closest freq index to fbot */ jtop = (int)rint(ftop/df) ; /* and to ftop */ if( jtop >= nby2 ) jtop = nby2-1 ; /* can't go past Nyquist! */ if( jbot >= jtop+1 ){ ERROR_message("bandpass: fbot and ftop too close ==> " "jbot=%d jtop=%d (df=%f)", jbot,jtop, df) ; RETURN(ndof) ; } /** quadratic detrending first? (should normally be used) **/ switch( qdet ){ case 2: ndof += 2 ; for( iv=0 ; iv < nvec ; iv++ ) THD_quadratic_detrend( nlen, vec[iv], NULL,NULL,NULL ) ; break ; case 1: ndof += 1 ; for( iv=0 ; iv < nvec ; iv++ ) THD_linear_detrend( nlen, vec[iv], NULL,NULL ) ; break ; case 0: for( iv=0 ; iv < nvec ; iv++ ) THD_const_detrend( nlen, vec[iv], NULL ) ; break ; } if( qdet > 0 || jbot > 0 || jtop < nby2-1 ){ /* 26 Feb 2010: do the FFTs */ zar = (complex *)malloc(sizeof(complex)*nfft) ; /* work array */ csfft_scale_inverse(1) ; /* scale inverse FFT by 1/nfft */ /** loop over vectors in pairs, FFT-ing and bandpassing **/ ndof += 2 ; /* for 0 and Nyquist freqs */ if( jbot >= 1 ) ndof += 2*jbot - 1 ; /* DOF for low freq */ ndof += 2*(nby2-jtop) - 1 ; /* DOF for high freq */ for( iv=0 ; iv < nvec ; iv+=2 ){ /* load a pair of vectors into zar to double up on FFTs of real data */ xar = vec[iv] ; if( iv == nvec-1 ){ /* last one has nothing to pair with */ for( jj=0 ; jj < nlen ; jj++ ){ zar[jj].r = xar[jj] ; zar[jj].i = 0.0f ; } } else { yar = vec[iv+1] ; for( jj=0 ; jj < nlen ; jj++ ){ zar[jj].r = xar[jj] ; zar[jj].i = yar[jj] ; } } for( jj=nlen ; jj < nfft ; jj++ ) zar[jj] = Zero ; /* zero fill */ csfft_cox( -1 , nfft , zar ) ; /*** the FFT ***/ /* delete unwanted frequencies */ zar[0] = zar[nby2] = Zero ; tapr = (nort > 0 && ort != NULL) ? 0.05f : 0.5f ; if( jbot >= 1 ){ zar[jbot].r *= tapr ; zar[jbot].i *= tapr ; zar[nfft-jbot].r *= tapr ; zar[nfft-jbot].i *= tapr ; for( jj=1 ; jj < jbot ; jj++ ) zar[jj] = zar[nfft-jj] = Zero ; } zar[jtop].r *= tapr ; zar[jtop].i *= tapr ; zar[nfft-jtop].r *= tapr ; zar[nfft-jtop].i *= tapr ; for( jj=jtop+1 ; jj < nby2 ; jj++ ) zar[jj] = zar[nfft-jj] = Zero ; csfft_cox( 1 , nfft , zar ) ; /*** inverse FFT ***/ /* unload vector pair back into original data arrays */ if( iv == nvec-1 ){ for( jj=0 ; jj < nlen ; jj++ ) xar[jj] = zar[jj].r ; } else { for( jj=0 ; jj < nlen ; jj++ ){ xar[jj] = zar[jj].r ; yar[jj] = zar[jj].i ; } } } /* end of loop over vector pairs: bandpassing now done */ free(zar) ; csfft_scale_inverse(0) ; } /* end of FFTs */ /*** remove orts? ***/ if( nort > 0 && ort != NULL ){ float **qort = (float **)malloc(sizeof(float *)*nort) ; MRI_IMAGE *qim , *pim ; float *par, *qar , *rar , *pt,*qt ; register float sum , xt ; register int kk ; /* must bandpass copy of orts first -- via recursion (so we don't re-introduce any of the removed frequencies) */ qim = mri_new( nlen , nort , MRI_float ) ; /** [Q] = nlen X nort **/ qar = MRI_FLOAT_PTR(qim) ; /** load with the orts **/ for( iv=0 ; iv < nort ; iv++ ){ qort[iv] = qar + iv*nlen ; memcpy( qort[iv] , ort[iv] , sizeof(float)*nlen ) ; } (void)THD_bandpass_vectors( nlen, nort, qort, dt, fbot, ftop, qdet, 0,NULL ) ; free(qort) ; /* compute pseudo-inverse ([P] = inv{[Q]'[Q]}[Q]') of bandpassed orts */ pim = mri_matrix_psinv( qim , NULL , 1.e-8 ) ; /** [P] = nort X nlen **/ if( pim == NULL ){ /* should not happen */ mri_free(qim) ; ERROR_message("can't remove bandpass orts?") ; RETURN(ndof) ; } par = MRI_FLOAT_PTR(pim) ; /* nort X nlen matrix */ /* Project the bandpassed orts out of data vectors: for each vector [y], replace it with [y] - [Q][P][y] ; this is more efficient than computing the nlen X nlen projection matrix [I]-[Q][P] and then applying to each vector, since that would require about nlen*nlen flops per vector, whereas the 2 matrix multiplications by [P] then [Q] will require about 2*nlen*nort flops per vector, a clear win if nort < nlen/2. */ rar = (float *)malloc(sizeof(float)*nort) ; /* will hold [P][y] */ for( iv=0 ; iv < nvec ; iv++ ){ xar = vec[iv] ; /* [y] vector */ /* compute nort-vector [r] = [P][y] */ for( jj=0 ; jj < nort ; jj++ ) rar[jj] = 0.0f ; /* initialize to 0 */ for( kk=0 ; kk < nlen ; kk++ ){ pt = par + kk*nort ; xt = xar[kk] ; for( jj=0 ; jj < nort ; jj++ ) rar[jj] += pt[jj]*xt ; } /* now subtract [Q][r] from [y] */ for( jj=0 ; jj < nort ; jj++ ){ qt = qar + jj*nlen ; xt = rar[jj] ; for( kk=0 ; kk < nlen ; kk++ ) xar[kk] -= qt[kk]*xt ; } } free(rar) ; mri_free(pim) ; mri_free(qim) ; ndof += nort ; } /* de-ortification is done */ /** done **/ if( nfft > nlen ){ double fac = ((double)nlen)/(double)nfft ; ndof = (int)rint(fac*ndof) ; } RETURN(ndof) ; }