-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[ENH] Implement pure-Python ICA with mixture modeling #22
Conversation
aroma/aroma.py
Outdated
) | ||
os.system(math_command) | ||
if not mask: | ||
mask = masking.compute_epi_mask(in_file) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More aggressive masking seems to be necessary. I noticed that the ICA components were showing up outside the brain with the old method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good to me. We should make sure that the outputs are not too different from the original workflow and update the integration test accordingly.
The results are very different. For one thing, the MELODIC outputs include both positive and negative values, while the GGM outputs only include positive values. The GGM from Here is an example using the data in our current (at least in this branch) tests folder: MELODICPython |
It's probable that MELODIC does additional processing on the maps before thresholding them, but it's also possible that the core mixture model procedure is different. I honestly don't know how to figure out either problem, but having a pure Python ICA approach that satisfies the AROMA classifier is crucial for non-fMRIPrep users (e.g., folks who want to supplement their tedana classification with AROMA). TL;DR: I'm stumped. |
I think we should have a hackathon and look at the small details in MELODIC! I'm currently busy but I might find some time for this in the near future.
We'll figure it out. |
Hi @tsalo . I've just been looking through the MELODIC code. Here's a summary of what MELODIC is doing: So, for each of the IC maps, it's doing: 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); If the data is normalized to std of 1, it adds the standard deviation back. if(opts.varnorm.value()&&melodat.get_stdNoisei().Storage()>0){
ICmap = SP(melodat.get_IC().Row(ctr),diagvals(ctr)*melodat.get_stdNoisei());
}else{
ICmap = melodat.get_IC().Row(ctr);
}
string wherelog;
if(opts.genreport.value())
wherelog = report.getDir();
else
wherelog = logger.getDir();
It calculates the mixture-model fit with a GGM and it saved the probability map and the model fit. dbgmsg(" ICmap max : "<< mean(ICmap,2).AsScalar() << endl);
mixmod.setup( ICmap,
wherelog,ctr,
melodat.get_mask(),
melodat.get_mean(),3);
message(" calculating mixture-model fit "<<endl);
mixmod.fit("GGM");
if(opts.output_MMstats.value()){
message(" saving probability map: ");
melodat.save4D(mixmod.get_probmap(),
string("stats/probmap_")+num2str(ctr));
message(" saving mixture model fit:");
melodat.saveascii(mixmod.get_params(),
string("stats/MMstats_")+num2str(ctr));
}
It z-scores the spatial maps. //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);
}
It perfmors smoothing on the probability maps. 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());
}
It thresholds and flips the map if there are more negative values than positive ones. 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);
}
It saved the results. //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;
message(" saving thresholded Z-stats image:");
melodat.save4D(mixmod.get_threshmaps(),
string("stats/thresh_zstat")+num2str(ctr));
}
//save mmpars
// mmpars((ctr-1)*5+1,1) = ctr;
// 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();
It creates the report. if( bool(opts.genreport.value()) ){
message(" creating report page ... ");
report.IC_rep(mixmod,ctr,melodat.get_IC().Nrows(),melodat.get_ICstats());
message(" done" << endl);
}
} |
So, to answer to this: yes, MELODIC is smoothing before thresholding. Here's the actual smoothing that it performs: template <class T>
volume<T> smooth(const volume<T>& source, float sigma_mm)
{
float sigmax, sigmay, sigmaz;
sigmax = sigma_mm/source.xdim();
sigmay = sigma_mm/source.ydim();
sigmaz = sigma_mm/source.zdim();
int nx=((int) (sigmax-0.001))*2 + 3;
int ny=((int) (sigmay-0.001))*2 + 3;
int nz=((int) (sigmaz-0.001))*2 + 3;
ColumnVector kernelx, kernely, kernelz;
kernelx = gaussian_kernel1D(sigmax,nx);
kernely = gaussian_kernel1D(sigmay,ny);
kernelz = gaussian_kernel1D(sigmaz,nz);
return convolve_separable(source,kernelx,kernely,kernelz);
} As you can see, it is just a Gaussian kernel applied to each of the dimensions. |
Per #54, I'm going to resolve the conflicts in this PR and then switch the target branch to a new one, so that @eurunuela can work on it directly. |
Changes from the merge:
|
Merging into |
Closes #1.
TODO:
nipy
's GGM.computefeats2
working).Changes proposed in this pull request:
nipy
for mixture modeling.tedana
for PCA + ICA.