function test_suite = test_surficial_io_niml_dset
% tests for AFNI NIML 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_niml_dset_dataset_io()
if cosmo_skip_test_if_no_external('afni')
return
end
ds = get_niml_dataset();
fn = cosmo_make_temp_filename('_tmp', '.niml.dset');
cleaner = onCleanup(@()delete(fn));
formats = {'ascii', 'binary', 'binary.lsbfirst', 'binary.msbfirst'};
n_formats = numel(formats);
for k = 1:n_formats
format = formats{k};
niml_str = get_niml_dset_string(format);
fid = fopen(fn, 'w');
fwrite(fid, niml_str);
fclose(fid);
ds2 = cosmo_surface_dataset(fn);
assert_dataset_equal(ds, ds2);
cosmo_map2surface(ds, fn, 'encoding', format);
ds3 = cosmo_surface_dataset(fn);
assert_dataset_equal(ds, ds3);
% try mapping using functionality in AFNI Matlab library
s = struct();
s.data = ds.samples';
s.node_indices = ds.a.fdim.values{1}(ds.fa.node_indices) - 1;
s.stats = ds.sa.stats;
s.labels = ds.sa.labels;
afni_niml_writesimple(s, fn, format);
ds4 = cosmo_surface_dataset(fn);
assert_dataset_equal(ds, ds4);
% ensure that mapping to struct works
s2 = cosmo_map2surface(ds, '-niml_dset', 'encoding', format);
assertEqual(s2, s);
end
function assert_dataset_equal(x, y)
assertElementsAlmostEqual(x.samples, y.samples, 'absolute', 1e-4);
assertEqual(sort(fieldnames(x)), sort(fieldnames(y)));
assertEqual(x.fa, y.fa);
assertEqual(x.sa, y.sa);
assertEqual(x.a, y.a);
function ds = get_niml_dataset()
ds = cosmo_synthetic_dataset('ntargets', 2, 'nchunks', 1, ...
'type', 'surface');
ds.a.fdim.values{1} = 1 + [1 4 7 8 5 3];
ds.sa = struct();
ds.sa.stats = {'Ftest(3,4)'; 'Zscore()'};
% TODO: use these complicated labels
% ds.sa.labels={'label1<>"&''';'label2'};
ds.sa.labels = {'label1'; 'label2'};
function string = get_niml_dset_string(format)
string = [ ...
get_niml_dset_header() ...
get_niml_dset_data(format) ...
get_niml_dset_footer()
];
function string = get_niml_dset_header()
string = bytes_printf([ ...
'<AFNI_dataset\n' ...
' dset_type="Node_Bucket"\n' ...
' self_idcode="XYZ_QJCWHYMMSUIUXCMBUZJASSJT"\n' ...
' filename="data.niml.dset"\n' ...
' label="data.niml.dset"\n' ...
' ni_form="ni_group" >\n' ...
]);
function string = get_niml_dset_footer()
string = bytes_printf([ ...
'<AFNI_atr\n' ...
' atr_name="COLMS_RANGE"\n' ...
' ni_type="String"\n' ...
' ni_dimen="1" >\n' ...
'"-3.6849477 2.0316862 1 0;-1.3264908 2.3386572 2 4"'...
'</AFNI_atr>\n' ...
'<AFNI_atr\n' ...
' atr_name="COLMS_LABS"\n' ...
' ni_type="String"\n' ...
' ni_dimen="1" >\n' ...
'"label1;label2"</AFNI_atr>\n' ...
'<AFNI_atr\n' ...
' atr_name="COLMS_TYPE"\n' ...
' ni_type="String"\n' ...
' ni_dimen="1" >\n' ...
'"Generic_Float;Generic_Float"</AFNI_atr>\n' ...
'<AFNI_atr\n' ...
' atr_name="COLMS_STATSYM"\n' ...
' ni_type="String"\n' ...
' ni_dimen="1" >\n' ...
'"Ftest(3,4);Zscore()"</AFNI_atr>\n' ...
'</AFNI_dataset>\n' ...
]);
% TODO: use these complicated labels:
% '"label1<>"&';label2"</AFNI_atr>\n' ...
function bytes = get_niml_dset_data(format)
ds = get_niml_dataset();
data = ds.samples;
nodes_base0 = ds.a.fdim.values{1}(ds.fa.node_indices) - 1;
[nrows, ncols] = size(data);
row_sep = sprintf('\n');
col_sep = ' ';
switch lower(format)
case 'ascii'
data_pat = [repmat(['%.6f' col_sep], 1, nrows) row_sep];
bucket_data = uint8(sprintf(data_pat, data));
node_pat = ['%d' row_sep];
node_data = uint8(sprintf(node_pat, nodes_base0));
ni_form = '';
case {'binary', 'binary.lsbfirst', 'binary.msbfirst'}
data_single = single(data(:)');
node_int = int32(nodes_base0(:)');
[unused, unused, endian] = computer();
computer_format = sprintf('binary.%ssbfirst', lower(endian));
do_swap = ~strcmp(format, 'binary') && ...
~strcmp(format, computer_format);
if do_swap
if cosmo_wtf('is_octave')
% Octave 3.8 does not support byteswap for single
data_single_int = typecast(data_single, 'int32');
data_single_int_swap = swapbytes(data_single_int);
data_single = typecast(data_single_int_swap, 'single');
else
data_single = swapbytes(data_single);
end
node_int = swapbytes(node_int);
end
if strcmp(format, 'binary')
ni_form_format = computer_format;
else
ni_form_format = format;
end
bucket_data = typecast(data_single, 'uint8');
node_data = typecast(node_int, 'uint8');
ni_form = sprintf(' ni_form="%s"\n', ni_form_format);
otherwise
error('unsupported format %s', format);
end
bytes = [ ...
bytes_printf([ ...
'<SPARSE_DATA\n' ...
' data_type="Node_Bucket_data"\n' ...
' ni_type="2*float"\n' ...
ni_form ...
' ni_dimen="6" >'])...
bucket_data ...
bytes_printf([ ...
'</SPARSE_DATA>\n' ...
'<INDEX_LIST\n' ...
' data_type="Node_Bucket_node_indices"\n' ...
' sorted_node_def="No"\n' ...
' COLMS_RANGE="1 8 0 3"\n' ...
' COLMS_LABS="Node Indices"\n' ...
' COLMS_TYPE="Node_Index"\n' ...
' ni_type="int"\n' ...
ni_form ...
' ni_dimen="6" >']) ...
node_data ...
bytes_printf([ ...
'</INDEX_LIST>\n']) ...
];
function b = bytes_printf(varargin)
b = uint8(sprintf(varargin{:}));