cosmo phase itc

function itc_ds=cosmo_phase_itc(ds,varargin)
% compute phase inter trial coherence
%
% itc_ds=cosmo_phase_itc(ds,varargin)
%
% Inputs:
%   ds                      dataset struct with fields:
%       .samples            PxQ complex matrix for P samples (trials,
%                           observations) and Q features (e.g. combinations
%                           of time points, frequencies and channels)
%       .sa.targets         Px1 array with trial conditions. Each condition
%                           must occur equally often; that is, the
%                           samples must be balanced.
%                           In the typical case of two conditions,
%                           .sa.targets must have exactly two unique
%                           values.
%       .sa.chunks          Px1 array indicating which samples can be
%                           considered to be independent. It is required
%                           that all samples are independent, therefore
%                           all values in .sa.chunks must be different from
%                           each other
%       .fa                 } optional feature attributes
%       .a                  } optional sample attributes
%  'samples_are_unit_length',u  (optional, default=false)
%                           If u==true, then all elements in ds.samples
%                           are assumed to be already of unit length. If
%                           this is indeed true, this can speed up the
%                           computation of the output.
%  'check_dataset',c        (optional, default=true)
%                           if c==false, there is no check for consistency
%                           of the ds input.
%
% Output:
%   itc_ds                  dataset struct with fields:
%       .samples            (N+1)xQ array with inter-trial coherence
%                           measure, where U=unique(ds.sa.targets) and
%                           N=numel(U). The first N rows correspond to the
%                           inter trial coherence for each condition. The
%                           last row is the inter trial coherence for all
%                           samples together.
%       .sa.targets         (N+1)x1 vector containing the values
%                           [U(:);NaN]' with trial conditions
%       .a                  } if present in the input, then the output
%       .fa                 } contains these fields as well
%
% #   For CoSMoMVPA's copyright information and license terms,   #
% #   see the COPYING file distributed with CoSMoMVPA.           #

    defaults=struct();
    defaults.samples_are_unit_length=false;
    defaults.check_dataset=true;

    opt=cosmo_structjoin(defaults,varargin{:});

    check_input(ds,opt);

    samples=ds.samples;
    if opt.samples_are_unit_length
        quick_check_some_samples_being_unit_length(samples);
    else
        % normalize
        samples=samples./abs(samples);
    end

    % split based on .sa.targets
    [idxs,classes]=cosmo_index_unique(ds.sa.targets);
    nclasses=numel(classes);
    nfeatures=size(samples,2);

    % allocate space for output
    itc=zeros(nclasses+1,nfeatures);

    % ITC for each class
    for k=1:nclasses
        samples_k=samples(idxs{k},:);
        itc(k,:)=itc_on_unit_length_elements(samples_k);
    end

    % overall ITC
    itc(nclasses+1,:)=itc_on_unit_length_elements(samples);

    % set output
    itc_ds=set_output(itc,ds,classes);


function itc_ds=set_output(itc,ds,classes)
    % store results
    itc_ds=struct();
    itc_ds.samples=itc;
    itc_ds.sa.targets=[classes(:); NaN];

    % copy .a and .fa fields, if present
    if isfield(ds,'a')
        itc_ds.a=ds.a;

        if isfield(ds.a,'sdim')
            % remove sample dimensions if present
            itc_ds.a=rmfield(itc_ds.a,'sdim');
        end
    end

    if isfield(ds,'fa')
        itc_ds.fa=ds.fa;
    end


function itc=itc_on_unit_length_elements(samples)
    % computes inter-trial coherence for each column seperately
    itc=abs(sum(samples,1)/size(samples,1));


function quick_check_some_samples_being_unit_length(samples)
    % instead of checking all values, only verify for a subset of values.
    % This should prevent most use cases where the user accidentally
    % uses non-normalized data, whereas checking all values would be
    % equivalent to actually computing their length for each of them.
    count_to_check=10;

    % generate random positions to check for unit length
    nelem=numel(samples);
    pos=ceil(rand(1,count_to_check)*nelem);

    samples_subset=samples(pos);
    lengths=abs(samples_subset);

    % safety margin
    delta=10*eps('single');
    if any(lengths+delta<1 | lengths-delta>1)
        error('.samples input is not of unit length');
    end


function check_input(ds,opt)
    % must be a proper dataset
    if opt.check_dataset
        raise_exception=true;
        cosmo_check_dataset(ds,raise_exception);

        % must have targets and chunks
        cosmo_isfield(ds,{'sa.targets','sa.chunks'},raise_exception);
    end

    % all chunks must be unique
    if ~isequal(sort(ds.sa.chunks),unique(ds.sa.chunks))
        error(['All values in .sa.chunks must be different '...
                    'from each other']);
    end

    % trial counts must be balanced
    [idxs,classes]=cosmo_index_unique(ds.sa.targets);
    class_count=cellfun(@numel,idxs);
    unequal_pos=find(class_count~=class_count(1),1);
    if ~isempty(unequal_pos)
        error(['.sa.targets indicates unbalanced targets, with '...
                        '.sa.targets==%d occuring %d times, and '...
                        '.sa.targets==%d occuring %d times.\n'...
                        'To obtain balanced targets, consider '...
                        'using cosmo_balance_dataset.'],...
                    classes(1),class_count(1),...
                    classes(unequal_pos),class_count(unequal_pos));
    end

    % input must be complex
    if isreal(ds.samples)
        error('.samples must be complex');
    end

    v=opt.samples_are_unit_length;
    if ~(islogical(v) ...
            && isscalar(v))
        error('option ''samples_are_unit_length'' must be logical scalar');
    end