function s=cosmo_map2surface(ds, fn, varargin)
% maps a dataset structure to AFNI/SUMA NIML dset or BV SMP file
%
% s=cosmo_map2surface(ds, output, ...)
%
% Inputs:
% ds dataset struct with surface-based data
% output String, indicating either the output format or the
% filename.
% - If output starts with a '-', then it must be one
% of:
% '-niml_dset' AFNI NIML
% '-bv_smp' BrainVoyager surface map
% '-gii' GIFTI
% - otherwise it must be a string indicating a file
% name, and end with one of
% '.niml.dset' AFNI NIML
% '.smp' BrainVoyager surface map
% '.gii' GIFTI
% 'encoding', e Optional encoding for AFNI NIML or GIFTI. Depending
% on the output format, supported values for e are:
% - NIML: 'ascii', 'binary',
% 'binary.lsbfirst', 'binary.msbfirst'
% (the 'binary' option uses the machine's native
% format with either the least or most significant
% byte first)
% - GIFTI: 'ASCII', 'Base64Binary',
% 'GZipBase64Binary'
% The encoding argument is ignored for BrainVoyager
% output.
% 'format', f Optional format to be used for output, if the
% output argument empty; one of 'niml_dset', 'bv_smp'
% or 'gii'.
%
% Output:
% s Structure containing the surface based data based
% on the output format; either a struct with
% NIML data, an xff object, or a GIFTI object.
%
% Examples:
% % (this example requires the AFNI Matlab toolbox)
% cosmo_skip_test_if_no_external('afni');
% %
% ds=cosmo_synthetic_dataset('type','surface');
% %
% % convert to AFNIML NIML format
% % (to store a file to disc, use a filename as the second argument)
% niml=cosmo_map2surface(ds,'-niml_dset');
% cosmo_disp(niml);
% %|| .node_indices
% %|| [ 0 1 2 3 4 5 ]
% %|| .data
% %|| [ 2.03 0.584 -1.44 -0.518 1.19 -1.33
% %|| -0.892 1.84 -0.262 2.34 -0.204 2.72
% %|| -0.826 1.17 -1.92 0.441 -0.209 0.148
% %|| 1.16 -0.848 3.09 1.86 1.76 0.502
% %|| 1.16 3.49 -1.37 0.479 -0.955 3.41
% %|| -1.29 -0.199 1.73 0.0832 0.501 -0.48 ]
%
% Notes:
% - this function is intended for datasets with surface data, i.e. with
% one or more values associated with each surface node. It does not
% support anatomical surface meshes that contain node coordinates and
% faces. To read and write such anatomical meshes, consider the surfing
% toolbox, github.com/nno/surfing
% - To load surface datasets, use cosmo_surface_dataset
%
% Dependencies:
% - for Brainvoyager files (.smp), it requires the NeuroElf
% toolbox, available from: http://neuroelf.net
% - for AFNI/SUMA NIML files (.niml.dset) it requires the AFNI
% Matlab toolbox, available from: https://github.com/afni/AFNI
%
% See also: cosmo_surface_dataset
%
% # For CoSMoMVPA's copyright information and license terms, #
% # see the COPYING file distributed with CoSMoMVPA. #
if ~ischar(fn)
error('second argument must be a string');
end
defaults=struct();
opt=cosmo_structjoin(defaults,varargin);
cosmo_check_dataset(ds,'surface');
formats=get_formats();
sp=cosmo_strsplit(fn,'-');
save_to_file=~isempty(sp{1});
if isfield(opt,'format')
fmt=opt.format;
elseif save_to_file
fmt=get_format(formats, fn);
else
if numel(sp)~=2
error('expected -{FORMAT}');
end
fmt=sp{2};
end
if ~isfield(formats,fmt)
error('Unsupported format %s', fmt);
end
format=formats.(fmt);
externals=format.externals;
cosmo_check_external(externals);
builder=format.builder;
s=builder(ds);
if nargout==0
cleaner=onCleanup(get_cleaner(format,s));
end
if save_to_file
writer=format.writer;
writer(fn, s, opt);
end
function f=get_cleaner(format,obj)
if isfield(format,'cleaner')
f=@()format.cleaner(obj);
else
f=@do_nothing;
end
function do_nothing()
% do nothing
function format=get_format(formats,fn)
endswith=@(s,e) isempty(cosmo_strsplit(s,e,-1));
names=fieldnames(formats);
for k=1:numel(names)
name=names{k};
exts=formats.(name).exts;
for j=1:numel(exts)
if endswith(fn,exts{j})
format=name;
return
end
end
end
error('Unsupported extension in %s', fn);
function formats=get_formats()
formats=struct();
formats.niml_dset.exts={'.niml.dset'};
formats.niml_dset.builder=@build_niml_dset;
formats.niml_dset.writer=@write_niml_dset;
formats.niml_dset.externals={'afni'};
formats.bv_smp.exts={'.smp'};
formats.bv_smp.builder=@build_bv_smp;
formats.bv_smp.writer=@write_bv_smp;
formats.bv_smp.cleaner=@(x)x.ClearObject();
formats.bv_smp.externals={'neuroelf'};
formats.gii.exts={'.gii'};
formats.gii.builder=@build_gifti;
formats.gii.writer=@write_gifti;
formats.gii.externals={'gifti'};
formats.pymvpa.exts={};
formats.pymvpa.builder=@build_pymvpa;
formats.pymvpa.writer=@write_pymvpa_struct;
formats.pymvpa.externals={};
function [data, node_indices]=get_surface_data_and_node_indices(ds)
[data, dim_labels, dim_values]=cosmo_unflatten(ds);
if ~isequal(dim_labels,{'node_indices'})
error('.a.fdim.labels must be {''node_indices''}');
end
node_indices=dim_values{1}(:)';
function p=build_pymvpa(ds)
p=ds;
% remove samples field
p=rmfield(p,'samples');
[data, node_indices]=get_surface_data_and_node_indices(ds);
p.samples=data;
node_indices_base0=int64(node_indices-1);
p.fa.node_indices=node_indices_base0;
p.a=rmfield(p.a,'fdim');
function write_pymvpa_struct(fn,m,opt)
save(fn,'-struct','m');
function g=build_gifti(ds)
s=struct();
[data, node_indices]=get_surface_data_and_node_indices(ds);
s.indices=node_indices(:);
s.cdata=data';
g=gifti(s);
function write_gifti(fn,g,opt)
if isfield(opt,'encoding')
args={opt.encoding};
else
args={};
end
save(g,fn,args{:});
function s=build_niml_dset(ds)
[data, node_indices]=get_surface_data_and_node_indices(ds);
s=struct();
s.node_indices=node_indices-1; % base 1 -> base 0
s.data=data';
if isfield(ds,'sa')
if isfield(ds.sa,'labels');
s.labels=ds.sa.labels;
end
if isfield(ds.sa,'stats');
s.stats=ds.sa.stats;
end
end
function write_niml_dset(fn,s,opt)
if isfield(opt, 'encoding')
args={opt.encoding};
else
args={};
end
afni_niml_writesimple(s, fn, args{:});
function s=build_bv_smp(ds)
[nsamples,nfeatures]=size(ds.samples);
[data, node_indices]=get_surface_data_and_node_indices(ds);
if ~isequal(node_indices,1:nfeatures)
error(['BrainVoyager smp only support data with all node '...
'present and with .a.fdim.values{1}=1:N, where N '...
'is the number of nodes']);
end
stats=cosmo_statcode(ds,'bv');
maps=cell(1,nsamples);
for k=1:nsamples
t=xff('new:smp');
map=t.Map;
t.ClearObject();
map.SMPData=data(k,:);
if k==1
% store the number of features
% note: unlike the niml_dset implementation, data is stored
% for all nodes, even if some node indices did not have a value
% originally
nfeatures=numel(map.SMPData);
end
if isfield(ds,'sa')
if isfield(ds.sa,'labels')
map.Name=ds.sa.labels{k};
end
if ~isempty(stats) && ~isempty(stats{k})
map=cosmo_structjoin(map, stats{k});
end
end
if ~isempty(stats) && ~isempty(stats{k})
map=cosmo_structjoin(map, stats{k});
end
map.BonferroniValue=nfeatures;
maps{k}=map;
end
s=xff('new:smp');
s.Map=cat(2,maps{:});
s.NrOfMaps=nsamples;
s.NrOfVertices=nfeatures;
neuroelf_bless_wrapper(s);
function write_bv_smp(fn,s,opt)
s.SaveAs(fn);
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);