cosmo phase stat

function stat_ds=cosmo_phase_stat(ds,varargin)
% Compute phase perturbation, or opposition sum or product phase statistic
%
% stat_ds=cosmo_phase_stat(ds,...)
%
% 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.
%                           There must be exactly two conditions, thus
%                           .sa.targets must have exactly two unique
%                           values. A balanced number of samples is
%                           requires, i.e. each of the two unique values in
%                           .sa.targets must occur equally often.
%       .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
%  'output',p               Return statistic, one of the following:
%                           - 'pbi': phase bifurcation index
%                           - 'pos': phase opposition sum
%                           - 'pop': phase opposition product
%  'samples_are_unit_length',u  (optional)
%                           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:
%   stat_ds                 struct with fields
%       .samples            1xQ array with 'pbi', 'pos', or 'pop' function
%       .a                  } if present in the input, then the output
%       .fa                 } contains these fields as well
%
% Notes:
%   - if a dataset is not balanced for number of trials, consider using
%     cosmo_balance_dataset to balance it.
%
% See also: cosmo_balance_dataset, cosmo_phase_itc
%
% #   For CoSMoMVPA's copyright information and license terms,   #
% #   see the COPYING file distributed with CoSMoMVPA.           #


    default=struct();
    default.check_dataset=true;
    opt=cosmo_structjoin(default,varargin{:});

    check_inputs(ds,opt);

    % compute inter-trial coherence for the two conditions using
    % cosmo_phase_itc, which will thrown an error if samples are not
    % balanced.
    itc_ds=cosmo_phase_itc(ds,opt);

    % itc_ds must have last entry to be NaN, indicating it is the ITC for
    % all trials together
    if size(itc_ds.samples,1)~=3
        error(['Input must have exactly two unique values '...
                        'for .sa.targets']);
    end

    assert(isequal([false;false;true],isnan(itc_ds.sa.targets)));

    % compute PBI, POP or POS
    itc1=itc_ds.samples(1,:);
    itc2=itc_ds.samples(2,:);
    itc_all=itc_ds.samples(3,:);

    stat=compute_phase_stat(opt.output,itc1,itc2,itc_all);

    % set result
    stat_ds=cosmo_slice(itc_ds,1,1,false);
    stat_ds.samples=stat;
    stat_ds.sa=struct();



function s=compute_phase_stat(name, itc1, itc2, itc_all)
    switch name
        case 'pbi'
            s=(itc1-itc_all).*(itc2-itc_all);

        case 'pop'
            s=(itc1.*itc2)-itc_all.^2;

        case 'pos'
            s=itc1+itc2-2*itc_all;

        otherwise
            assert(false,'this should not happen');
    end


function check_inputs(ds,opt)
    if opt.check_dataset
        cosmo_check_dataset(ds);
    end

    if ~(isstruct(ds) ...
            && isfield(ds,'samples') ...
            && isfield(ds,'sa') ...
            && isfield(ds.sa,'targets'))
        error(['first input must be struct with fields .samples and '...
                            '.sa.targets']);
    end


    if ~isfield(opt,'output')
        error('option ''output'' is required');
    end

    allowed_values={'pbi','pos','pop'};
    if ~cosmo_match({opt.output},allowed_values)
        error('option ''output'' must be one of: ''%s''',...
                cosmo_strjoin(allowed_values,''', '''));
    end