cosmo vol grid convert skl

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