function is_ok=cosmo_check_external(external, raise_)
% Checks whether a certain external toolbox exists, or list citation info
%
% is_ok=cosmo_check_external(external[, raise_])
%
% Inputs:
% external string or cell of strings. Currently supports:
% 'afni' AFNI matlab toolbox
% 'afni_bin' AFNI binaries present (unix-only)
% 'neuroelf' Neuroelf toolbox
% 'nifti' NIFTI toolbox
% 'fieldtrip' Fieldtrip
% 'libsvm' libSVM toolbox
% 'surfing' surfing toolbox
% 'gifti' GIfTI library for matlab
% 'xunit' xUnit unit test framework
% 'moxunit' MOxUnit unit test framework
% 'matlabsvm' SVM classifier in matlab stats
% toolbox (prior 2018a)
% 'matlabcsvm'
% 'svm' Either matlabsvm or libsvm
% '@{name}' Matlab toolbox {name}
% It can also be '-list', '-tic', '-toc',' or
% '-cite'; see below for their meaning.
% raise_ if true (the default), an error is raised if the
% external is not present.
%
% Returns:
% is_ok boolean indicating whether the external is
% present. A matlab toolbox must be prefixed
% by a '@'. If external is a cell if P elements,
% then the output is a Px1 boolean vector.
% Special switches allowed are:
% '-list': returns a cell of strings with
% the available externals
% '-tic': reset list of cached externals
% (see note below)
% '-toc': returns a cell of string of
% all externals queried so far
% '-cite': prints a list of publications to
% cite based on the output from
% '-toc'
%
% Examples:
% % see if the AFNI matlab toolbox is available, if not raise an error
% cosmo_check_external('afni')
%
% % see if libsvm and neuroelf are available, if not raise an error
% cosmo_check_external({'libsvm','neuroelf'});
%
% % see if libsvm and neuroelf and store the result in
% % the 2x1 boolean array is_ok. An error is not raised if
% % either is not present.
% is_ok=cosmo_check_external({'libsvm','neuroelf'},false);
%
% % see if the matlab 'stats' toolbox is available
% cosmo_check_external('@stats');
%
% % list the available externals
% cosmo_check_external('-list')
%
% % reset the list of cached externals, so that using '-cite' below
% % will only show externals checked since this reset
% cosmo_check_external('-tic')
%
% % check two externals
% cosmo_check_external({'afni','neuroelf'});
%
% % list the externals checked for since the last '-tic'
% cosmo_check_external('-toc')
%
% % list the publications associated with the externals
% cosmo_check_external('-cite');
%
% # For CoSMoMVPA's copyright information and license terms, #
% # see the COPYING file distributed with CoSMoMVPA. #
persistent cached_present_names;
if isnumeric(cached_present_names)
cached_present_names=cell(0);
end
if nargin<2
raise_=true;
end
if iscell(external)
% cell input - check for each of them using recursion
nexternals=numel(external);
is_ok=false(nexternals,1); % space for output
me=str2func(mfilename()); % the present function
for k=1:nexternals
is_ok(k)=me(external{k}, raise_);
end
return
end
if external(1)=='-'
% process special user switch
switch external(2:end)
case 'list'
% return a list of externals
supported_externals=fieldnames(get_externals());
me=str2func(mfilename()); % the present function
cached_present_names_copy=cached_present_names;
msk=me(supported_externals,false);
cached_present_names=cached_present_names_copy;
is_ok=supported_externals(msk);
case 'tic'
cached_present_names=cell(0);
case 'toc'
is_ok=cached_present_names;
case 'cite'
citation_str=get_citation_str(cached_present_names);
s=sprintf(['If you use CoSMoMVPA and/or some '...
'other toolboxes for a publication, '...
'please cite:\n\n%s\n'], citation_str);
if nargout==0
disp(s);
is_ok=[];
else
is_ok=s;
end
otherwise
error('illegal switch %s', external);
end
return
end
if external(1)=='@'
toolbox_name=external(2:end);
is_ok=check_matlab_toolbox(toolbox_name,raise_);
elseif external(1)=='!'
command_name=external(2:end);
is_ok=check_which(command_name,raise_);
external='';
else
is_ok=check_external_toolbox(external,raise_);
end
if is_ok && ...
~isempty(external) && ...
~cosmo_match({external},cached_present_names)
cached_present_names{end+1}=external;
end
function is_ok=check_which(command_name,raise_)
is_ok=exist(command_name,'builtin') || ...
~isempty(which(command_name));
if ~is_ok && raise_
error('Function ''%s'' is not available', command_name);
end
function is_ok=check_external_toolbox(external_name,raise_)
externals=get_externals();
if ~isfield(externals, external_name)
error('Unknown external ''%s''', external_name);
end
ext=externals.(external_name);
if iscell(ext)
% at least one of them must be ok
is_ok=false;
for j=1:numel(ext)
is_ok=is_ok || check_external_toolbox(ext{j},false);
end
if ~is_ok && raise_
error('None of the following externals was found: %s',...
cosmo_strjoin(ext,', '));
end
return
end
if isempty(cosmo_strsplit(external_name,'_bin',-1))
% binary package
env='system';
else
env=cosmo_wtf('environment');
end
error_msg=[];
% simulate goto statement
while true
if ~ext.is_present()
suffix=ext_get_what_to_do_message(ext,env);
error_msg=sprintf(['%s is required, but it was not '...
'found in the %s path. If it is not present on your '...
'system, obtain it from:\n\n %s\n\nthen, %s.'], ...
ext.label(), env, url2str(ext.url), suffix);
break;
end
if ~ext.is_recent()
suffix=ext_get_what_to_do_message(ext,env);
error_msg=sprintf(['%s was found on your %s path, but '...
'seems out of date. Please download the latest '...
'version from:\n\n %s\n\nthen, %s.'], ...
ext.label(), env, url2str(ext.url), suffix);
break;
end
if isfield(ext,'conflicts')
conflicts=ext.conflicts;
names=fieldnames(conflicts);
for k=1:numel(names)
name=names{k};
if ~externals.(name).is_present()
continue;
end
conflict=conflicts.(name);
if conflict()
trouble_maker=externals.(name).label();
me=ext.label();
error_msg=sprintf(['The %s conflicts with the %s, '...
'making the %s unusable. You may '...
'have to change the %s path so '...
'that the location of the %s comes '...
'below (after) that of the %s.'],...
trouble_maker,me,me,...
env,trouble_maker,me);
break;
end
end
if ~isempty(error_msg)
break;
end
end
break;
end
is_ok=isempty(error_msg);
if ~is_ok && raise_
error(error_msg);
end
function msg=ext_get_what_to_do_message(ext,env)
if isfield(ext, 'what_to_do')
msg=ext.what_to_do();
else
msg=sprintf(['if applicable, add the necessary directories '...
'to the %s path'], env);
end
function is_ok=check_matlab_toolbox(toolbox_name,raise_)
if cosmo_wtf('is_matlab')
toolbox_dir=fullfile(toolboxdir(''),toolbox_name);
% directory must exist and be in the path
is_ok=isdir(toolbox_dir) && toolbox_in_matlab_path(toolbox_dir);
else
is_ok=false;
end
if ~is_ok && raise_
error('The matlab toolbox ''%s'' seems absent',...
toolbox_name);
end
function tf=toolbox_in_matlab_path(toolbox_dir)
persistent cached_toolbox_dir;
persistent cached_tf;
persistent cached_path;
cur_path=path();
if ~(isequal(cur_path, cached_path) && ...
isequal(toolbox_dir, cached_toolbox_dir))
toolbox_dir_esc=toolbox_dir;
if isequal(filesep,'\')
toolbox_dir_esc=strrep(toolbox_dir_esc,'\','\\');
end
paths=cosmo_strsplit(path(),pathsep());
starts_with_toolbox_dir=@(x)isempty(...
cosmo_strsplit(x,toolbox_dir_esc,1));
cached_tf=any(cellfun(starts_with_toolbox_dir,paths));
cached_path=cur_path;
cached_toolbox_dir=toolbox_dir;
end
tf=cached_tf;
function s=url2str(url)
if strcmp(cosmo_wtf('environment'),'matlab')
s=sprintf('<a href="%s">%s</a>',url,url);
else
s=url;
end
function w=noerror_which(varargin)
% Octave raises an expection when 'which' is called and a mex-file of
% incompatible architecture is found
w='';
try
w=which(varargin{:});
catch
% do nothing
end
function externals=get_externals()
persistent cached_externals;
if ~isstruct(cached_externals)
cached_externals=get_externals_helper();
end
externals=cached_externals;
function externals=get_externals_helper()
% helper function that defines the externals.
externals=struct();
yes=@() true;
has=@(x) ~isempty(noerror_which(x));
has_toolbox=@(x)check_matlab_toolbox(x,false);
path_of=@(x) fileparts(noerror_which(x));
is_in_path=@(x)has(x) && cosmo_match({path_of(x)},...
cosmo_strsplit(path(),pathsep()));
externals.cosmo.is_present=@()is_in_path(mfilename());
externals.cosmo.is_recent=yes;
externals.cosmo.label='CoSMoMVPA toolbox';
externals.cosmo.url='http://cosmomvpa.org';
externals.cosmo.authors={'N. N. Oosterhof','A. C. Connolly', ...
'J. V. Haxby'};
externals.cosmo.year='2016';
externals.cosmo.ref=['CoSMoMVPA: multi-modal multivariate pattern '...
'analysis of neuroimaging data in '...
'Matlab / GNU Octave. '...
'Frontiers in Neuroinformatics, '...
'doi:10.3389/fninf.2016.00027.'];
externals.afni_bin.is_present=@() isunix() && ...
~unix(['which afni > /dev/null && '...
'which 3dresample > /dev/null && '...
'afni --version >/dev/null']);
externals.afni_bin.is_recent=yes;
externals.afni_bin.label='AFNI binaries';
externals.afni_bin.url='http://afni.nimh.nih.gov/afni';
externals.afni_bin.authors={'R. W. Cox'};
externals.afni_bin.ref=['AFNI: Software for analysis and '...
'visualization of functional magnetic '...
'resonance neuroimages. Computers and '...
'Biomedical Research, 29: 162-173, 1996'];
externals.afni_bin.what_to_do=['consider the environment you are '...
'running Matlab from. It may '...
'be required to start matlab '...
'from the shell'];
externals.afni.is_present=@() has('BrikLoad');
% Octave requires a more recent version of AFNI, whereas there is
% currently no need for Matlab users to upgrade
externals.afni.is_recent=@() has('afni_swapbytes');
externals.afni.label='AFNI Matlab library';
externals.afni.url='https://github.com/afni/AFNI';
externals.afni.authors={'Z. Saad','G. Chen'};
externals.neuroelf.is_present=@() has('xff');
externals.neuroelf.is_recent=yes;
externals.neuroelf.label='NeuroElf toolbox';
externals.neuroelf.url='http://neuroelf.net';
externals.neuroelf.authors={'J. Weber'};
externals.nifti.is_present=@() has('load_nii');
externals.nifti.is_recent=yes;
externals.nifti.label='NIFTI toolbox';
externals.nifti.url=['http://www.mathworks.com/matlabcentral/',...
'fileexchange/8797-tools-for-nifti-and-analyze-image'];
externals.nifti.authors={'J. Shen'};
externals.fieldtrip.is_present=@() has('ft_defaults');
% in the future, may require from 2014 onwards
%externals.fieldtrip.is_recent=getfield(dir(which('ft_databrowser')),...
% 'datenum')>datenum(2014,1,1);
externals.fieldtrip.is_recent=yes;
externals.fieldtrip.label='FieldTrip toolbox';
externals.fieldtrip.url='http://fieldtrip.fcdonders.nl';
externals.fieldtrip.authors={'R. Oostenveld','P. Fries','E. Maris',...
'J.-M. Schoffelen'};
externals.fieldtrip.year='2011';
externals.fieldtrip.ref=['FieldTrip: Open Source Software for '...
'Advanced Analysis of MEG, EEG, and '...
'Invasive Electrophysiological Data, '...
'Computational Intelligence and '...
'Neuroscience, vol. 2011, ',...
'Article ID 156869, 9 pages.',...
'doi:10.1155/2011/156869'];
externals.eeglab.is_present=@() has('eeglab') ...
&& has('pop_loadset');
externals.eeglab.is_recent=yes;
externals.eeglab.label='EEGLAB toolbox';
externals.eeglab.url='https://sccn.ucsd.edu/eeglab/';
externals.eeglab.authors={'A. Delorme','S. Makeig'};
externals.eeglab.year='2004';
externals.eeglab.ref=['EEGLAB: an open source toolbox for analysis '...
'of single-trial EEG dynamics. '...
'Journal of Neuroscience Methods 134:9-21'];
externals.libsvm.is_present=@() has('svmpredict') && ...
has('svmtrain');
% require version 3.18 or later, because it has a 'quiet' option
% for svmpredict
externals.libsvm.is_recent=@() get_libsvm_version()>=318;
externals.libsvm.label='LIBSVM toolbox';
externals.libsvm.url='https://github.com/cjlin1/libsvm';
externals.libsvm.authors={'C.-C. Chang', 'C.-J. Lin'};
externals.libsvm.year='2011';
externals.libsvm.ref=['LIBSVM: '...
'a library for support vector machines. '...
'ACM Transactions on Intelligent Systems '...
'and Technology, 2:27:1--27:27, 2011'];
externals.libsvm.conflicts.neuroelf=@() isequal(...
path_of('svmtrain'),...
fileparts(path_of('xff')));
externals.libsvm.conflicts.matlabsvm=@() ~isequal(...
path_of('svmpredict'),...
path_of('svmtrain'));
externals.surfing.is_present=@() has('surfing_voxelselection');
% require recent version with surfing_write
externals.surfing.is_recent=@() has('surfing_write') && ...
has('surfing_nodeselection') && ...
get_surfing_version() >= .5;
externals.surfing.label='Surfing toolbox';
externals.surfing.url='http://github.com/nno/surfing';
externals.surfing.authors={'N. N. Oosterhof','T. Wiestler',...
'J. Diedrichsen'};
externals.surfing.year='2011';
externals.surfing.ref=['A comparison of volume-based and '...
'surface-based multi-voxel pattern '...
'analysis. Neuroimage 56 (2), 593-600'];
externals.gifti.is_present=@() has('gifti');
externals.gifti.is_recent=yes;
externals.gifti.label='MATLAB/Octave GIfTI Library';
externals.gifti.url='https://github.com/gllmflndn/gifti';
externals.gifti.authors={'G. Flandin'};
externals.xunit.is_present=@() has('runtests') && ...
has('VerboseTestRunDisplay');
externals.xunit.is_recent=yes;
externals.xunit.label='MATLAB xUnit Test Framework';
externals.xunit.url=['http://www.mathworks.it/matlabcentral/'...
'fileexchange/22846-matlab-xunit-test-framework'];
externals.xunit.authors={'S. Eddins'};
externals.matlab.is_present=@() cosmo_wtf('is_matlab');
externals.matlab.is_recent=yes;
externals.matlab.label=@() sprintf('Matlab %s',cosmo_wtf('version'));
externals.matlab.url='http://www.mathworks.com';
externals.matlab.authors={'The Mathworks, Natick, MA, United States'};
externals.octave.is_present=@() cosmo_wtf('is_octave');
externals.octave.is_recent=yes;
externals.octave.label=@() sprintf('GNU Octave %s',...
cosmo_wtf('version'));
externals.octave.url='http://www.gnu.org/software/octave/';
externals.octave.authors={'Octave community'};
externals.matlabsvm.is_present=@() (has_toolbox('stats') || ...
has_toolbox('bioinfo')) && ...
has('svmtrain') && ...
has('svmclassify') && ...
is_matlab_prior_2018a();
externals.matlabsvm.is_recent=yes;
externals.matlabsvm.conflicts.neuroelf=@() isequal(...
path_of('svmtrain'),...
fileparts(path_of('xff')));
externals.matlabsvm.conflicts.libsvm=@() ~isequal(...
path_of('svmtrain'),...
path_of('svmclassify'));
externals.matlabsvm.label='Matlab stats or bioinfo toolbox';
externals.matlabsvm.url='http://www.mathworks.com';
externals.matlabcsvm.is_present=@() cosmo_wtf('is_matlab') && ...
has('fitcsvm');
externals.matlabcsvm.is_recent=yes;
externals.matlabcsvm.label='Matlab stats or bioinfo toolbox';
externals.matlabcsvm.url='http://www.mathworks.com';
externals.svm={'libsvm', 'matlabsvm'}; % need either
externals.distatis.is_present=yes;
externals.distatis.is_recent=yes;
externals.distatis.label='DISTATIS CoSMoMVPA implementation';
externals.distatis.url=externals.cosmo.url;
externals.distatis.authors={'Abdi, H.','Valentin, D.',...
'O''Toole, A. J.','Edelman, B.'};
externals.distatis.year='2005';
externals.distatis.ref=['DISTATIS: The analysis of multiple '...
'distance matrices. In Proceedings of the '...
'IEEE Computer Society: International '...
'conference on computer vision and '...
'pattern recognition, San Diego, CA, USA '...
'(pp. 42-47)'];
externals.fast_marching.is_present=@() has(...
'perform_front_propagation_mesh');
externals.fast_marching.is_recent=yes;
externals.fast_marching.label=['toolbox fast marching [included '...
'in surfing]'];
externals.fast_marching.authors={'Gabriel Peyre'};
externals.fast_marching.ref=['Toolbox Fast Marching - A toolbox '...
'Fast Marching and level '...
'sets computations [https://www.'...
'ceremade.dauphine.fr/'...
'~peyre/matlab/fast-marching/'...
'content.html]'];
externals.fast_marching.url=externals.surfing.url;
externals.moxunit.is_present=@() has('moxunit_runtests');
externals.moxunit.is_recent=yes;
externals.moxunit.label=['Matlab/Octave MOxUnit '...
'Test Framework'];
externals.moxunit.authors={'N. N. Oosterhof'};
externals.moxunit.url='https://github.com/MOxUnit/MOxUnit';
externals.moxunit.conflicts.xunit=@() same_path({'runtests',...
'initTestSuite'});
externals.octave_pkg_parallel.is_present=@() has_octave_package(...
'parallel');
externals.octave_pkg_parallel.is_recent=yes;
externals.octave_pkg_parallel.label=['GNU Octave parallel package'];
externals.octave_pkg_parallel.authors={'Hayato Fujiwara',...
'Jaroslav Hajek, Olaf Till'};
externals.octave_pkg_parallel.url=['http://http://octave.'...
'sourceforge.net/parallel/'];
externals.octave_pkg_statistics.is_present=@() has_octave_package(...
'statistics');
externals.octave_pkg_statistics.is_recent=yes;
externals.octave_pkg_statistics.label=['GNU Octave statistics '...
'package'];
externals.octave_pkg_statistics.authors={'Arno Onken'};
externals.octave_pkg_statistics.url=['http://http://octave.'...
'sourceforge.net/statistics/'];
externals.mocov.is_present=@() has('mocov');
externals.mocov.is_recent=yes;
externals.mocov.label=['Matlab/Octave MOcov '...
'Coverage report generator'];
externals.mocov.authors={'N. N. Oosterhof'};
externals.mocov.url='https://github.com/MOcov/MOcov';
externals.modox.is_present=@() has('modox_runtests');
externals.modox.is_recent=yes;
externals.modox.label=['Matlab/Octave MOdox '...
'documentation test framework'];
externals.modox.authors={'N. N. Oosterhof'};
externals.modox.url='https://github.com/MOdox/MOdox';
function tf=is_matlab_prior_2018a()
this_version=cosmo_wtf('version_number');
matlab_pivot=[9, 4]; % verison 2018a
n_elem = numel(matlab_pivot);
delta = this_version(1:n_elem) - matlab_pivot;
if all(delta==0)
% this is version 2018a
tf=false;
else
idx=find(delta~=0,1);
% positive delta means that the current matlab version
% is later than 2018a
tf=delta(idx)<0;
end
function tf=has_octave_package(label)
tf=false;
if ~cosmo_wtf('is_octave')
return;
end
result=pkg('list');
if isempty(result)
return;
end
idx_label=find(cellfun(@(x) strcmp(x.name,label),result));
if isempty(idx_label)
return;
end
result=result(idx_label);
assert(numel(result)==1);
tf=result{1}.loaded;
function tf=same_path(args)
pths=cellfun(@(x)fileparts(which(x)),args,'UniformOutput',false);
tf=all(cellfun(@(x)isequal(x,pths{1}),pths(2:end)));
function version=get_libsvm_version()
svm_root=fileparts(fileparts(noerror_which('svmpredict')));
svm_h_fn=fullfile(svm_root,'svm.h');
fid=fopen(svm_h_fn);
if fid<0
error('Unable to open %s; cannot determine libsvm version',...
svm_h_fn);
end
c=onCleanup(@()fclose(fid));
chars=fread(fid,Inf,'char=>char');
lines=cosmo_strsplit(chars','\n');
for k=1:numel(lines)
sp=cosmo_strsplit(lines{k},'LIBSVM_VERSION');
if numel(sp)>1
version=str2double(sp{end});
return
end
end
error('Could not find LIBSVM version in %s', svm_h_fn);
function version=get_surfing_version()
surfing_root=fileparts(fileparts(noerror_which(...
'surfing_voxelselection')));
surfing_version_fn=fullfile(surfing_root,'VERSION');
fid=fopen(surfing_version_fn);
if fid<0
if ~exist(surfing_version_fn,'file')
version=0;
return;
else
error('Unable to open %s, cannot determine surfing version',...
surfing_version_fn);
end
end
c=onCleanup(@()fclose(fid));
chars=fread(fid,Inf,'char=>char')';
v_str=regexp(chars,'version\s+([\d\.]*)','tokens');
if iscell(v_str)
v_str=v_str{1};
end
version=str2double(v_str);
function c=add_to_cell(c, v)
if ~cosmo_match({v},c)
c{end+1}=v;
end
function citation_str=get_citation_str(cached_present_names)
% always cite CoSMoMVPA
present_names=cached_present_names;
present_names=add_to_cell(present_names,'cosmo');
if cosmo_wtf('is_matlab')
present_names=add_to_cell(present_names,'matlab');
end
if cosmo_wtf('is_octave')
present_names=add_to_cell(present_names,'octave');
end
externals=get_externals();
n=numel(present_names);
cites=cell(n,1);
cites_msk=false(n,1);
for k=1:n
external_name=present_names{k};
if ~isfield(externals,external_name)
% built-in
continue;
end
external=externals.(external_name);
if ~isfield(external,'authors')
continue;
end
if isfield(external,'ref')
% reference provided, use label to prefix URL
title_str=external.ref;
url_prefix_str=sprintf('%s ', external.label);
else
% no reference, use label as title and no prefix for URL
title_str=external.label();
url_prefix_str='';
end
author_str=cosmo_strjoin(external.authors,', ');
if isfield(external,'year')
author_str=sprintf('%s (%s)', author_str, external.year);
end
cites{k}=sprintf('%s. %s. %savailable online from %s',...
author_str,...
title_str, url_prefix_str, external.url);
cites_msk(k)=true;
end
citation_str=cosmo_strjoin(cites(cites_msk),'\n\n');