Skip to content
Snippets Groups Projects
fsl_sbca.cc 15.40 KiB
/*  fsl_glm - 

    Christian F. Beckmann, FMRIB Image Analysis Group

    Copyright (C) 2008 University of Oxford  */

/*  CCOPYRIGHT  */

#include "libvis/miscplot.h"
#include "miscmaths/miscmaths.h"
#include "miscmaths/miscprob.h"
#include "utils/options.h"
#include <vector>
#include "newimage/newimageall.h"
#include "melhlprfns.h"

using namespace MISCPLOT;
using namespace MISCMATHS;
using namespace Utilities;
using namespace std;

// The two strings below specify the title and example usage that is
// printed out as the help or usage message

  string title=string("fsl_sbca (Version 1.0)")+
		string("\nCopyright(c) 2008, University of Oxford (Christian F. Beckmann)\n")+
		string(" \n Performs seed-based correlation analysis on FMRI data\n")+
		string(" using either a single seed coordinate or a seed mask \n")+
		string(" ");
  string examples="fsl_sbca -i <input> -s <seed> -t <target> -o <basename> [options]";

//Command line Options {
  Option<string> fnin(string("-i,--in"), string(""),
		string("        input file name (4D image file)"),
		true, requires_argument);
  Option<string> fnout(string("-o,--out"), string(""),
		string("output file base name"),
		true, requires_argument);
  Option<string> fnseed(string("-s,--seed"), string(""),
		string("seed voxel coordinate or file name of seed mask (3D/4D file)"),
		true, requires_argument);
  Option<string> fntarget(string("-t,--target"), string(""),
		string("file name of target mask(s) (3D or 4D file)"),
		true, requires_argument);
  Option<bool> regress_only(string("-r,--reg"), false,
		string("perform time series regression rather than classification to targets"),
		false, no_argument);
  Option<string> fnconf(string("--conf"), string(""),
		string("        file name (or comma-separated list of file name) for confound ascii txt files"),
		false, requires_argument);
  Option<string> fnseeddata(string("--seeddata"), string(""),
		string("file name of 4D data file for the seed"),
		false, requires_argument);		
  Option<bool> map_bin(string("--bin"), false,
		string("        binarise spatial maps prior to calculation of time courses"),
		false, no_argument);
  Option<bool> verbose(string("-v,--verbose"), false,
		string("switch on diagnostic messages"),
		false, no_argument);
  Option<bool> tc_mean(string("--mean"), false,
		string("        use mean instead of Eigenvariates for calculation of time courses"),
		false, no_argument);
  Option<int> tc_order(string("--order"), 1,
		string("        number of Eigenvariates (default 1)"),
		false, requires_argument);	
  Option<bool> abscc(string("--abscc"), false,
		string("        use maximum absolute value instead of of maximum value of the cross-correlations"),
		false, no_argument);			
  Option<bool> out_seeds(string("--out_seeds"), false,
		string("output seed mask image as <basename>_seeds"),
		false, no_argument);
  Option<bool> out_seedmask(string("--out_seedmask"), false,
		string("output seed mask image as <basename>_seedmask"),
		false, no_argument);
  Option<bool> out_ttcs(string("--out_ttcs"), false,
		string("output target time courses as <basename>_ttc<X>.txt"),
		false, no_argument);
  Option<bool> out_conf(string("--out_conf"), false,
		string("output confound time courses as <basename>_confounds.txt"),
		false, no_argument);
  Option<bool> out_tcorr(string("--out_tcorr"), false,
		string("output target correlations as <basename>_tcorr.txt"),
		false, no_argument, false);
  Option<int> help(string("-h,--help"), 0,
		string("display this help text"),
		false,no_argument);
  Option<bool> debug(string("--debug"), false,
		string("        switch on debug messages"),
		false, no_argument, false);
		/*
}
*/
//Globals {
	Matrix data, confounds;
	volume4D<float> orig_data;
	volume<float> maskS, maskT; 
	int voxels = 0;
	Matrix seeds, coords;
	vector<Matrix> ttcs;
	
	Matrix out1, out2;
	 /*
}
*/
////////////////////////////////////////////////////////////////////////////

// Local functions
void save4D(Matrix what, volume<float>& msk, string fname){
  if(debug.value()) 
	cerr << "DBG: in save4D" << endl;
  volume4D<float> tempVol;
  tempVol.setmatrix(what,msk);
  save_volume4D(tempVol,fname);
}

void save4D(volume<float>& in, string fname){
  if(debug.value()) 
	cerr << "DBG: in save4D" << endl;
  volume4D<float> tempVol;
  tempVol.addvolume(in);
  save_volume4D(tempVol,fname);
}

ReturnMatrix create_coords(string what){
  if(debug.value()) 
	cerr << "DBG: in create_coords" << endl;
  Matrix res;
  ifstream fs(what.c_str());
  if (!fs) { 
	Matrix tmp(1,3);
	char *p;
	char t[1024];
	const char *discard = ", [];{(})abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~!@#$%^&*_-=+|\':><./?";	  
	strcpy(t, what.c_str());
	p=strtok(t,discard);
	tmp(1,1) = atoi(p);
	p=strtok(NULL,discard);
	tmp(1,2) = atoi(p);
	p=strtok(NULL,discard);
	tmp(1,3) = atoi(p);
	res = tmp;
	
	do{
	  p=strtok(NULL,discard);
	  if(p){
		tmp(1,1) = atoi(p);
		p=strtok(NULL,discard);
		tmp(1,2) = atoi(p);
		p=strtok(NULL,discard);
		tmp(1,3) = atoi(p);
		res &= tmp;   	
      }
    }while(p);			
  }else{
    res = read_ascii_matrix(fs);
    fs.close();
  }

  if(res.Ncols()!=3){
	cerr << "ERROR: incorrect format " << what << endl;
	exit(1);
  }

 // if(verbose.value())
//	cout << " Created seed coordinates (size: " << res.Nrows() << " x " << res.Ncols() << ")" << endl;
  res.Release();
	
  return res;	
}

void create_mask(string what){
  if(debug.value()) 
	cerr << "DBG: in create_mask" << endl;
  
  coords = create_coords(what);
  maskS = orig_data[0] * 0.0;
  for(int ctr = 1; ctr <= coords.Nrows(); ctr++)
	maskS(coords(ctr,1),coords(ctr,2),coords(ctr,3)) = 1.0;
  maskS.binarise(1e-8);
}	

void create_seeds(string what){
  if(debug.value()) 
	cerr << "DBG: in create_seeds" << endl;


  volume4D<float> tmp_vol;
  if(fsl_imageexists(what)){
	read_volume4D(tmp_vol,what);
	maskS = tmp_vol[0];
    if(!samesize(orig_data[0],maskS)){
	  cerr << "ERROR: Seed mask image does not match input image" << endl;
      exit(1);
    }
  }  
  else
    create_mask(what);	

  if(tmp_vol.tsize() > 1 && tmp_vol.tsize() == orig_data.tsize()){
	maskS *= tmp_vol[0] / tmp_vol.tsize();
	for(int ctr=1; ctr < tmp_vol.tsize(); ctr++)
	  maskS += tmp_vol[ctr] * tmp_vol[ctr] / tmp_vol.tsize(); 
    maskS.binarise(1e-8);
	seeds = remmean(tmp_vol.matrix(maskS),1);
  }
  else{    
    volume4D<float> tmp_mask;
    tmp_mask.addvolume(maskS);
    maskS.binarise(1e-8);
    if(fnseeddata.value()>"" && fsl_imageexists(fnseeddata.value())){
      volume4D<float> seed_data;
	  if(verbose.value())
		cout << " Reading input data for seeds " << fnseeddata.value() << endl;
      read_volume4D(seed_data,fnseeddata.value());
      seeds = remmean(seed_data.matrix(maskS),1);
    }else{
	  seeds = remmean(orig_data.matrix(maskS),1);	
      if(!map_bin.value()){
    	Matrix scales = tmp_mask.matrix(maskS);	
        seeds = SP(seeds, ones(seeds.Nrows(),1) * scales);
      }
    }
  }
	
  voxels = seeds.Ncols();

  if(debug.value()){
	cerr << "DBG: " << voxels << " voxels" << endl;
	cerr << "DBG: seeds matrix is " << seeds.Nrows() << " x " << seeds.Ncols() << endl;
  }

  if(verbose.value())
	cout << " Created seed time courses "  << endl;

}

ReturnMatrix create_confs(string what){
  if(debug.value()) 
	cerr << "DBG: in create_confs" << endl;
  
  Matrix res, tmp;    
  char *p;
  char t[1024];
  const char *discard = ",";

  strcpy(t, what.c_str());
  p=strtok(t,discard);
  res = remmean(read_ascii_matrix(string(p)),1);
  do{
    p=strtok(NULL,discard);
    if(p){
		tmp = read_ascii_matrix(string(p));
		if(tmp.Nrows()!=res.Nrows()){
			cerr << "ERROR: confound matrix" << string(p) << " is of wrong size "<< endl;
			exit(1);
		}
    	res |= remmean(tmp,1);	
    }
  }while(p);

  if(verbose.value())
	cout << " Created confound matrix (size: " << res.Nrows() << " x " << res.Ncols() << ")" << endl;
  res.Release();
  return res;
}

ReturnMatrix calc_ttc(volume<float>& in){
	if(debug.value()) 
	cerr << "DBG: in calc_ttc" << endl;
  
	Matrix res, tmp, scales;
	
	volume<float> tmp1;
	volume4D<float> tmp2;

	tmp1 = in;
	tmp1.binarise(1e-8);
	maskT += tmp1;
	
	tmp2.addvolume(in);	
	scales = tmp2.matrix(tmp1);
	tmp = remmean(orig_data.matrix(tmp1),1);
	
	if(!map_bin.value())
		tmp = SP(tmp, ones(tmp.Nrows(),1) * scales);
	
	if(tc_mean.value())
		res = mean(tmp,2);
	else{
	   	SymmetricMatrix Corr;
		Corr << tmp * tmp.t() / tmp.Ncols();
		DiagonalMatrix tmpD;
	    EigenValues(Corr,tmpD,res);	
		res = fliplr(res.Columns(res.Ncols()-tc_order.value()+1 , res.Ncols())) * std::sqrt(tmp.Nrows());	
		
		Matrix res2 = mean(tmp,2);

		if(debug.value())
			cerr << "DBG: mean size is " << res2.Nrows() << " x " << res2.Ncols() << endl;
		res2 = res2.Column(1).t() * res.Column(1);
		
		if((float)res2.AsScalar() < 0){
			res = -1.0 * res;
			if(debug.value())
				cerr << "DBG: flipping first eigenvariates" << endl;
		}
	}
	
	if(debug.value())
		cerr << "DBG: size is " << res.Nrows() << " x " << res.Ncols() << endl;
	res.Release();
	return res;
}

void create_target_tcs(){
	if(debug.value()) 
	cerr << "DBG: in create_target_tcs" << endl;
  
	volume4D<float> tmptarg;
	read_volume4D(tmptarg,fntarget.value());	
    maskT = orig_data[0] * 0.0;

	for(int ctr=0; ctr < tmptarg.tsize(); ctr++){
	   ttcs.push_back(calc_ttc(tmptarg[ctr]));
	}	
	
	if(debug.value()) {
		cerr << "DBG: " << ttcs.size() << " target matrices created " << endl;
	}
	if(verbose.value())
	  cout << " Created target mask time courses "  << endl;
	
}

int setup(){
  if(debug.value()) 
	cerr << "DBG: in setup" << endl;
  if(fsl_imageexists(fnin.value())){ //read data
	if(verbose.value())
      cout << " Reading input file " << fnin.value() << endl;
    read_volume4D(orig_data,fnin.value());
  }
  else{
	cerr << "ERROR: Invalid input file " << fnin.value() << endl;       
	exit(1);
    }

  create_seeds(fnseed.value());
  if(!regress_only.value())
    create_target_tcs();
  else{
   	volume4D<float> tmptarg;
	read_volume4D(tmptarg,fntarget.value());	
	maskT = tmptarg[0];
	maskT.binarise(1e-8);
	data = orig_data.matrix(maskT);
	data = remmean(data,1);
  }

  if(fnconf.value()>"")
    confounds = create_confs(fnconf.value());

  return 0;	
}

ReturnMatrix calc_tcorr(int in){
  if(debug.value()) 
	cerr << "DBG: in calc_tcorr" << endl;

	Matrix res = zeros(1,seeds.Ncols()), partial_conf, targetcol;
	
	for(int ctr = 0; ctr < (int)ttcs.size(); ctr++)
	  if(ctr != in){
	  	if(partial_conf.Storage() == 0)
	    	partial_conf = ttcs.at(ctr);
	  	else	
    		partial_conf |= ttcs.at(ctr);
      }

    if(ttcs.at(in).Ncols()>1)
      if(partial_conf.Storage()>0)
        partial_conf = ttcs.at(in).Columns(2,ttcs.at(in).Ncols()) | partial_conf;
      else
		partial_conf = ttcs.at(in).Columns(2,ttcs.at(in).Ncols());
		
 	if(confounds.Storage() > 0)
      if(partial_conf.Storage()>0)
        partial_conf |= confounds;	
      else
        partial_conf = confounds;	

    if(debug.value() && partial_conf.Storage()>0) 
      cerr << "DBG: partial_conf " << partial_conf.Nrows() << " x " << partial_conf.Ncols() << endl;

	targetcol = ttcs.at(in).Column(1);
    if(debug.value()) 
      cerr << "DBG: targetcol " << targetcol.Nrows() << " x " << targetcol.Ncols() << endl;

	for(int ctr = 1; ctr <= seeds.Ncols(); ctr++)
      res(1,ctr) = Melodic::corrcoef(targetcol, seeds.Column(ctr), partial_conf).AsScalar();
    
	res.Release();
	return res;	
}

void calc_res(){
  if(debug.value()) 
	cerr << "DBG: in calc_res" << endl;

  out2 = zeros(1,seeds.Ncols());	

  if(!regress_only.value()){
	//Target TCs exist
	if(verbose.value())
	  cout << " Calculating partial correlation scores between seeds and targets "  << endl;

	Matrix tmp;
	int tmp2;
	out1=zeros(ttcs.size(),seeds.Ncols());
	for(int ctr = 0 ;ctr < (int)ttcs.size(); ctr++)
	  out1.Row(ctr+1) = calc_tcorr(ctr);					

    for(int ctr = 1 ;ctr <= out1.Ncols(); ctr++){
	  if(!abscc.value()){
	  	out1.Column(ctr).Maximum1(tmp2);
      	out2(1,ctr) = tmp2;
	  }else
	  {
	  	out1.Column(ctr).MaximumAbsoluteValue1(tmp2);
      	out2(1,ctr) = tmp2;		
	  }
    }
      
	if(debug.value()){ 
      cerr << "DBG: out1 " << out1.Nrows() << " x " << out1.Ncols() << endl;
      cerr << "DBG: out2 " << out2.Nrows() << " x " << out2.Ncols() << endl;
	}
  }
  else{
	//no Target TCs
	if(verbose.value())
	  cout << " Calculating partial correlation maps "  << endl;

	out1 = zeros(seeds.Ncols(), data.Ncols());
	if(confounds.Storage()>0){
	  data = data - confounds * pinv(confounds) * data;
      seeds = seeds - confounds * pinv(confounds) * seeds;
    }

	if(debug.value()){ 
      cerr << "DBG: seeds " << seeds.Nrows() << " x " << seeds.Ncols() << endl;
      cerr << "DBG: data " << data.Nrows() << " x " << data.Ncols() << endl;
    }

	for(int ctr = 1 ;ctr <= seeds.Ncols(); ctr++){
	  Matrix tmp;
	  if(coords.Storage()>0){
	    tmp = orig_data.voxelts(coords(ctr,1), coords(ctr,2), coords(ctr,3));
		volume4D<float> tmpVol;
		tmpVol.setmatrix(out2,maskS);
		tmpVol( coords(ctr,1), coords(ctr,2), coords(ctr,3), 0) = ctr; 
		out2 = tmpVol.matrix(maskS); 
     	if(confounds.Storage()>0)
		  tmp = tmp - confounds * pinv(confounds) * tmp;
	  }
	  else{
		tmp = seeds.Column(ctr);
     	out2(1,ctr) = ctr;
	  }
	  for(int ctr2 =1; ctr2 <= data.Ncols(); ctr2++)
        out1(ctr,ctr2) = Melodic::corrcoef(tmp,data.Column(ctr2)).AsScalar();      
    }

	if(debug.value()){ 
      cerr << "DBG: out1 " << out1.Nrows() << " x " << out1.Ncols() << endl;
      cerr << "DBG: out2 " << out2.Nrows() << " x " << out2.Ncols() << endl;
    }
  }
}

void write_res(){
  if(verbose.value())
    cout << " Saving results " << endl;
		
  if(debug.value()) 
	cerr << "DBG: in write_res" << endl;
    
  if(regress_only.value()){
	save4D(out2,maskS, fnout.value()+"_index");
	save4D(out1,maskT, fnout.value()+"_corr");
  }
  else{
	save4D(out1,maskS, fnout.value()+"_corr");
	save4D(out2,maskS, fnout.value()+"_index");
  }

  if(out_ttcs.value() && ttcs.size()>0)
    for(int ctr = 0 ;ctr < (int)ttcs.size(); ctr++)
      write_ascii_matrix(ttcs.at(ctr),fnout.value()+"_ttc"+num2str(ctr+1)+".txt");	
  
  if(out_conf.value() && confounds.Storage()>0)
	write_ascii_matrix(confounds, fnout.value()+"_confounds.tx");
	
  if(out_seeds.value())   	
    save4D(seeds, maskS, fnout.value()+"_seeds");
  if(out_seedmask.value())   
    save4D(maskS,fnout.value()+"_seedmask");
  	
}

int do_work(int argc, char* argv[]) {
  if(setup())
	exit(1);
  calc_res();
  write_res();
  return 0;
}

////////////////////////////////////////////////////////////////////////////

int main(int argc,char *argv[]){
	  Tracer tr("main");
	  OptionParser options(title, examples);
	  try{
	    // must include all wanted options here (the order determines how
	    //  the help message is printed)
		options.add(fnin);
		options.add(fnseed);
		options.add(fnout);
		options.add(fntarget);
		options.add(regress_only);
		options.add(fnconf);
		options.add(fnseeddata);
		options.add(map_bin);
		options.add(tc_mean);
		options.add(abscc);
		options.add(tc_order);
		options.add(out_seeds);
		options.add(out_seedmask);
		options.add(out_ttcs);
		options.add(out_conf);
		options.add(out_tcorr);
		options.add(verbose);
		options.add(help);
		options.add(debug);
	    options.parse_command_line(argc, argv);

	    // line below stops the program if the help was requested or 
	    //  a compulsory option was not set
	    if ( (help.value()) || (!options.check_compulsory_arguments(true)) ){
			options.usage();
			exit(EXIT_FAILURE);
	    }else{
	  		// Call the local functions
	  		return do_work(argc,argv);
		}
	  }catch(X_OptionError& e) {
		options.usage();
	  	cerr << endl << e.what() << endl;
	    exit(EXIT_FAILURE);
	  }catch(std::exception &e) {
	    cerr << e.what() << endl;
	  } 
}