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 exception 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.octave_pkg_statistics_libsvm = externals.libsvm;
externals.octave_pkg_statistics_libsvm.is_present = @() ...
externals.octave_pkg_statistics.is_present() && ...
has_octave_statistics_svmtrain();
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]; % version 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 tf = has_octave_statistics_svmtrain()
% Check for presence of svmtran and svmpredict in the octave
% statistics toolbox. Unfortunately calling these functions
% in the libsvm-way may crash Octave.
svm_root = fileparts(fileparts(noerror_which('svmtrain')));
ttest_root = fileparts(noerror_which('ttest'));
tf = has_octave_package('statistics') && ...
~isempty(svm_root) && ...
isequal(ttest_root, svm_root) && ...
isequal(fileparts(noerror_which('svmtrain')), ...
fileparts(noerror_which('svmpredict')));
function version = get_libsvm_version()
if has_octave_statistics_svmtrain()
version = 325; % >=318
return
end
svm_root = fileparts(fileparts(noerror_which('svmpredict')));
svm_h_fn = fullfile(svm_root, 'svm.h');
if ~exist(svm_h_fn, 'file')
error('File not found %s; cannot determine libsvm version', ...
svm_h_fn);
end
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');