Skip to content
Snippets Groups Projects
melodic.cc 13.3 KiB
Newer Older
Mark Jenkinson's avatar
Mark Jenkinson committed
/*  MELODIC - Multivariate exploratory linear optimized decomposition into 
              independent components
    
    melodic.cc - main program file

    Christian F. Beckmann, FMRIB Image Analysis Group
    
    Copyright (C) 1999-2008 University of Oxford */
Mark Jenkinson's avatar
Mark Jenkinson committed

/*  CCOPYRIGHT  */
Mark Jenkinson's avatar
Mark Jenkinson committed

#include <iostream>
Christian Beckmann's avatar
Christian Beckmann committed
#include <iomanip>
#include "newmatap.h"
#include "newmatio.h"
#include "newimage/newimageall.h"
#include "miscmaths/miscmaths.h"
#include "miscmaths/miscprob.h"
#include <string>
#include <math.h>
#include "utils/options.h"
#include "utils/log.h"
Mark Jenkinson's avatar
Mark Jenkinson committed
#include "meloptions.h"
#include "meldata.h"
#include "melpca.h"
#include "melica.h"
#include "melodic.h"
#include "melreport.h"
#include "melhlprfns.h"
Mark Jenkinson's avatar
Mark Jenkinson committed
#include "melgmix.h"

using namespace Utilities;
using namespace NEWMAT;
using namespace NEWIMAGE;
using namespace Melodic;
using namespace MISCPLOT;

Christian Beckmann's avatar
Christian Beckmann committed
string myfloat2str(float f, int width, int prec, bool scientif){
  ostringstream os;
  int redw = int(std::abs(std::log10(std::abs(f))))+1;
  if(width>0)
     os.width(width);
  if(scientif)
    os.setf(ios::scientific);
  os.precision(redw+std::abs(prec));
  os.setf(ios::internal, ios::adjustfield);
  os << f;
  return os.str();
}
Mark Jenkinson's avatar
Mark Jenkinson committed

Matrix mmall(Log& logger, MelodicOptions& opts,
	     MelodicData& melodat, MelodicReport& report, Matrix& probs);
Mark Jenkinson's avatar
Mark Jenkinson committed
void mmonly(Log& logger, MelodicOptions& opts,
	    MelodicData& melodat, MelodicReport& report);

Christian Beckmann's avatar
Christian Beckmann committed
int main(int argc, char *argv[]){
Mark Jenkinson's avatar
Mark Jenkinson committed
  try{
    // Setup logging:
    Log& logger  =   LogSingleton::getInstance();
 
    // parse command line - will output arguments to logfile
    MelodicOptions& opts = MelodicOptions::getInstance();
    opts.parse_command_line(argc, argv, logger, Melodic::version); 

    //set up data object
    MelodicData melodat(opts,logger);
Christian Beckmann's avatar
Christian Beckmann committed
    //set up report object     
Mark Jenkinson's avatar
Mark Jenkinson committed
    MelodicReport report(melodat,opts,logger);
   
    if (opts.filtermode || opts.filtermix.value().length()>0 || opts.ICsfname.value().length()>0){
Mark Jenkinson's avatar
Mark Jenkinson committed
      if(opts.filtermode){ // just filter out some noise from a previous run
Christian Beckmann's avatar
Christian Beckmann committed
    		melodat.setup();
Christian Beckmann's avatar
Christian Beckmann committed
				if(opts.debug.value())
					message("  Denoising data setup completed "<< endl);
Christian Beckmann's avatar
Christian Beckmann committed
				melodat.remove_components();
Christian Beckmann's avatar
Christian Beckmann committed
      } else
Christian Beckmann's avatar
Christian Beckmann committed
				mmonly(logger,opts,melodat,report);
Christian Beckmann's avatar
Christian Beckmann committed
    } else {  // standard PICA now
Mark Jenkinson's avatar
Mark Jenkinson committed
      int retry = 0;
      bool no_conv;
      bool leaveloop = false;

      melodat.setup();

	  if (opts.maxRestart.value()<0)
		opts.maxRestart.set_T(melodat.data_dim());
 
Mark Jenkinson's avatar
Mark Jenkinson committed
      do{
Christian Beckmann's avatar
Christian Beckmann committed
				//do PCA pre-processing
				MelodicPCA pcaobj(melodat,opts,logger,report);
				pcaobj.perf_pca();
				pcaobj.perf_white();

				//do ICA
				MelodicICA icaobj(melodat,opts,logger,report);
				icaobj.perf_ica(melodat.get_white()*melodat.get_Data());
Mark Jenkinson's avatar
Mark Jenkinson committed
    
Christian Beckmann's avatar
Christian Beckmann committed
				no_conv = icaobj.no_convergence;

				if(no_conv){
				    retry++;
					if((opts.approach.value()=="symm")&&(retry == opts.maxRestart.value())){
						// try final round with defl
						opts.approach.set_T("defl");
					    message(endl << "Restarting MELODIC using deflation approach" << endl << endl);	
					}
					else{
					    // try using different dim	
						if((int)opts.pca_dim.value()*opts.retryfactor.value() > (int)(0.05*melodat.data_dim()+1)){
		      				opts.pca_dim.set_T((int)opts.pca_dim.value()*opts.retryfactor.value());
		    			}
     					else{
							if((int)opts.pca_dim.value()/opts.retryfactor.value() > (int)(melodat.data_dim())){
			      				opts.pca_dim.set_T((int)opts.pca_dim.value()/opts.retryfactor.value());
			    			}
							else{
								leaveloop = TRUE;
							}
						}	
					}				
Christian Beckmann's avatar
Christian Beckmann committed
				}
Mark Jenkinson's avatar
Mark Jenkinson committed
      } while (no_conv && retry<opts.maxRestart.value() && !leaveloop);	
     
      if(!no_conv){
Christian Beckmann's avatar
Christian Beckmann committed
				//save raw IC results
				melodat.save();
				Matrix pmaps;//(melodat.get_IC());
				Matrix mmres;
			
				message("Creating report index page ...");
Christian Beckmann's avatar
Christian Beckmann committed
				if( bool(opts.genreport.value()) ){
		  		report.analysistxt();
					if(melodat.get_numfiles()>1)
						report.Smode_rep();
		  		report.PPCA_rep();
				}
					
				message("done"<< endl <<endl);
Christian Beckmann's avatar
Christian Beckmann committed
				if(opts.perf_mm.value())
	  			mmres = mmall(logger,opts,melodat,report,pmaps);
				else{
	  			if( bool(opts.genreport.value()) ){
	    			message(endl 
		    			<< "Creating web report in " << report.getDir() 
		    			<< " " << endl);
	    			for(int ctr=1; ctr<= melodat.get_IC().Nrows(); ctr++){
	      			string prefix = "IC_"+num2str(ctr);
	      			message("  " << ctr);
	      			report.IC_simplerep(prefix,ctr,melodat.get_IC().Nrows());
	    			}

	    			message(endl << endl <<
		    			" To view the output report point your web browser at " <<
		    			report.getDir() + "/00index.html" << endl<< endl); 
	  			}
				}		 

				message("finished!" << endl << endl);
      } 
Christian Beckmann's avatar
Christian Beckmann committed
	  else { 
Christian Beckmann's avatar
Christian Beckmann committed
				message(endl <<"No convergence -- giving up " << endl << endl);
Christian Beckmann's avatar
Christian Beckmann committed
  catch(Exception e) {
Mark Jenkinson's avatar
Mark Jenkinson committed
      cerr << endl << e.what() << endl;
Christian Beckmann's avatar
Christian Beckmann committed
  }
  catch(X_OptionError& e) {
Mark Jenkinson's avatar
Mark Jenkinson committed
      cerr << endl << e.what() << endl;
Christian Beckmann's avatar
Christian Beckmann committed
  }
Mark Jenkinson's avatar
Mark Jenkinson committed

  return 0;
}

void mmonly(Log& logger, MelodicOptions& opts,
Christian Beckmann's avatar
Christian Beckmann committed
	MelodicData& melodat, MelodicReport& report){
Mark Jenkinson's avatar
Mark Jenkinson committed

  Matrix ICs;
  Matrix mixMatrix;
  Matrix fmixMatrix;
  volume<float> Mask;
  volume<float> Mean;
  
  {
    volume4D<float> RawData;
    message("Reading data file " << opts.inputfname.value().at(0) << "  ... ");
Matthew Webster's avatar
Matthew Webster committed
    read_volume4D(RawData,opts.inputfname.value().at(0));
Mark Jenkinson's avatar
Mark Jenkinson committed
    message(" done" << endl);
    Mean = meanvol(RawData);
  }

  {
    volume4D<float> RawIC;
    message("Reading components " << opts.ICsfname.value() << "  ... ");
    read_volume4D(RawIC,opts.ICsfname.value());
    message(" done" << endl);

    message("Creating mask   ... ");
    Mask = binarise(RawIC[0],float(RawIC[0].min()),float(RawIC[0].max()));

    ICs = RawIC.matrix(Mask);
    if(ICs.Nrows()>1){
      Matrix DStDev=stdev(ICs);
      
      volume4D<float> tmpMask;
      tmpMask.setmatrix(DStDev,Mask);
      
      float tMmax;
      volume<float> tmpMask2;
      tmpMask2 = tmpMask[0];
      tMmax = tmpMask2.max();
      double st_mean = DStDev.Sum()/DStDev.Ncols();
      double st_std  = stdev(DStDev.t()).AsScalar();
      
      Mask = binarise(tmpMask2,(float) max((float) st_mean-3*st_std,
					   (float) 0.01*st_mean),tMmax);  
      ICs = RawIC.matrix(Mask);
    }
    else{
      Mask = binarise(RawIC[0],float(0.001),float(RawIC[0].max())) 
	+ binarise(RawIC[0],float(RawIC[0].min()),float(-0.001));
      ICs = RawIC.matrix(Mask);
    }

    //cerr << "ICs : " << ICs.Ncols() << ICs.Nrows() << endl;
    message(" done" << endl);
  }

  if(opts.filtermix.value().length() > 0){
    message("Reading mixing matrix " << opts.filtermix.value() << " ... ");
    mixMatrix = read_ascii_matrix(opts.filtermix.value());
    if (mixMatrix.Storage()<=0) {
      cerr <<" Please specify the mixing matrix correctly" << endl;
      exit(2);
    }
    message(" done" << endl);
  }else{
	mixMatrix=unifrnd(ICs.Nrows()+1,ICs.Nrows());	
Mark Jenkinson's avatar
Mark Jenkinson committed
  }
Mark Jenkinson's avatar
Mark Jenkinson committed

Christian Beckmann's avatar
Christian Beckmann committed
  if(opts.smodename.value().length() > 0){
    message("Reading matrix of subject modes: " << opts.smodename.value());
    Matrix tmp;
    tmp = read_ascii_matrix(opts.smodename.value());
    if (tmp.Storage()<=0) {
      cerr <<" Please specify the mixing matrix correctly" << endl;
      exit(2);
    }
    message(" done" << endl);
    for (int ctr = 1; ctr <= tmp.Ncols(); ctr++){
      Matrix tmp2 = tmp.Column(ctr);
      melodat.add_Smodes(tmp2);
    }
  }

Mark Jenkinson's avatar
Mark Jenkinson committed
  melodat.set_mask(Mask);
  melodat.set_mean(Mean);
  melodat.set_IC(ICs);
  melodat.set_mix(mixMatrix);
  fmixMatrix = calc_FFT(mixMatrix, opts.logPower.value());
Mark Jenkinson's avatar
Mark Jenkinson committed
  melodat.set_fmix(fmixMatrix);
  fmixMatrix = pinv(mixMatrix);
  melodat.set_unmix(fmixMatrix);
Christian Beckmann's avatar
Christian Beckmann committed
  //  melodat.sort();
Mark Jenkinson's avatar
Mark Jenkinson committed
  //  write_ascii_matrix("ICs",ICs);
  
  Matrix mmres;
  Matrix pmaps;//(ICs);
 if(opts.perf_mm.value())
Mark Jenkinson's avatar
Mark Jenkinson committed
    mmres = mmall(logger,opts,melodat,report,pmaps);
Christian Beckmann's avatar
Christian Beckmann committed
	}
Mark Jenkinson's avatar
Mark Jenkinson committed

Christian Beckmann's avatar
Christian Beckmann committed
Matrix mmall(Log& logger, MelodicOptions& opts,MelodicData& melodat, MelodicReport& report, Matrix& pmaps){
Mark Jenkinson's avatar
Mark Jenkinson committed
  
  Matrix mmpars(5*melodat.get_IC().Nrows(),5);
  mmpars = 0;
  Log stats;
  
  if(opts.output_MMstats.value()){  
    stats.makeDir(logger.appendDir("stats"),"stats.log");
  }

  message(endl 
	  << "Running Mixture Modelling on Z-transformed IC maps ..." 
	  << endl);
  
  ColumnVector diagvals;
  diagvals=pow(diag(melodat.get_unmix()*melodat.get_unmix().t()),-0.5);
  
Mark Jenkinson's avatar
Mark Jenkinson committed
  for(int ctr=1; ctr <= melodat.get_IC().Nrows(); ctr++){
    MelGMix mixmod(opts, logger);
    
    message("  IC map " << ctr << " ... "<< endl;);
    
    Matrix ICmap;
    if(melodat.get_stdNoisei().Storage()>0)
 		dbgmsg(" stdNoisei max : "<< melodat.get_stdNoisei().Maximum() <<" "<< melodat.get_stdNoisei().Minimum() << endl);
Mark Jenkinson's avatar
Mark Jenkinson committed

    if(opts.varnorm.value()&&melodat.get_stdNoisei().Storage()>0){
      ICmap = SP(melodat.get_IC().Row(ctr),diagvals(ctr)*melodat.get_stdNoisei());
Christian Beckmann's avatar
Christian Beckmann committed
    }else{
      ICmap = melodat.get_IC().Row(ctr);
Christian Beckmann's avatar
Christian Beckmann committed
	}
Mark Jenkinson's avatar
Mark Jenkinson committed
    string wherelog;
    if(opts.genreport.value())
      wherelog = report.getDir();
    else
      wherelog = logger.getDir();

Christian Beckmann's avatar
Christian Beckmann committed
		dbgmsg(" ICmap max : "<< mean(ICmap,2).AsScalar() << endl);
Matthew Webster's avatar
Matthew Webster committed
    mixmod.setup( ICmap,
Mark Jenkinson's avatar
Mark Jenkinson committed
		  wherelog,ctr,
		  melodat.get_mask(), 
		  melodat.get_mean(),3);
Christian Beckmann's avatar
Christian Beckmann committed
    message("   calculating mixture-model fit "<<endl);
Mark Jenkinson's avatar
Mark Jenkinson committed
    mixmod.fit("GGM");

    if(opts.output_MMstats.value()){
      message("   saving probability map:  ");
Mark Jenkinson's avatar
Mark Jenkinson committed
      melodat.save4D(mixmod.get_probmap(),
Christian Beckmann's avatar
Christian Beckmann committed
		     string("stats/probmap_")+num2str(ctr));
	  message("   saving mixture model fit:");
	  melodat.saveascii(mixmod.get_params(),
			 string("stats/MMstats_")+num2str(ctr));  
    //re-scale spatial maps to mean 0 for nht
    if(opts.rescale_nht.value()){
      message("   re-scaling spatial maps ... "<< endl); 
      RowVector tmp;
      tmp = mixmod.get_means();
      float dmean  = tmp(1);
      tmp = mixmod.get_vars();
      float dstdev = sqrt(tmp(1));

      tmp = (mixmod.get_means() - dmean)/dstdev;
      mixmod.set_means(tmp);
      tmp = (mixmod.get_vars() / (dstdev*dstdev));
      mixmod.set_vars(tmp);

      //tmp = (mixmod.get_data() - dmean)/dstdev;
      tmp = (ICmap - dmean)/dstdev;
      mixmod.set_data(tmp);
      //if(opts.varnorm.value()&&melodat.get_stdNoisei().Storage()>0)
      //	tmp = SP(tmp,pow(diagvals(ctr)*melodat.get_stdNoisei(),-1));
     
      melodat.set_IC(ctr,tmp);
    }

Christian Beckmann's avatar
Christian Beckmann committed
    if(opts.smooth_probmap.value()<0.0){
      message("   smoothing probability map ... "<< endl);
      mixmod.smooth_probs(0.5*(std::min(std::min(std::abs(melodat.get_mean().xdim()),std::abs(melodat.get_mean().ydim())),std::abs(melodat.get_mean().zdim()))));  
    }

    if(opts.smooth_probmap.value()>0.0){
      message("   smoothing probability map ... "<< endl);
      mixmod.smooth_probs(opts.smooth_probmap.value());
    }

Mark Jenkinson's avatar
Mark Jenkinson committed
    message("   thresholding ... "<< endl);
    mixmod.threshold(opts.mmthresh.value());  

    Matrix tmp;
    tmp=(mixmod.get_threshmaps().Row(1));
    float posint = SP(tmp,gt(tmp,zeros(1,tmp.Ncols()))).Sum();
    float negint = -SP(tmp,lt(tmp,zeros(1,tmp.Ncols()))).Sum();
    
    if((posint<0.01)&&(negint<0.01)){
      mixmod.clear_infstr();
      mixmod.threshold("0.05n "+opts.mmthresh.value());
      posint = SP(tmp,gt(tmp,zeros(1,tmp.Ncols()))).Sum();
      negint = -SP(tmp,lt(tmp,zeros(1,tmp.Ncols()))).Sum();
    }
    if(negint>posint){//flip map
      //  melodat.flipres(ctr);
      //  mixmod.flipres(ctr);
Mark Jenkinson's avatar
Mark Jenkinson committed
    }

    //save mixture model stats 
    if(opts.output_MMstats.value()){
      stats << " IC " << num2str(ctr) << " " << mixmod.get_type() << endl
	    << " Means :  " << mixmod.get_means() << endl
	    << " Vars. :  " << mixmod.get_vars()  << endl
	    << " Prop. :  " << mixmod.get_pi()    << endl << endl;
Christian Beckmann's avatar
Christian Beckmann committed
      message("   saving thresholded Z-stats image:");
Mark Jenkinson's avatar
Mark Jenkinson committed
      melodat.save4D(mixmod.get_threshmaps(),
		     string("stats/thresh_zstat")+num2str(ctr));
    }

    //save mmpars
    // mmpars((ctr-1)*5+1,1) = ctr;
Christian Beckmann's avatar
Christian Beckmann committed
		//     if(mixmod.get_type()=="GGM")
		//       mmpars((ctr-1)*5+1,2) = 1.0;
		//     else
		//       mmpars((ctr-1)*5+1,2) = 0.0;
		//     mmpars((ctr-1)*5+1,2) = mixmod.get_means().Ncols();
		//     tmp =  mixmod.get_means();
		//     for(int ctr2=1;ctr2<=mixmod.get_means().Ncols();ctr2++)
		//       mmpars((ctr-1)*5+2,ctr2) = tmp(1,ctr2);
		//     tmp =  mixmod.get_vars();
		//     for(int ctr2=1;ctr2<=mixmod.get_vars().Ncols();ctr2++)
		//       mmpars((ctr-1)*5+3,ctr2) = tmp(1,ctr2);
		//     tmp =  mixmod.get_pi(); 
		//     for(int ctr2=1;ctr2<=mixmod.get_pi().Ncols();ctr2++)
		//       mmpars((ctr-1)*5+4,ctr2) = tmp(1,ctr2);
		//     mmpars((ctr-1)*5+5,1) = mixmod.get_offset();
Mark Jenkinson's avatar
Mark Jenkinson committed

    if( bool(opts.genreport.value()) ){
      message("   creating report page ... ");
      report.IC_rep(mixmod,ctr,melodat.get_IC().Nrows(),melodat.get_ICstats());	    
Mark Jenkinson's avatar
Mark Jenkinson committed
      message("   done" << endl);
    }
  }
  if(!opts.filtermode&&opts.ICsfname.value().length()==0){
    //now safe new data
    //    bool what = opts.verbose.value();
    //opts.verbose.set_T(false);
Christian Beckmann's avatar
Christian Beckmann committed
    
    melodat.set_after_mm(TRUE);
    melodat.save();
    //opts.verbose.set_T(what);

Christian Beckmann's avatar
Christian Beckmann committed
    //if(melodat.get_IC().Storage()>0){
    //  volume4D<float> tempVol;	
    //  tempVol.setmatrix(melodat.get_IC(),melodat.get_mask());
    //  save_volume4D(tempVol,logger.appendDir(opts.outputfname.value() 
Christian Beckmann's avatar
Christian Beckmann committed
  	//					     + "_IC"),melodat.tempInfo);
	  //  message(endl<< endl << " Saving " << logger.appendDir(opts.outputfname.value() + "_IC") <<endl);
   	//}

   if( bool(opts.genreport.value()) ){
Mark Jenkinson's avatar
Mark Jenkinson committed
    message(endl << endl << 
	    " To view the output report point your web browser at " <<
	    report.getDir() + "/00index.html" << endl << endl); 
Mark Jenkinson's avatar
Mark Jenkinson committed
  }
  return mmpars;
Christian Beckmann's avatar
Christian Beckmann committed
  }