cosmo dim insert

function ds=cosmo_dim_insert(ds,dim,index,labels,values,attr,varargin)
% insert a dataset dimension
%
% ds_result=cosmo_dim_insert(ds,dim,index,labels,values,attr,...)
%
% Inputs:
%   ds                  dataset struct
%   dim                 dimension along which dimensions must be inserted,
%                       1=samples, 2=features
%   index               position at which dimension must be inserted,
%                       in .a.sdim (if dim==1) or .a.fdim (if dim==2)
%   labels              dimension labels
%   values              dimension values
%   attr                cell with values for .sa or .fa, or a struct with
%                       the fields that are in labels
%   'matrix_labels',m   (optional) any label for which the corresponding
%                       value is a matrix must be an element of the
%                       cellstring m. Currently this applies to the 'pos'
%                       field in MEEG source data
%
% Output:
%   ds_result           dataset struct with dim_labels removed from
%                       .a.{fdim,sdim} and .{fa,sa}.
%
% Example:
%     % generate tiny fmri dataset
%     ds=cosmo_synthetic_dataset();
%     %
%     % remove first two feature dimensions ('i' and 'j')
%     dim_labels=ds.a.fdim.labels(1:2);
%     dim_values=ds.a.fdim.values(1:2);
%     dsr=cosmo_dim_remove(ds,dim_labels);
%     %
%     % add them back in
%     ds_humpty=cosmo_dim_insert(dsr,2,1,dim_labels,dim_values,...
%                                             {ds.fa.i,ds.fa.j});
%     %
%     % the output is the same as the original dataset
%     isequal(ds,ds_humpty)
%
% Notes:
%   - this is a utility function, mostly intended for use by other
%     functions
%   - this function does not check for duplicate dimensions
%
% #   For CoSMoMVPA's copyright information and license terms,   #
% #   see the COPYING file distributed with CoSMoMVPA.           #


    defaults.matrix_labels=cell(0);
    defaults.check_dataset=true;
    opt=cosmo_structjoin(defaults,varargin{:});

    prefixes='sf';
    prefix=prefixes(dim);
    attr_name=[prefix 'a'];
    dim_name=[prefix 'dim'];

    ds=ensure_has_xa_xdim(ds,dim,attr_name,dim_name);

    % get values in proper size
    attr_values=get_attr_values(labels,attr);
    dim_values=get_dim_values(labels,values,dim,opt);

    if ~iscellstr(labels)
        error('labels must be a cell with strings');
    end

    ds.a.(dim_name).labels=insert_elements(ds.a.(dim_name).labels, ...
                                                index, labels, dim);
    ds.a.(dim_name).values=insert_elements(ds.a.(dim_name).values, ...
                                                index, dim_values, dim);
    for j=1:numel(labels)
        label=labels{j};
        ds.(attr_name).(label)=attr_values{j};
    end

    if opt.check_dataset
        cosmo_check_dataset(ds);
    end

function ds=ensure_has_xa_xdim(ds,dim,attr_name,dim_name)
    if ~cosmo_isfield(ds,attr_name)
        ds.(attr_name)=struct();
    end

    if ~cosmo_isfield(ds,['a.' dim_name]);
        ds.a.(dim_name)=struct();
        empty_size=[0 0];
        empty_size(dim)=1;
        ds.a.(dim_name).labels=cell(empty_size);
        ds.a.(dim_name).values=cell(empty_size);
    end

function ys=insert_elements(xs,i,y,dim)
    % insets y in xs at position i
    n=numel(xs);

    if i<-n || i>(n+1)
        error('position index %d must be in range 1..%d, or -%d..-1',...
                        i,n+1,n+1);
    end

    if i<=0
        i=n+i+1;
    end

    xs_col=xs(:);
    ys=[xs_col(1:(i-1));y(:);xs_col(i:end)];
    if dim==1
        ys=ys';
    end

function dim_values=get_dim_values(labels,values,dim,opt)
    matrix_labels=opt.matrix_labels;

    n=numel(labels);

    if ~iscell(labels)
        error('labels argument must be a cell');
    end

    if ~iscell(values)
        error('values argument must be a cell');
    end


    if numel(values)~=n
        error('size mismatch between labels and values');
    end

    dim_values_shape=[1 1];
    dim_values_shape(3-dim)=n;

    dim_values=cell(dim_values_shape);
    for j=1:n
        label=labels{j};
        value=values{j};

        if ~cosmo_match({label},matrix_labels)
            sz=size(value);
            if ~any(sz==1)
                error(['dim value for %s must be a vector, because it '...
                            'is not set in the matrix_labels option'],...
                            label);
            end

            needs_transpose=sz(dim)==1;

            if needs_transpose
                value=value';
            end
        end
        dim_values{j}=value;
    end





function values=get_attr_values(labels,attr)
    % get elements for .sa or .fa. attr can either be a cell or a struct
    if isstruct(attr)
        values=cell(size(labels));
        for k=1:numel(labels)
            label=labels{k};
            if ~isfield(attr,label)
                error('missing field %s', label);
            end
            values{k}=attr.(label);
        end
    elseif iscell(attr)
        values=attr;
    else
        error('illegal attr value: must be struct or cell');
    end

    n=numel(labels);

    if numel(values)~=n
        error(['number of values (%d) does not match the number of '...
                    'labels'],numel(values),n);
    end