function ds_conv = cosmo_vol_grid_convert(ds, varargin)
% convert between volumetric (fmri) and grid-based (meeg source) dataset
%
% ds_conv=cosmo_vol_grid_convert(ds[,direction])
%
% Inputs:
% ds dataset struct, either in fmri or meeg source form
% direction (optional) conversion direction, either
% - 'tovol' : convert to a volumetric (fmri) dataset
% - 'togrid': convert to a source (meeg) dataset
% If this option is omitted, then 'tovol' is selected if
% ds is an meeg source dataset, and 'togrid' otherwise
%
% Output:
% ds_conv dataset in volumetric or source format
%
% Example:
% ds=cosmo_synthetic_dataset('size','normal');
% % feature attributes are i, j, k
% cosmo_disp(ds.fa,'edgeitems',2);
% %|| .i
% %|| [ 1 2 ... 2 3 ]@1x30
% %|| .j
% %|| [ 1 1 ... 2 2 ]@1x30
% %|| .k
% %|| [ 1 1 ... 5 5 ]@1x30
% % fmri dataset has .a.vol
% cosmo_disp(ds.a,'edgeitems',2);
% %|| .fdim
% %|| .labels
% %|| { 'i'
% %|| 'j'
% %|| 'k' }
% %|| .values
% %|| { [ 1 2 3 ]
% %|| [ 1 2 ]
% %|| [ 1 2 3 4 5 ] }
% %|| .vol
% %|| .mat
% %|| [ 2 0 0 -3
% %|| 0 2 0 -3
% %|| 0 0 2 -3
% %|| 0 0 0 1 ]
% %|| .dim
% %|| [ 3 2 5 ]
% %|| .xform
% %|| 'scanner_anat'
% %
% % convert to get grid representation
% ds_grid=cosmo_vol_grid_convert(ds,'togrid');
% % feature attribute is pos
% cosmo_disp(ds_grid.fa,'edgeitems',2);
% %|| .pos
% %|| [ 1 11 ... 20 30 ]@1x30
% % meeg source dataset has no .a.vol
% cosmo_disp(ds_grid.a,'edgeitems',2);
% %|| .fdim
% %|| .labels
% %|| { 'pos' }
% %|| .values
% %|| { [ -1 -1 ... 3 3
% %|| -1 -1 ... 1 1
% %|| -1 1 ... 5 7 ]@3x30 }
% %
% % convert it back to a format identical to the input
% ds_vol=cosmo_vol_grid_convert(ds_grid,'tovol');
% %
% isequal(ds.fa,ds_vol.fa)
% %|| true
% isequal(ds.a.vol.mat,ds_vol.a.vol.mat)
% %|| true
% isequal(ds.a.fdim,ds_vol.a.fdim)
% %|| true
%
% Notes:
% - when ds is an meeg source dataset, it is required that the positions
% can be placed in a regular grid
%
% # For CoSMoMVPA's copyright information and license terms, #
% # see the COPYING file distributed with CoSMoMVPA. #
check_input(ds);
direction = get_direction(ds, varargin{:});
switch direction
case 'tovol'
ds_conv = convert_grid2vol(ds);
case 'togrid'
ds_conv = convert_vol2grid(ds);
case 'none'
ds_conv = ds;
otherwise
assert(false, 'this should not happen');
end
cosmo_check_dataset(ds_conv);
function ds_conv = convert_vol2grid(ds)
% ensure required fields are there
pos = cosmo_vol_coordinates(ds);
nfeatures = size(ds.samples, 2);
[idxs, unq_pos_tr] = cosmo_index_unique(pos');
pos_values = unq_pos_tr';
pos_fa = zeros(1, nfeatures);
for k = 1:numel(idxs)
pos_fa(idxs{k}) = k;
end
assert(all(pos_fa ~= 0));
% remove i, j, k fields
ds_conv = cosmo_dim_remove(ds, {'i', 'j', 'k'});
% remove volumetric field
ds_conv.a = rmfield(ds_conv.a, 'vol');
% insert position field
ds_conv = cosmo_dim_insert(ds_conv, 2, 0, {'pos'}, ...
{pos_values}, {pos_fa}, 'matrix_labels', {'pos'});
function ds_conv = convert_grid2vol(ds)
pos_label = 'pos';
ijk_labels = {'i'; 'j'; 'k'};
[dim, index] = cosmo_dim_find(ds, pos_label, true);
assert(dim == 2);
values = ds.a.fdim.values{index};
ndim = size(values, 1);
if ndim ~= 3
error('''pos'' in .a.fdim.values{%d} must be 3xN', index);
end
tolerance = 1e-6;
min_deltas = zeros(ndim, 1);
min_values = zeros(ndim, 1);
dim_sizes = zeros(ndim, 1);
dim_values = cell(ndim, 1);
fa = struct();
for dim = 1:ndim
% attempt to convert grid to linear space
vs = values(dim, :);
unq_vs = unique(vs);
min_value = min(unq_vs);
deltas = unique(diff(unq_vs));
deltas = deltas(deltas > tolerance);
if numel(deltas) == 0
min_delta = 1;
max_ratio = 0;
else
min_delta = min(deltas);
ratios = (unq_vs - min_value) / min_delta;
max_ratio = round(max(ratios));
if numel(ratios) ~= (1 + max_ratio) || ...
max(abs(ratios - (0:max_ratio))) > tolerance
error(['''pos'' in .a.fdim.values{%d}(%d,:) do not '...
'follow grid-like structure'], index, dim);
end
end
% store data
min_deltas(dim) = min_delta;
min_values(dim) = min_value;
dim_sizes(dim) = max_ratio + 1;
% convert position to integer in range 1:dim_sizes(dim)
vs_idxs = 1 + round((vs - min_value) / min_delta);
dim_values{dim} = 1:dim_sizes(dim);
% set feature attribute
ijk_label = ijk_labels{dim};
fa.(ijk_label) = (vs_idxs(ds.fa.(pos_label)));
end
mat = diag([min_deltas; 1]);
mat(1:3, 4) = min_values - min_deltas;
ds_conv = cosmo_dim_remove(ds, 'pos');
ds_conv = cosmo_dim_insert(ds_conv, 2, 0, ijk_labels, dim_values, fa);
ds_conv.a.vol.mat = mat;
ds_conv.a.vol.dim = dim_sizes(:)';
if cosmo_isfield(ds_conv, 'a.meeg.dim')
ds_conv.a.meeg = rmfield(ds_conv.a.meeg, 'dim');
end
function check_input(ds)
cosmo_check_dataset(ds);
function tf = ds_has_vol(ds)
tf = all(cosmo_isfield(ds, {'fa.i', 'fa.j', 'fa.k', 'a.vol.mat'}));
function tf = ds_has_pos(ds)
tf = cosmo_isfield(ds, 'fa.pos');
function direction = get_direction(ds, varargin)
narg = numel(varargin);
if narg > 1
error('More than two input arguments are not supported');
end
has_direction = narg == 1;
if ds_has_vol(ds)
guessed_direction = 'togrid';
elseif ds_has_pos(ds)
guessed_direction = 'tovol';
else
error(['Input dataset must either have .fa.pos '...
'(for MEEG source), or '...
'both fa.{i,j,k} and .a.vol.mat (for fMRI volume)']);
end
if has_direction
direction = varargin{1};
if ~cosmo_match({direction}, {'togrid', 'tovol'})
error('direction must be ''togrid'' or ''tovol''');
end
if ~strcmp(direction, guessed_direction)
direction = 'none';
end
else
direction = guessed_direction;
end