Skip to content
Snippets Groups Projects
meldata.cc 16.7 KiB
Newer Older
Mark Jenkinson's avatar
Mark Jenkinson committed
/*  MELODIC - Multivariate exploratory linear optimized decomposition into 
              independent components
    
    meldata.cc - data handler / container class

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

/*  CCOPYRIGHT  */

#include "newimage/newimageall.h"
Mark Jenkinson's avatar
Mark Jenkinson committed
#include "meloptions.h"
#include "meldata.h"
#include "melodic.h"
#include "utils/log.h"
#include <time.h>
Mark Jenkinson's avatar
Mark Jenkinson committed

using namespace Utilities;
using namespace NEWIMAGE;

namespace Melodic{

  void MelodicData::setup()
  {
    {
      volume4D<float> RawData;
      message("Reading data file " << opts.inputfname.value() << "  ... ");
      read_volume4D(RawData,opts.inputfname.value(),tempInfo);
      message(" done" << endl);
      for(int ctr=1; ctr<=opts.dummy.value(); ctr++){
	RawData.deletevolume(ctr);
      }    
    
      // calculate a Mean image and save it
      Mean = meanvol(RawData);
      tmpnam(Mean_fname); // generate a tmp name
      save_volume(Mean,Mean_fname);
      create_mask(RawData, Mask);
      if(Mask.xsize()==RawData.xsize() &&
	 Mask.ysize()==RawData.ysize() &&
	 Mask.zsize()==RawData.zsize())
	 Data = RawData.matrix(Mask);
      else{
	cerr << "ERROR:: mask and data have different dimensions  \n\n";
	exit(2);
      }
	
           
      // clean /tmp
      char callRMstr[1000];
      ostrstream osc(callRMstr,1000);
      osc  << "rm " << string(Mean_fname) <<"*  " << '\0';
      system(callRMstr);
         
      //mask out constant voxels
      message("Excluding voxels with constant value " << endl);
      Matrix DStDev=stdev(Data);
      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);
      Data = RawData.matrix(Mask);
    }

    message("Data size : " << Data.Nrows() << " x " << Data.Ncols() <<endl);

    {// remove mean volume
      //    if((opts.remove_meanvol.value()||opts.varnorm.value())){
      message(string("Removing mean image ... "));
      meanR=mean(Data);
      Data=remmean(Data); 
      message("done" << endl);
      //}else{
      // meanR=zeros(1,Data.Ncols());
      //}
    }
    {// remove mean time course
      meanC=mean(Data,2);
      Data=remmean(Data,2); 
    }

    {//switch dimension in case temporal ICA is required
      if(opts.temporal.value()){
	message(string("Switching dimensions for temporal ICA") << endl);
	Data = Data.t();
	Matrix tmp;
	tmp = meanC;
	meanC = meanR.t();
	meanR = tmp.t();
	message("Data size : " << Data.Nrows() << " x " << Data.Ncols() <<endl);
      }
    }


Mark Jenkinson's avatar
Mark Jenkinson committed
    {// variance-normalize the data
      DiagonalMatrix tmpD;Matrix tmpE;
      message(string("Estimating data covariance ... "));
      SymmetricMatrix Corr;
      Corr = cov(Data.t());
      EigenValues(Corr,tmpD,tmpE);
      
      Matrix RE; DiagonalMatrix RD;
      //RE = tmpE.Columns(2,Corr.Ncols());
      RE = tmpE;
      //RD << abs(tmpD.SymSubMatrix(2,Corr.Ncols()));    
      RD << abs(tmpD);
      Matrix tmpWhite;Matrix tmpDeWhite;
      tmpWhite = sqrt(abs(RD.i()))*RE.t();
      tmpDeWhite = RE*sqrt(RD);
      message("done"<<endl);
      if(opts.varnorm.value())
	message(string("Perform variance-normalisation ... "));
      Matrix WS;
      WS = tmpWhite * Data;
      for(int ctr1 =1; ctr1<=WS.Nrows(); ctr1++){
	for(int ctr2 =1; ctr2<=WS.Ncols(); ctr2++){
	  if(abs(WS(ctr1,ctr2))<3.1)
	    WS(ctr1,ctr2)=0.0;
	}
      }
      
      stdDevi = pow(stdev(Data - tmpDeWhite*WS),-1);   
Mark Jenkinson's avatar
Mark Jenkinson committed
      Data = Data + meanC*ones(1,Data.Ncols());
    }
    
    DataVN = SP(Data,ones(Data.Nrows(),1)*stdDevi);

    if(opts.output_all.value()){
      volume4D<float> tempVol; 
      tempVol.setmatrix(stdDevi,Mask);
      save_volume4D(tempVol,logger.appendDir(opts.outputfname.value() 
					     + "_vn_stdev"),tempInfo);
      tempVol.setmatrix(DataVN,Mask);
      save_volume4D(tempVol,logger.appendDir(opts.outputfname.value() 
					     + "_vn"),tempInfo);
    }

Mark Jenkinson's avatar
Mark Jenkinson committed
    if(opts.varnorm.value()){
      Data = DataVN;
      message("done"<<endl);
    }
    {//remove row mean
      if(opts.temporal.value()){
	message(string("Removing mean image ... "));
Mark Jenkinson's avatar
Mark Jenkinson committed
      }else{
	message(string("Removing mean time course ... "));
      }
      meanC=mean(Data,2);
      Data=remmean(Data,2); 
      message("done"<<endl);
Mark Jenkinson's avatar
Mark Jenkinson committed
    }
    
    if(opts.segment.value().length()>0){
       create_RXweight();
    }
 
    //save the mask
    save_volume(Mask,logger.appendDir("mask"));

    //seed the random number generator
    double tmptime = time(NULL);
    srand((unsigned int) tmptime/Data.Ncols()*Data.Nrows());
   
  } // void setup()

  void MelodicData::save()
  {   

    //check for temporal ICA
    if(opts.temporal.value()){
      message(string("temporal ICA: transform back the data ... "));
Mark Jenkinson's avatar
Mark Jenkinson committed
      Matrix tmpIC = mixMatrix.t();
      mixMatrix=IC.t();
      IC=tmpIC;

      unmixMatrix=pinv(mixMatrix);
      Data = Data.t();
      tmpIC = meanC;
      meanC = meanR.t();
      meanR = tmpIC.t();
      //  whiteMatrix = whiteMatrix.t;
      //  dewhiteMatrix = dewhiteMatrix.t();
      message(string("done") << endl);
      opts.temporal.set_T(false); // Do not switch again!
Mark Jenkinson's avatar
Mark Jenkinson committed
    }
 
    message("Writing results to : " << endl);

    //Output IC	
Christian Beckmann's avatar
Christian Beckmann committed
    if((IC.Storage()>0)&&(opts.output_origIC.value())&&(after_mm==false)){
Mark Jenkinson's avatar
Mark Jenkinson committed
      volume4D<float> tempVol; 
      tempVol.setmatrix(IC,Mask);
      //strncpy(tempInfo.header.hist.aux_file,"render3",24);
      save_volume4D(tempVol,logger.appendDir(opts.outputfname.value() 
Christian Beckmann's avatar
Christian Beckmann committed
					     + "_oIC"),tempInfo);
Mark Jenkinson's avatar
Mark Jenkinson committed
      message("  " << logger.appendDir(opts.outputfname.value() + "_oIC") <<endl);
    }

    //Output IC -- adjusted for noise	
      if(IC.Storage()>0){
      volume4D<float> tempVol;	
	//      volumeinfo tempInfo;
	//  read_volume4D(tempVol,opts.inputfname.value(),tempInfo); 

      //Matrix ICadjust = IC;
Mark Jenkinson's avatar
Mark Jenkinson committed
      Matrix ICadjust;
Christian Beckmann's avatar
Christian Beckmann committed
      if(after_mm)
	ICadjust = IC;
      else{
	stdNoisei = pow(stdev(Data - mixMatrix * IC)*std::sqrt((float)(Data.Nrows()-1))/
			std::sqrt((float)(Data.Nrows()-IC.Nrows())),-1);

	ColumnVector diagvals;
	diagvals=pow(diag(unmixMatrix*unmixMatrix.t()),-0.5);
	ICadjust = SP(IC,diagvals*stdNoisei);
      }
Mark Jenkinson's avatar
Mark Jenkinson committed
      tempVol.setmatrix(ICadjust,Mask);
      //strncpy(tempInfo.header.hist.aux_file,"render3",24);
      save_volume4D(tempVol,logger.appendDir(opts.outputfname.value() 
					     + "_IC"),tempInfo);
      message("  " << logger.appendDir(opts.outputfname.value() + "_IC") <<endl);

      if(opts.output_origIC.value()){
	tempVol.setmatrix(stdNoisei,Mask);
	save_volume4D(tempVol,logger.appendDir(string("Noise_stddev_inv")),tempInfo);
	message("  " << logger.appendDir(string("Noise_stddev_inv")) <<endl);
      }
      
Mark Jenkinson's avatar
Mark Jenkinson committed
    }

    //Output mixMatrix
    if(mixMatrix.Storage()>0){
      write_ascii_matrix(logger.appendDir(opts.outputfname.value() + "_mix"),
			 mixMatrix); 
      mixFFT=calc_FFT(mixMatrix);
      write_ascii_matrix(logger.appendDir(opts.outputfname.value() + "_FTmix"),
			 mixFFT);      
      message("  "<<
	      logger.appendDir(opts.outputfname.value() + "_mix") <<endl);
      message("  "<<
	      logger.appendDir(opts.outputfname.value() + "_FTmix") <<endl);
    }

    //Output unmixMatrix
    if(opts.output_unmix.value() && unmixMatrix.Storage()>0){
      write_ascii_matrix(logger.appendDir(opts.outputfname.value() + "_unmix"),unmixMatrix);
Mark Jenkinson's avatar
Mark Jenkinson committed
      message("  "<<
	  logger.appendDir(opts.outputfname.value() + "_unmix") <<endl);
    }

    //Output Mask
    message("  "<< logger.appendDir("mask") <<endl);

    //Output mean
    if(opts.output_mean.value() && meanC.Storage()>0 && meanR.Storage()>0){
      write_ascii_matrix(logger.appendDir(opts.outputfname.value() + "_meanR"),
			 meanR);
      write_ascii_matrix(logger.appendDir(opts.outputfname.value() + "_meanC"),
			 meanC);
      message("  "<<
	  logger.appendDir(opts.outputfname.value() + "_meanR") <<endl);
      message("  "<<
	  logger.appendDir(opts.outputfname.value() + "_meanC") <<endl);
    }

    //Output white
    if(opts.output_white.value() && whiteMatrix.Storage()>0&&
       dewhiteMatrix.Storage()>0){
      write_ascii_matrix(logger.appendDir(opts.outputfname.value() + "_white"),
			 whiteMatrix);
      write_ascii_matrix(logger.appendDir(opts.outputfname.value() + "_dewhite"),dewhiteMatrix);
      Matrix tmp;
      tmp=calc_FFT(dewhiteMatrix);
      write_ascii_matrix(logger.appendDir(opts.outputfname.value() + "_FTdewhite"),tmp);
      message("  "<<
	  logger.appendDir(opts.outputfname.value() + "_white") <<endl);
      message("  "<<
	  logger.appendDir(opts.outputfname.value() + "_dewhite") <<endl);
    }

    //Output PCA
    if(opts.output_pca.value() && pcaD.Storage()>0&&pcaE.Storage()>0){
      write_ascii_matrix(logger.appendDir(opts.outputfname.value() + "_pcaE"),
			 pcaE);
      message("  "<<
	      logger.appendDir(opts.outputfname.value() + "_pcaE") <<endl);
Mark Jenkinson's avatar
Mark Jenkinson committed
      write_ascii_matrix(logger.appendDir(opts.outputfname.value() + "_pcaD"),
			 (Matrix) diag(pcaD));
      message("  "<<
	      logger.appendDir(opts.outputfname.value() + "_pcaD") <<endl);
      
Mark Jenkinson's avatar
Mark Jenkinson committed
      volume4D<float> tempVol;	

      Matrix PCAmaps;

      if(whiteMatrix.Ncols()==Data.Ncols()){
	PCAmaps = dewhiteMatrix.t();
      }else
	PCAmaps = whiteMatrix * Data;
     
Mark Jenkinson's avatar
Mark Jenkinson committed

      tempVol.setmatrix(PCAmaps,Mask);
      //strncpy(tempInfo.header.hist.aux_file,"render3",24);
      save_volume4D(tempVol,logger.appendDir(opts.outputfname.value() 
					     + "_pca"),tempInfo);
      message("  " << 
	  logger.appendDir(opts.outputfname.value() + "_pca") <<endl);
Mark Jenkinson's avatar
Mark Jenkinson committed
  } //void save()
  
  int MelodicData::remove_components()
  {  
    message("Reading " << opts.filtermix.value() << endl) 
    mixMatrix = read_ascii_matrix(opts.filtermix.value());
    if (mixMatrix.Storage()<=0) {
      cerr <<" Please specify the mixing matrix correctly" << endl;
      exit(2);
    }
    
    unmixMatrix = pinv(mixMatrix);
    IC = unmixMatrix * Data;

    string tmpstr;
    tmpstr = opts.filter.value();

    Matrix noiseMix;
    Matrix noiseIC;

    int ctr=0;    
    char *p;
    char t[1024];
    const char *discard = ", [];{(})abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~!@#$%^&*_-=+|\':><./?";
    
    message("Filtering the data...");
    strcpy(t, tmpstr.c_str());
    p=strtok(t,discard);
    ctr = atoi(p);
    if(ctr>0 && ctr<=mixMatrix.Ncols()){
      message(" "<< ctr );
      noiseMix = mixMatrix.Column(ctr);
      noiseIC  = IC.Row(ctr).t();    
    }else{
      cerr << endl<< "component number "<<ctr<<" does not exist" << endl;
    }
    
    do{
      p=strtok(NULL,discard);
      if(p){
	ctr = atoi(p);
	
        if(ctr>0 && ctr<=mixMatrix.Ncols()){
	  message(" "<<ctr);
	  noiseMix |= mixMatrix.Column(ctr);
	  noiseIC  |= IC.Row(ctr).t();
	}
	else{
	  cerr << endl<< "component number "<<ctr<<" does not exist" << endl;
	}
      }
    }while(p);
    message(endl);
    Matrix newData;
    newData = Data - noiseMix * noiseIC.t();

    //cerr << newData.Nrows() << " x " << newData.Ncols() << endl;
    //cerr << meanC.Nrows() << " x " << meanC.Ncols() << endl;
    //cerr << meanR.Nrows() << " x " << meanR.Ncols() << endl;
    newData = newData + meanC*ones(1,newData.Ncols());
    newData = newData + ones(newData.Nrows(),1)*meanR;
    
    volume4D<float> tmp;
    read_volume4D(tmp,opts.inputfname.value()); 
    tmp.setmatrix(newData,Mask);
    save_volume4D(tmp,logger.appendDir(opts.outputfname.value() + "_ICAfiltered")); 
   
    return 0;
  } // int remove_components()

  void MelodicData::create_RXweight()
  {
    message("Reading the weights for the covariance R_X from file "<< opts.segment.value() << endl);
  
    volume4D<float> tmpRX;
    read_volume4D(tmpRX,opts.segment.value());
    RXweight = tmpRX.matrix(Mask);
  } 
 
  void MelodicData::create_mask(volume4D<float> &theData, 
				volume<float> &theMask)
  {
    if(opts.use_mask.value() && opts.maskfname.value().size()>0){   // mask provided 
      read_volume(theMask,opts.maskfname.value());
      message("Mask provided : " << opts.maskfname.value()<<endl);
    }
    else{
      if(opts.perf_bet.value() && opts.use_mask.value()){ //use BET
	message("Create mask ... ");
	// set up all strings
	string BET_outputfname = string(Mean_fname)+"_brain";

	string BET_path = opts.binpath + "bet";
	string BET_optarg = "-m -f 0.4"; // see man bet
Stephen Smith's avatar
Stephen Smith committed
	string Mask_fname = BET_outputfname+"_mask";
Mark Jenkinson's avatar
Mark Jenkinson committed

	// Setup external call to BET:

	char callBETstr[1000];
	ostrstream osc(callBETstr,1000);
	osc  << BET_path << " " << Mean_fname << " " 
	     << BET_outputfname << " " << BET_optarg << " > /dev/null " << '\0';
	
        message("  Calling BET: " << callBETstr << endl);
	system(callBETstr);
	
	// read back the Mask file   
	read_volume(theMask,Mask_fname);

	message("done" << endl);
      }  
      else{
	if(opts.use_mask.value()){   //just threshold the Mean
	  message("Create mask ... ");
	  float Mmin, Mmax, Mtmp;
	  Mmin = Mean.min(); Mmax = Mean.max();
	  theMask = binarise(Mean,Mmin + opts.threshold.value()* (Mmax-Mmin),Mmax);
          Mtmp = Mmin + opts.threshold.value()* (Mmax-Mmin);
	  message("done" << endl);
	}
	else{ //well, don't threshold then
	  theMask = Mean;
	  theMask = 1.0;
	}
      }
    }
    if(opts.remove_endslices.value()){ 
      // just in case mc introduced something nasty
      message("  Deleting end slices" << endl);
      for(int ctr1=theMask.miny(); ctr1<=theMask.maxy(); ctr1++){
	for(int ctr2=theMask.minx(); ctr2<=theMask.maxx(); ctr2++){   
	  theMask(ctr2,ctr1,Mask.minz()) = 0.0;
	  theMask(ctr2,ctr1,Mask.maxz()) = 0.0;
	}
      }
    }
  } //void create_mask()


  void MelodicData::status(const string &txt)
  {
    cout << "MelodicData Object " << txt << endl;
    if(Data.Storage()>0){cout << "Data: " << Data.Nrows() <<"x" << Data.Ncols() << endl;}else{cout << "Data empty " <<endl;}
    if(pcaE.Storage()>0){cout << "pcaE: " << pcaE.Nrows() <<"x" << pcaE.Ncols() << endl;}else{cout << "pcaE empty " <<endl;}
    if(pcaD.Storage()>0){cout << "pcaD: " << pcaD.Nrows() <<"x" << pcaD.Ncols() << endl;}else{cout << "pcaD empty " <<endl;}
    if(whiteMatrix.Storage()>0){cout << "white: " << whiteMatrix.Nrows() <<"x" << whiteMatrix.Ncols() << endl;}else{cout << "white empty " <<endl;}
    if(dewhiteMatrix.Storage()>0){cout << "dewhite: " << dewhiteMatrix.Nrows() <<"x" << dewhiteMatrix.Ncols() << endl;}else{cout << "dewhite empty " <<endl;}
    if(mixMatrix.Storage()>0){cout << "mix: " << mixMatrix.Nrows() <<"x" << mixMatrix.Ncols() << endl;}else{cout << "mix empty " <<endl;}
    if(unmixMatrix.Storage()>0){cout << "unmix: " << unmixMatrix.Nrows() <<"x" << unmixMatrix.Ncols() << endl;}else{cout << "unmix empty " <<endl;}
    if(IC.Storage()>0){cout << "IC: " << IC.Nrows() <<"x" << IC.Ncols() << endl;}else{cout << "IC empty " <<endl;}
   
  } //void status()

  Matrix MelodicData::calc_FFT(const Matrix& Mat)
  {
    Matrix res;
    for(int ctr=1; ctr <= Mat.Ncols(); ctr++)
      {
	ColumnVector tmpCol;
	tmpCol=Mat.Column(ctr);
	ColumnVector FtmpCol_real;
	ColumnVector FtmpCol_imag;
	ColumnVector tmpPow;
	if(tmpCol.Nrows()%2 != 0){
	  Matrix empty(1,1); empty=0;
	  tmpCol &= empty;}
	RealFFT(tmpCol,FtmpCol_real,FtmpCol_imag);
	tmpPow = pow(FtmpCol_real,2)+pow(FtmpCol_imag,2);
	tmpPow = tmpPow.Rows(2,tmpPow.Nrows());
	if(opts.logPower.value()) tmpPow = log(tmpPow);
	if(res.Storage()==0){res= tmpPow;}else{res|=tmpPow;}
      }
    return res;
  } //Matrix calc_FFT()

  Matrix MelodicData::smoothColumns(const Matrix &inp)
  {
    Matrix temp(inp);
    int ctr1 = temp.Nrows();
    Matrix temp2(temp);
    temp2=0;
    
    temp = temp.Row(4) & temp.Row(3) & temp.Row(2) & temp & temp.Row(ctr1-1) 
      & temp.Row(ctr1-2) &temp.Row(ctr1-3);
    
    double kern[] ={0.0045 , 0.055, 0.25, 0.4, 0.25, 0.055, 0.0045};
    double fac = 0.9090909;

    //  Matrix FFTinp;
    //FFTinp = calc_FFT(inp);
    //write_ascii_matrix(logger.appendDir(opts.outputfname.value() + "_FT"),
    //			 FFTinp);    FFTinp = calc_FFT(inp);
  //write_ascii_matrix(logger.appendDir(opts.outputfname.value() + "_Sinp"),
    //		 inp);   
    
    for(int cc=1;cc<=temp2.Ncols();cc++){
      //  double all = FFTinp.Column(cc).Rows(1,30).SumAbsoluteValue() / FFTinp.Column(cc).SumAbsoluteValue();
      // if( all > 0.5){
      for(int cr=1;cr<=temp2.Nrows();cr++){
	temp2(cr,cc) = fac*( kern[0] * temp(cr,cc) + kern[1] * temp(cr+1,cc) + 
			     kern[2] * temp(cr+2,cc) + kern[3] * temp(cr+3,cc) + 
			     kern[4] * temp(cr+4,cc) + kern[5] * temp(cr+5,cc) + 
			     kern[6] * temp(cr+6,cc));
      }//}
      //else{
      //	for(int cr=1;cr<=temp2.Nrows();cr++){
      //	  temp2(cr,cc) = temp(cr+3,cc);
      //	}
      //}
    }
//write_ascii_matrix(logger.appendDir(opts.outputfname.value() + "_Sout"),
//		 temp2);   
    return temp2;
  }
}