function test_suite=test_surface_io()
% tests for surface input/output
%
% # For CoSMoMVPA's copyright information and license terms, #
% # see the COPYING file distributed with CoSMoMVPA. #
try % assignment of 'localfunctions' is necessary in Matlab >= 2016
test_functions=localfunctions();
catch % no problem; early Matlab versions can use initTestSuite fine
end
initTestSuite;
function test_surface_dataset_gifti()
if cosmo_skip_test_if_no_external('gifti')
return;
end
save_and_load('gii');
function test_surface_dataset_niml_dset()
if cosmo_skip_test_if_no_external('afni')
return;
end
save_and_load('niml_dset');
function test_surface_dataset_bv_smp()
if cosmo_skip_test_if_no_external('neuroelf')
return;
end
save_and_load('bv_smp');
function test_surface_dataset_pymvpa()
save_and_load('pymvpa');
function test_surface_io_exceptions()
aet_in=@(varargin)assertExceptionThrown(@()...
cosmo_surface_dataset(varargin{:}),'');
aet_out=@(varargin)assertExceptionThrown(@()...
cosmo_map2surface(varargin{:}),'');
tmp_fn=cosmo_make_temp_filename();
aet_in(tmp_fn);
aet_in(struct());
aet_in({});
ds=cosmo_synthetic_dataset('type','surface');
aet_out(struct,'-bv_smp');
aet_out(struct,'-gii');
aet_out(struct,'-niml_dset');
aet_out(ds,tmp_fn);
aet_out(ds,'-foo');
aet_out(ds,struct());
aet_out(ds,{});
function test_pymvpa_3d_string_array()
py_ds=struct();
py_ds.samples=rand(3,4);
py_ds.fa.node_indices=[0,3,4,6];
sa_labels={'foo';'bar';'foobaz'};
sa_labels_3d=reshape(strvcat(sa_labels),[3 1 6]);
py_ds.sa.labels=sa_labels_3d;
ds=cosmo_surface_dataset(py_ds);
assertEqual(ds.sa.labels,sa_labels);
function props=format2props(format)
f2p=struct();
f2p.gii.ext='.gii';
f2p.gii.writer=@(fn,x)save(x,fn);
f2p.gii.reader=@(fn)gifti(fn);
f2p.gii.cleaner=@do_nothing;
f2p.gii.isa=@(x)isa(x,'gifti');
f2p.bv_smp.ext='.smp';
f2p.bv_smp.writer=@(fn,x)x.SaveAs(fn);
f2p.bv_smp.reader=@read_bv_and_bless;
f2p.bv_smp.cleaner=@(x)x.ClearObject();
f2p.bv_smp.isa=@(x)isa(x,'xff');
f2p.niml_dset.ext='.niml.dset';
f2p.niml_dset.writer=@(fn,x)afni_niml_writesimple(x,fn);
f2p.niml_dset.reader=@(fn)afni_niml_readsimple(fn);
f2p.niml_dset.cleaner=@do_nothing;
f2p.niml_dset.isa=@(x)isstruct(x) && isfield(x,'node_indices');
f2p.pymvpa.ext='.mat';
f2p.pymvpa.writer=@write_mat_struct;
f2p.pymvpa.reader=@import_struct_data_with_error_if_illegal;
f2p.pymvpa.cleaner=@do_nothing;
f2p.pymvpa.map2surface=@(x,fn)write_mat_struct(fn,...
cosmo_map2surface(x,'','format','pymvpa'));
f2p.pymvpa.isa=@(x)isstruct(x) && ...
cosmo_isfield(x,{'samples'}) && ...
~cosmo_isfield(x,'a.fdim');
props=f2p.(format);
function x=read_bv_and_bless(fn)
x=xff(fn);
neuroelf_bless_wrapper(x);
function x=do_nothing(x)
% do nothing
function result=neuroelf_bless_wrapper(arg)
% deals with recent neuroelf (>v1.1), where bless is deprecated
s=warning('off','neuroelf:xff:deprecated');
resetter=onCleanup(@()warning(s));
result=bless(arg);
function write_mat_struct(fn,s)
save(fn,'-struct','s');
function s=import_struct_data_with_error_if_illegal(fn)
try
s=load(fn);
catch
error('Unable to read %s',fn);
end
function save_and_load(format)
ds=cosmo_synthetic_dataset('type','surface','nchunks',1);
nfeatures=size(ds.samples,2);
ds.fa.node_indices=ds.fa.node_indices(nonid_randperm(nfeatures));
if strcmp(format,'bv_smp')
xff_count_orig=helper_count_xff_objects();
% ensure count of objects does not increase
else
xff_count_orig=NaN;
% also permute node indices
ds.a.fdim.values{1}=ds.a.fdim.values{1}(nonid_randperm(nfeatures));
end
ds.sa=struct();
ds.sa.stats={'Ftest(3,4)';'Zscore()'};
ds.sa.labels={'label1';'label2'};
props=format2props(format);
ext=props.ext;
tmp_fn2=cosmo_make_temp_filename('_tmp',ext);
tmp_file_cleaner=onCleanup(@()delete(tmp_fn2));
if isfield(props,'map2surface')
mapper=props.map2surface;
else
mapper=@cosmo_map2surface;
end
helper_assert_memory_use_equal(format,xff_count_orig);
mapper(ds,tmp_fn2);
helper_assert_memory_use_equal(format,xff_count_orig);
ds2=cosmo_surface_dataset(tmp_fn2);
helper_assert_memory_use_equal(format,xff_count_orig);
assert_dataset_equal(ds,ds2,format);
if strcmp(format,'bv_smp')
ds2.a.fdim.values{1}=ds.a.fdim.values{1}(...
nonid_randperm(nfeatures));
assertExceptionThrown(@()cosmo_map2surface(ds2,tmp_fn2),'');
helper_assert_memory_use_equal(format,xff_count_orig);
end
o=cosmo_map2surface(ds,['-' format]);
writer=props.writer;
reader=props.reader;
cleaner=props.cleaner;
writer(tmp_fn2,o);
cleaner(o);
helper_assert_memory_use_equal(format,xff_count_orig);
o2=reader(tmp_fn2);
ds3=cosmo_surface_dataset(o2);
assert_dataset_equal(ds,ds3,format);
% extra format-specific tests
switch format
case 'gii'
o3=gifti(struct('cdata',o2.cdata));
ds4=cosmo_surface_dataset(o3);
ds3.a.fdim.values{1}=1:nfeatures;
assert_dataset_equal(ds3,ds4,format);
case 'niml'
o2=rmfield(o2,'node_indices');
ds4=cosmo_surface_dataset(o2);
ds3.a.fdim.values{1}=1:nfeatures;
assert_dataset_equal(ds3,ds4,format);
end
% clean up intermediate objects
cleaner(o2);
helper_assert_memory_use_equal(format,xff_count_orig);
% use format explicitly
o4=cosmo_map2surface(ds,'','format',format);
assert_isa_func=props.isa;
assert_isa_func(o4);
ds4=cosmo_surface_dataset(o4);
assert_dataset_equal(ds,ds4,format);
cleaner(o4);
% test with dataset attributes
targets=[3 4];
chunks=5;
ds4=cosmo_surface_dataset(ds,'targets',targets,'chunks',chunks);
ds4_expected=ds;
ds4_expected.sa.chunks=[chunks; chunks];
ds4_expected.sa.targets=targets(:);
assertEqual(ds4,ds4_expected);
% make illegal dataset
fid=fopen(tmp_fn2,'w');
fprintf(fid,'foo');
fclose(fid);
switch format
case 'bv_smp'
exception_io_failed='xff:XFFioFailed';
% neuroelf v<1.1 did not have the 'neuroelf:' prefix
exception_bad_content={'xff:BadFileContent',...
'neuroelf:xff:badFileContent'};
otherwise
exception_io_failed='';
exception_bad_content='';
end
% file should not be readable
assertExceptionThrown(@()reader(tmp_fn2),...
exception_io_failed);
% file should not exist
tmp_fn2=cosmo_make_temp_filename();
assert_helper_any_exception_thrown(@()reader(tmp_fn2),...
exception_bad_content);
helper_assert_memory_use_equal(format,xff_count_orig);
function helper_assert_memory_use_equal(format,xff_count_orig)
if strcmp(format,'bv_smp')
xff_count_final=helper_count_xff_objects();
delta=xff_count_final-xff_count_orig;
assert(delta==0,sprintf('count increased by %d',delta));
end
function rp=nonid_randperm(n)
assert(n>1);
while true
rp=randperm(n);
if ~isequal(rp,1:n)
return;
end
end
function assert_dataset_equal(x,y,format)
mp=cosmo_align(y.fa.node_indices,x.fa.node_indices);
mp2=cosmo_align(y.a.fdim.values{1},x.a.fdim.values{1});
z=cosmo_slice(y,mp(mp2),2);
z.a.fdim.values{1}=z.a.fdim.values{1}(mp2);
assertEqual(x.a,z.a);
assertEqual(x.fa,z.fa);
assertElementsAlmostEqual(x.samples,z.samples,'absolute',1e-4);
if ~strcmp(format,'gii')
assertEqual(x.sa,z.sa);
end
function assert_helper_any_exception_thrown(f, exception)
if ischar(exception)
assertExceptionThrown(f,exception);
return
end
assert(iscell(exception))
try
f();
error('no exception thrown');
catch
e=lasterror();
i=strmatch(e.identifier,exception,'exact');
if isempty(i)
error('None of the following exceptions was thrown: %s',...
cosmo_strjoin(exception,', '));
end
end
function count=helper_count_xff_objects
if cosmo_skip_test_if_no_external('!evalc')
return;
end
xff_str=evalc('xff()');
lines=cosmo_strsplit(xff_str,'\n');
pre_idx=strmatch(' # | Type | ',lines);
line_idxs=strmatch('------------------',lines);
if isempty(pre_idx)
% Neuroelf < v1.1, no objects
assert(isempty(line_idxs));
count=0;
return;
end
post_idx=line_idxs(line_idxs>(pre_idx+2));
assert(numel(pre_idx)==1);
if numel(post_idx)==0
count=0;
return;
end
assert(numel(post_idx)==1);
offset=2;
count=post_idx-pre_idx-offset;