From da03bc3faa6e41adc77801365d47e0a98ccdcb01 Mon Sep 17 00:00:00 2001
From: Christian Beckmann <c.beckmann@donders.ru.nl>
Date: Mon, 10 Nov 2008 17:58:52 +0000
Subject: [PATCH]  added fsl_mvlm

---
 Makefile    |   9 +-
 fsl_glm.cc  |   2 +-
 fsl_mvlm.cc | 381 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 389 insertions(+), 3 deletions(-)
 create mode 100644 fsl_mvlm.cc

diff --git a/Makefile b/Makefile
index ecf8f16..552f8ad 100644
--- a/Makefile
+++ b/Makefile
@@ -18,19 +18,21 @@ GGMIX_OBJS = ggmix.o
 
 FSL_GLM_OBJS = melhlprfns.o fsl_glm.o
 
+FSL_MVLM_OBJS = melhlprfns.o fsl_mvlm.o
+
 FSL_REGFILT_OBJS = melhlprfns.o fsl_regfilt.o
 
 MELODIC_OBJS =  meloptions.o melhlprfns.o melgmix.o meldata.o melpca.o melica.o melreport.o melodic.o 
 
 TESTXFILES = test
 
-XFILES = fsl_glm fsl_regfilt melodic
+XFILES = fsl_glm fsl_mvlm fsl_regfilt melodic
 
 RUNTCLS = Melodic
 
 SCRIPTS = melodicreport
 
-all: ggmix fsl_regfilt fsl_glm melodic
+all: ggmix fsl_regfilt fsl_glm fsl_mvlm melodic
 
 ggmix: ${GGMIX_OBJS}
 	${AR} -r libggmix.a ${GGMIX_OBJS} 
@@ -44,6 +46,9 @@ test: ${TEST_OBJS}
 fsl_glm: ${FSL_GLM_OBJS}
 	$(CXX) ${CXXFLAGS} ${LDFLAGS} -o $@ ${FSL_GLM_OBJS} ${LIBS}
 
+fsl_mvlm: ${FSL_MVLM_OBJS}
+		$(CXX) ${CXXFLAGS} ${LDFLAGS} -o $@ ${FSL_MVLM_OBJS} ${LIBS}
+
 fsl_regfilt: ${FSL_REGFILT_OBJS}
 	$(CXX) ${CXXFLAGS} ${LDFLAGS} -o $@ ${FSL_REGFILT_OBJS} ${LIBS}
 
diff --git a/fsl_glm.cc b/fsl_glm.cc
index 0d78bab..bc5ff31 100644
--- a/fsl_glm.cc
+++ b/fsl_glm.cc
@@ -58,7 +58,7 @@ using namespace std;
 		string("        perform MELODIC variance-normalisation on data"),
 		false, no_argument);
 	Option<bool> perf_demean(string("--demean"),FALSE,
-		string("        switch on de-meaning of design and data"),
+		string("switch on de-meaning of design and data"),
 		false, no_argument);
 	Option<int> help(string("-h,--help"), 0,
 		string("display this help text"),
diff --git a/fsl_mvlm.cc b/fsl_mvlm.cc
new file mode 100644
index 0000000..619c712
--- /dev/null
+++ b/fsl_mvlm.cc
@@ -0,0 +1,381 @@
+/*  fsl_glm - 
+
+    Christian F. Beckmann, FMRIB Image Analysis Group
+
+    Copyright (C) 2006-2008 University of Oxford  */
+
+/*  CCOPYRIGHT  */
+
+//Header & includes
+#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"
+
+#define message(msg) { \
+  if(verbose.value()) \
+    { \
+      cout << msg; \
+    } \
+}
+
+#define dbgmsg(msg) { \
+  if(debug.value()) {\
+	cerr << msg << endl; } \
+}
+
+#define outMsize(msg,Mat) { \
+  if(debug.value())						\
+    cerr << "     " << msg << "  " <<Mat.Nrows() << " x " << Mat.Ncols() << endl;	\
+}
+
+#define outM(msg,Mat) { \
+  if(verbose.value())						\
+    cout << "     " << msg << "  " <<Mat.Nrows() << " x " << Mat.Ncols() << endl;	\
+}
+
+
+using namespace MISCPLOT;
+using namespace MISCMATHS;
+using namespace Utilities;
+using namespace std;
+
+
+//Command-line output
+// The two strings below specify the title and example usage that is
+// printed out as the help or usage message
+
+  string title=string("fsl_mvlm (Version 1.0)")+
+		string("\nCopyright(c) 2008, University of Oxford (Christian F. Beckmann)\n")+
+		string(" \n Multivariate Linear Model regression on\n")+
+		string(" time courses and/or 3D/4D imges using  SVD (PCA), PLS, normalised PLS, \n")+
+		string(" CVA, SVD-CVA or  MLM\n\n");
+  string examples="fsl_mvlm -i <input> -o <output> [options]";
+
+
+//Command line Options 
+    Option<string> fnin(string("-i,--in"), string(""),
+		string("        input file name (text matrix or 3D/4D image file)"),
+		true, requires_argument);
+    Option<string> fnout(string("-o,--out"), string(""),
+		string("basename for output files "),
+		true, requires_argument);
+	Option<string> approach(string("-a,--alg"), string("PCA"),
+		string("algorithm for decomposition: PCA (or SVD; default), PLS, orthPLS, CVA, SVD-CVA, MLM"),
+		false, requires_argument);	
+    Option<string> fndesign(string("-d,--design"), string(""),
+		string("file name of the GLM design matrix (time courses or spatial maps)"),
+		false, requires_argument);
+    Option<string> fnmask(string("-m,--mask"), string(""),
+		string("mask image file name if input is image"),
+		false, requires_argument);
+	Option<bool> normdes(string("--des_norm"),FALSE,
+		string("switch on normalisation of the design matrix columns to unit std. deviation"),
+		false, no_argument);
+	Option<bool> perfvn(string("--vn"),FALSE,
+		string("        perform MELODIC variance-normalisation on data"),
+		false, no_argument);
+	Option<bool> perf_demean(string("--demean"),FALSE,
+		string("switch on de-meaning of design and data"),
+		false, no_argument);
+	Option<int> help(string("-h,--help"), 0,
+		string("display this help text"),
+		false,no_argument);
+	Option<bool> verbose(string("-v,--verbose"),FALSE,
+		string("switch on verbose output"),
+		false, no_argument);
+	Option<bool> debug(string("--debug"),FALSE,
+		string("switch on debug output"),
+		false, no_argument, false);
+	// Output options	
+	Option<string> outres(string("--out_res"),string(""),
+		string("output file name for residuals"),
+		false, requires_argument, false);
+	Option<string> outdata(string("--out_data"),string(""),
+		string("output file name for pre-processed data"),
+		false, requires_argument);
+	Option<string> outvnscales(string("--out_vnscales"),string(""),
+		string("output file name for scaling factors for variance normalisation"),
+		false, requires_argument);
+
+//Globals 
+	Melodic::basicGLM glm;
+	int voxels = 0;
+	Matrix data, tmpdata;
+	Matrix design;
+	Matrix meanR;
+	Matrix svd_X_U, svd_X_V, svd_Y_U, svd_Y_V;
+	DiagonalMatrix svd_X_D, svd_Y_D;	
+	RowVector vnscales;
+	volume<float> mask;
+	volumeinfo volinf;  
+
+////////////////////////////////////////////////////////////////////////////
+
+// Local functions
+void save4D(Matrix what, string fname){
+		if(what.Ncols()==data.Ncols()||what.Nrows()==data.Nrows()){
+			volume4D<float> tempVol;
+			if(what.Nrows()>what.Ncols())
+				tempVol.setmatrix(what.t(),mask);
+			else
+				tempVol.setmatrix(what,mask);
+			save_volume4D(tempVol,fname,volinf);
+		}
+}
+
+bool isimage(Matrix what){
+	if((voxels > 0)&&(what.Ncols()==voxels || what.Nrows()==voxels))
+		return TRUE;
+	else
+		return FALSE;
+}
+
+void saveit(Matrix what, string fname){
+	if(isimage(what))
+		save4D(what,fname);
+	else if(fsl_imageexists(fndesign.value()))
+		write_ascii_matrix(what.t(),fname);
+	else
+		write_ascii_matrix(what,fname);
+}
+
+int setup(){
+	
+	dbgmsg(" In <setup>");
+	message(" Reading data " << fnin.value() << " ... ");
+	if(fsl_imageexists(fnin.value())){//read data
+		//input is 3D/4D vol
+		volume4D<float> tmpdata;
+		read_volume4D(tmpdata,fnin.value(),volinf);
+		
+		// create mask
+		if(fnmask.value()>""){
+			read_volume(mask,fnmask.value());
+			if(!samesize(tmpdata[0],mask)){
+				cerr << "ERROR: Mask image does not match input image" << endl;
+				return 1;
+			};
+		}else{
+			mask = tmpdata[0]*0.0+1.0;	
+		}
+		
+		data = tmpdata.matrix(mask);
+		voxels = data.Ncols();
+		if(perf_demean.value())
+			data = remmean(data,1);
+		if(perfvn.value())
+			vnscales = Melodic::varnorm(data);
+	}
+	else
+		data = read_ascii_matrix(fnin.value());	
+
+	message("done" << endl;);
+	if(fndesign.value().length()>0){
+		message(" Reading design " << fndesign.value() << " ... ");
+		if(fsl_imageexists(fndesign.value())){//read design
+			volume4D<float> tmpdata;
+			read_volume4D(tmpdata,fndesign.value());
+			if(!samesize(tmpdata[0],mask)){
+				cerr << "ERROR: GLM design does not match input image in size" << endl;
+				return 1;
+			}
+			design = tmpdata.matrix(mask).t();
+			data = data.t();
+		}else{
+			design = read_ascii_matrix(fndesign.value());
+		}
+		message("done" << endl;);
+	}else
+		design = ones(data.Nrows(),1);
+
+	meanR=mean(data,1);
+	if(perf_demean.value()){
+		data = remmean(data,1);
+		design = remmean(design,1);
+	}
+	
+	if(normdes.value())
+		design =  SP(design,ones(design.Nrows(),1)*pow(stdev(design,1),-1));
+	
+	SVD( design, svd_X_D, svd_X_U, svd_X_V );
+	if(data.Nrows()>=data.Ncols())
+		SVD ( data, svd_Y_D, svd_Y_U, svd_Y_V );
+	else{
+		SVD ( data.t(), svd_Y_D, svd_Y_V, svd_Y_U );
+	}		
+
+	if(fnout.value().length() == 0){
+		string basename = fnin.value();
+		basename = make_basename(basename);
+		fnout.set_T(basename+string("_mvlm_"));	
+	}
+	
+	outM("Data matrix : ", data);
+	outM("Design matrix : ", design);
+
+	dbgmsg(" initial SVD : ");
+	outMsize("svd_Y_U",svd_Y_U);
+	outMsize("svd_Y_V",svd_Y_V);
+	outMsize("svd_Y_D",svd_Y_D);
+
+	outMsize("svd_X_U",svd_X_U);
+	outMsize("svd_X_V",svd_X_V);
+	outMsize("svd_X_D",svd_X_D);
+
+	dbgmsg(" Leaving <setup>");
+	return 0;	
+}
+
+void write_res(){
+	dbgmsg(" In <write_res>");
+	
+	message(" Writing results ... ")
+	if(isimage(svd_Y_V)){
+		saveit(svd_Y_V,fnout.value()+string("maps"));
+		saveit(svd_Y_U,fnout.value()+string("tcs"));
+	}
+	else{
+		saveit(svd_Y_V.t(),fnout.value()+string("tcs"));
+		saveit(svd_Y_U,fnout.value()+string("maps"));
+	}
+	saveit(svd_Y_D.AsColumn(),fnout.value()+string("scales"));
+	if(outres.value()>"")
+		
+	if(outdata.value()>"")
+		saveit(data,outdata.value());
+	if(outvnscales.value()>"")
+		saveit(vnscales,outvnscales.value());
+	
+	message("done" << endl;);
+	dbgmsg(" Leaving <write_res>");
+		
+}
+
+int do_work(int argc, char* argv[]) {
+	dbgmsg(" In <do_work>");
+	
+    if(setup())
+      exit(1);
+
+	//modify data
+	
+	//X = svd_X_U * svd_X_D * svd_X_V.t();
+	//Y = svd_Y_U * svd_Y_D * svd_Y_V.t();
+	//X'X = svd_X_V *pow(svd_X_D,2) * svd_X_V.t();
+	//(X'X)^(-1) = svd_X_V *pow(svd_X_D,-2) * svd_X_V.t()
+ 	//(X'X)^(-1/2) = svd_X_V *pow(svd_X_D,-1) * svd_X_V.t()
+			
+	if(approach.value()==string("PLS")) {
+		message(" Using method : " << approach.value() << endl;);
+		data = design.t() * data;
+	} 
+	if(approach.value()==string("orthoPLS")) {
+		message(" Using method : " << approach.value() << endl;);
+		data = (svd_X_V * svd_X_D.i() * svd_X_V.t()) * design.t() * data;
+	} 
+	if(approach.value()==string("CVA")) {
+		message(" Using method : " << approach.value() << endl;);
+		data = design.t() * svd_Y_U * svd_Y_V.t();
+		data = (svd_X_V * svd_X_D.i() * svd_X_V.t() ) * data;
+	}
+	if(approach.value()==string("SVD-CVA")) {
+		message(" Using method : " << approach.value() << endl;);
+		tmpdata = data;
+		data = design.t() * svd_Y_U;
+		data = (svd_X_V * svd_X_D.i() * svd_X_V.t() ) * data;
+	}
+	if(approach.value()==string("MLM")) {
+		message(" Using method : " << approach.value() << endl;);
+
+		Matrix RE;
+	    DiagonalMatrix RD;
+	    SymmetricMatrix tmp;
+	    tmp << cov(data.t());
+	    EigenValues(tmp,RD,RE);
+//		S = RE * RD * RE.t()
+		
+		tmp << sqrtm(svd_X_V * svd_X_D * svd_X_U.t() * RE * RD * RE.t() *svd_X_U * svd_X_D * svd_X_V.t());
+		data =  tmp.i()*design.t() * data;
+	}
+
+    if( approach.value()!=string("MLM") && approach.value()!=string("CVA") && approach.value()!=string("PLS") &&
+		approach.value()!=string("SVD-CVA") && approach.value()!=string("orthoPLS"))
+		message(" Using method : PCA" << endl;);
+	
+	//perform an SVD on data	
+	outMsize(" New Data ", data);
+		
+	if(data.Nrows()>=data.Ncols())
+		SVD ( data, svd_Y_D, svd_Y_U, svd_Y_V );
+	else{
+		SVD ( data.t(), svd_Y_D, svd_Y_V, svd_Y_U );
+		svd_Y_U = svd_Y_U.t();
+		svd_Y_V = svd_Y_V.t();		
+	}	
+
+	dbgmsg(" Finished SVD : ");
+	outMsize("svd_Y_U",svd_Y_U);
+	outMsize("svd_Y_V",svd_Y_V);
+	outMsize("svd_Y_D",svd_Y_D);
+	
+	svd_Y_V = sqrtm(svd_Y_D) * svd_Y_V;	
+	svd_Y_U = svd_Y_U * sqrtm(svd_Y_D);	
+
+	if(approach.value()==string("SVD-CVA"))
+		svd_Y_V = svd_Y_V *tmpdata;
+	write_res();
+	
+	dbgmsg(" Leaving <do_work>");
+	
+	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(fnout);
+			options.add(approach);
+			options.add(fndesign);
+			options.add(fnmask);
+			options.add(normdes);
+			options.add(perfvn);
+			options.add(perf_demean);
+			options.add(help);
+			options.add(verbose);
+			options.add(debug);
+			options.add(outres);
+			options.add(outdata);
+			options.add(outvnscales);
+
+	    	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;
+	  } 
+}
+
-- 
GitLab