function res=cosmo_distatis(ds, varargin)
% apply DISTATIS measure to each feature
%
% res=cosmo_statis_measure(ds, opt)
%
% Inputs:
%    ds               dataset struct with dissimilarity values; usually
%                     the output from @cosmo_dissimilarity_matrix_measure
%                     applied to each subject followed by cosmo_stack. It
%                     can also be a cell with datasets (one per subject).
%    'return', d      d can be 'distance' (default) or 'crossproduct'.
%                     'distance' returns a distance matrix, whereas
%                     'crossproduct' returns a crossproduct matrix
%    'split_by', s    sample attribute that discriminates chunks
%                     (participants) (default: 'chunks')
%    'shape', sh      shape of output if it were unflattened using
%                     cosmo_unflatten, either 'square' (default) or
%                     'triangle' (which gives the lower diagonal of the
%                     distance matrix)
%
% Returns:
%    res              result dataset struct with feature-wise optimal
%                     compromise distance matrix across subjects
%      .samples
%
%
% Example:
%     % (This example cannot be documentation tested using Octave,
%     %  since Octave does not allow for-loops with evalc)
%     cosmo_skip_test_if_no_external('matlab');
%     %
%     ds=cosmo_synthetic_dataset('nsubjects',5,'nchunks',1,'ntargets',4);
%     %
%     % define neighborhood (here a searchlight with radius of 1 voxel)
%     nbrhood=cosmo_spherical_neighborhood(ds,'radius',1,'progress',false);
%     %
%     % define measure
%     measure=@cosmo_dissimilarity_matrix_measure;
%     % each subject is a chunk
%     ds.sa.chunks=ds.sa.subject;
%     % compute DSM for each subject
%     sp=cosmo_split(ds,'chunks');
%     for k=1:numel(sp)
%         sp{k}=cosmo_searchlight(sp{k},nbrhood,measure,'progress',false);
%         sp{k}.sa.chunks=ones(6,1)*k;
%     end
%     % merge results
%     dsms=cosmo_stack(sp);
%     %
%     r=cosmo_distatis(dsms,'return','distance','progress',false);
%     cosmo_disp(r);
%     %|| .samples
%     %||   [     0         0         0         0         0         0
%     %||     0.818      1.09      0.77     0.653      1.03     0.421
%     %||     0.869       1.3      1.06      1.04     0.932      1.07
%     %||       :         :         :         :         :         :
%     %||      1.16     0.889      0.99     0.631      1.48     0.621
%     %||     0.268     0.952     0.965     0.462     0.943      1.04
%     %||         0         0         0         0         0         0 ]@16x6
%     %|| .fa
%     %||   .center_ids
%     %||     [ 1         2         3         4         5         6 ]
%     %||   .i
%     %||     [ 1         2         3         1         2         3 ]
%     %||   .j
%     %||     [ 1         1         1         2         2         2 ]
%     %||   .k
%     %||     [ 1         1         1         1         1         1 ]
%     %||   .nvoxels
%     %||     [ 3         4         3         3         4         3 ]
%     %||   .radius
%     %||     [ 1         1         1         1         1         1 ]
%     %||   .quality
%     %||     [ 0.685     0.742     0.617     0.648     0.757     0.591 ]
%     %||   .nchunks
%     %||     [ 5         5         5         5         5         5 ]
%     %|| .a
%     %||   .fdim
%     %||     .labels
%     %||       { 'i'  'j'  'k' }
%     %||     .values
%     %||       { [ 1         2         3 ]  [ 1         2 ]  [ 1 ] }
%     %||   .sdim
%     %||     .labels
%     %||       { 'targets1'  'targets2' }
%     %||     .values
%     %||       { [ 1    [ 1
%     %||           2      2
%     %||           3      3
%     %||           4 ]    4 ] }
%     %||   .vol
%     %||     .mat
%     %||       [ 2         0         0        -3
%     %||         0         2         0        -3
%     %||         0         0         2        -3
%     %||         0         0         0         1 ]
%     %||     .dim
%     %||       [ 3         2         1 ]
%     %||     .xform
%     %||       'scanner_anat'
%     %|| .sa
%     %||   .targets1
%     %||     [ 1
%     %||       2
%     %||       3
%     %||       :
%     %||       2
%     %||       3
%     %||       4 ]@16x1
%     %||   .targets2
%     %||     [ 1
%     %||       1
%     %||       1
%     %||       :
%     %||       4
%     %||       4
%     %||       4 ]@16x1
%
% Reference:
%   - Abdi, H., Valentin, D., O?Toole, A. J., & Edelman, B. (2005).
%     DISTATIS: The analysis of multiple distance matrices. In
%     Proceedings of the IEEE Computer Society: International conference
%     on computer vision and pattern recognition, San Diego, CA, USA
%     (pp. 42?47).
%
% Notes:
%   - DISTATIS tries to find an optimal compromise distance matrix across
%     the different samples (participants)
%   - Output can be reshape to matrix or array form using
%     cosmo_unflatten(res,1)
%
% #   For CoSMoMVPA's copyright information and license terms,   #
% #   see the COPYING file distributed with CoSMoMVPA.           #

    cosmo_check_external('distatis');

    defaults.return='distance';
    defaults.split_by='chunks';
    defaults.shape='square';
    defaults.mask_output=[];
    defaults.progress=100;
    defaults.feature_ids=[];
    defaults.autoscale=true;
    defaults.abs_correlation=false;
    defaults.weights='eig';

    opt=cosmo_structjoin(defaults,varargin);

    subject_cell=get_subject_data(ds,opt);
    nsubj=numel(subject_cell);

    [dsms,nclasses,dim_labels,dim_values]=get_dsms(subject_cell);

    feature_ids=get_feature_ids(size(dsms{1},3),opt);
    nfeatures=numel(feature_ids);

    quality=zeros(1,nfeatures);
    nobservations=zeros(1,nfeatures);

    prev_msg='';
    clock_start=clock();
    show_progress=nfeatures>1 && opt.progress;

    for k=1:nfeatures
        feature_id=feature_ids(k);
        x=zeros(nclasses*nclasses,nsubj);

        for j=1:nsubj
            dsm=dsms{j}(:,:,feature_id);
            x(:,j)=distance2crossproduct(dsm, opt.autoscale);
        end

        [x,subj_msk]=cosmo_remove_useless_data(x);
        nkeep=sum(subj_msk);

        % equivalent, but slower:
        % [e,v]=eigs(c,1);

        [ew,v]=get_weights(x, feature_id, nkeep, opt);

        % compute compromise
        compromise=x*ew;

        result=convert_compromise(compromise, opt);

        if feature_id==1
            % allocate space
            samples=zeros(numel(result),nfeatures);
        end

        samples(:,k)=result;

        quality(:,k)=v/nkeep;
        nobservations(:,k)=nkeep;

        if show_progress && (k<10 || ...
                                mod(k, opt.progress)==0 || ...
                                k==nfeatures)
            status=sprintf('quality=%.3f%% (avg)',mean(quality(1:k)));
            prev_msg=cosmo_show_progress(clock_start,k/nfeatures,...
                                                        status,prev_msg);
        end
    end

    % set output in either triangular or square shape
    [res,i,j]=get_samples_in_shape(samples,nclasses,opt.shape);
    res=copy_fields(ds,res,{'fa','a'});

    % add attributes
    res.fa.quality=quality;
    res.fa.nchunks=nobservations;
    res.a.sdim=struct();
    res.a.sdim.labels=dim_labels;
    res.a.sdim.values=dim_values;

    res.sa.(dim_labels{1})=i(:);
    res.sa.(dim_labels{2})=j(:);

    cosmo_check_dataset(res);

function [res,i,j]=get_samples_in_shape(samples,nclasses,shape)
    res=struct();
    switch shape
        case 'triangle'
            [msk,i,j]=distance_matrix_mask(nclasses);
            res.samples=samples(msk(:),:);
        case 'square'
            res.samples=samples;
            [i,j]=find(ones(nclasses));
        otherwise
            error('unsupported direction %s', shape);
    end

function dst=copy_fields(src,dst,keys)
    for k=1:numel(keys)
        key=keys{k};
        if isfield(src,key)
            dst.(key)=src.(key);
        end
    end

function feature_ids=get_feature_ids(nfeatures, opt)
    feature_ids=opt.feature_ids;
    if isempty(feature_ids);
        feature_ids=1:nfeatures;
    end

function [ew,v]=get_weights(x, feature_id, nkeep, opt)
    switch opt.weights
        case 'eig'
            [ew,v]=eigen_weights(x, feature_id);

        case 'uniform'
            % all the same (allowing for comparison with 'eig')
            ew=ones(nkeep,1)/nkeep;
            v=0;

        otherwise
            error('illegal weight %s', opt.weights);
    end

function subject_cell=get_subject_data(ds,opt)
    if isstruct(ds)
        subject_cell=cosmo_split(ds,opt.split_by);
    else
        subject_cell=ds;
    end

    if numel(subject_cell)==0
        error('empty input');
    end

function [ew,v]=eigen_weights(x, feature_id)

    c=cosmo_corr(x);

    negative_c=c<0;

    if any(negative_c(:))

        [i,j]=find(negative_c);
        error(['feature %d has negative correlation between '...
                'sample %d and %d, which is not supported by '...
                'distatis. DISTATIS assumes that the similarity '...
                'data from all samples (typically: participants) '...
                'correlate positively. Because that is not the '...
                'case, you cannot use DISTATIS analysis on this '...
                'data. '],...
                feature_id,i(1),j(1));
    end

    [v,e]=fast_eig1(c);

    if all(e<0)
        e=-e;
    end

    assert(all(e>0));
    assert(v>0);

    % normalize first eigenvector
    ew=e/sum(e);

function result=convert_compromise(compromise, opt)
    switch opt.return
        case 'crossproduct'
            result=compromise;
        case 'distance'
            result=crossproduct2distance(compromise);
        otherwise
            error('illegal opt.return');
    end

function z=crossproduct2distance(x)
    n=sqrt(numel(x));
    e=ones(n,1);
    d=x(1:(n+1):end);
    dd=d*e';
    ddt=dd';
    y=dd(:)+ddt(:)-2*x;
    z=ensure_distance_vector(y);

function assert_symmetric(x, tolerance)
    if nargin<2, tolerance=1e-8; end

    % assert x is a square matrix
    sz=size(x);
    assert(isequal(sz,sz([2 1])));

    xx=x'-x;

    msk=xx>tolerance;
    if any(msk)
        [i,j]=find(msk,1);
        error('not symmetric: x(%d,%d)=%d ~= %d=x(%d,%d)',...
                i,j,x(i,j),x(j,i),j,i);
    end

function z_vec=distance2crossproduct(x, autoscale)

    n=size(x,1);
    e=ones(n,1);
    m=e*(1/n);
    ee=eye(n)-e*m';
    y=-.5*ee*(x+x')*ee';
    if autoscale
        z=(1/fast_eig1(y))*y;
    else
        z=y;
    end
    assert_symmetric(z);
    % equivalent, but slower:
    % z=(1/eigs(y,1))*y(:);

    z_vec=z(:);

function [lambda,pivot]=fast_eig1(x)
    % returns the first eigenvalue in lambda, and the corresponding
    % eigenvector in pivot
    if cosmo_wtf('is_matlab')
        [pivot,lambda]=eigs(x,1);
    else
        % There seems a bug in Octave for 'eigs',
        % so use 'eig' instead.
        % http://savannah.gnu.org/bugs/?44004
        [e,v]=eig(x);
        diag_v=diag(v);

        % find largest eigenvalue and eigenvector
        [lambda,i]=max(diag_v);
        pivot=e(:,i);
    end

    % The code below is disabled because under certain circumstances
    % it would return a near-zero eigenvalue if indeed one eigenvalue (but
    % not the largest one) is zero.
    % % compute first (largest) eigenvalue and corresponding eigenvector
    % % using power iteration method; benchmarking suggests this can be up to
    % % five times as fast as using eigs(x,1)
    % n=size(x,1);
    % pivot=ones(n,1);
    % tolerance=1e-8;
    % max_iter=1000;
    %
    % old_lambda=NaN;
    % for k=1:max_iter
    %     z=x*pivot;
    %     pivot=z / norm(z);
    %
    %     lambda=pivot'*z;
    %     if abs(lambda-old_lambda)/lambda<tolerance
    %         z=x*pivot;
    %         pivot=z / sqrt(sum(z.^2));
    %
    %         lambda=pivot'*z;
    %         return
    %     end
    %     old_lambda=lambda;
    % end
    %
    % % matlab fallback
    % [pivot,lambda]=eigs(x,1);

function y=ensure_distance_vector(x)
    tolerance=1e-8;

    n=sqrt(numel(x));
    xsq=reshape(x,n,n);

    dx=diag(xsq);
    assert(all(dx<tolerance));

    xsq=xsq-diag(dx);

    delta=xsq-xsq';
    assert(all(delta(:)<tolerance));

    xsq=.5*(xsq+xsq');
    y=xsq(:);

function [dsms,nclasses,dim_labels,dim_values]=get_dsms(data_cell)
    nsubj=numel(data_cell);

    % allocate
    dsms=cell(nsubj,1);
    for k=1:numel(data_cell)
        data=data_cell{k};

        % get data
        [dsm,dim_labels,dim_values,is_ds]=get_dsm(data);

        % store data
        dsms{k}=dsm;

        if k==1
            nclasses=size(dsm,1);
            first_dim_labels=dim_labels;
            first_dim_values=dim_values;

            data_first=data;
        else

            if ~isequal(first_dim_labels,dim_labels)
                error('dim label mismatch between subject 1 and %d',k);
            end
            if ~isequal(first_dim_values,dim_values)
                error('dim label mismatch between subject 1 and %d',k);
            end

            % check for compatibility over subjects, raises an error if not
            % kosher
            if is_ds
                cosmo_stack({cosmo_slice(data,1),...
                                cosmo_slice(data_first,1)},1,'unique');
            end
        end
    end

function [msk,i,j]=distance_matrix_mask(nclasses)
    msk=triu(repmat(1:nclasses,nclasses,1),1)'>0;
    [i,j]=find(msk);

function [dsm, dim_labels, dim_values, is_ds]=get_dsm(data)
    is_ds=isstruct(data);
    if is_ds
        [dsm,dim_labels,dim_values]=cosmo_unflatten(data,1);
    elseif isnumeric(data)
        sz=size(data);
        if numel(sz)~=2
            error('only vectorized distance matrices are supported');
        end
        [n,nfeatures]=size(data);

        side=(1+sqrt(1+8*n))/2; % so that side*(side-1)/2==n
        if ~isequal(side, round(side))
            error(['size %d of input vector is not correct for '...
                    'the number of elements below the diagonal of a '...
                    'square (distance) matrix'], n);
        end

        [msk,i,j]=distance_matrix_mask(side);
        dsm=zeros([side,side,nfeatures]);

        assert(numel(i)==n);
        for pos=1:n
            dsm(i(pos),j(pos),:)=data(pos,:);
        end

        sq1=cosmo_squareform(data(:,1));
        dsm1=dsm(:,:,1);
        assert(isequal(sq1,dsm1+dsm1'));

        dim_labels={'targets1','targets2'};
        dim_values={(1:side)',(1:side)'};
    else
        error('illegal input: expect dataset struct, or cell with arrays');
    end


function [curr_segments] = filter_segments(seg_obj, gp_obj, graph_idx, ...
                                           curr_segments)
%FILTER_SEGMENTS filters (discards) the segments based on different
% criterion
%
% @authors:     Ahmad Humayun,  Fuxin Li
% @contact:     ahumayun@cc.gatech.edu
% @affiliation: Georgia Institute of Technology
% @date:        Fall 2013 - Summer 2014

    % first check if we want to filter segments for the current graph
    if ~seg_obj.segm_params.graph_filter_segs{graph_idx}
        curr_segments.segs_meta_info.energies = [];
        return;
    end

    fprintf('\tFiltering segments\n');

    t_fltr = tic;
    % if no segments, return
    if isempty(curr_segments.cut_segs)
        return;
    end

    % separate each segment into its connected components. Also remove the
    % components (split segments) which are below min_npixels size
    [curr_segments] = ...
        separate_conn_comp(curr_segments, seg_obj, ...
                           seg_obj.segm_params.filter_min_seg_pixels);

    % just computes the pairwise edges cut value divided by the number of
    % pairwise edges cut (this is called the cut ratio)
    [curr_segments, t_energy] = compute_energies(curr_segments, ...
                                                 seg_obj, gp_obj);

    % filter segment which are above the filter_max_energy cut ratio
    [curr_segments] = filter_energies(curr_segments, seg_obj, ...
                                seg_obj.segm_params.filter_max_energy, ...
                                t_energy);

    % randomly throw away segments if more than filter_max_rand
    [curr_segments] = filter_rand(curr_segments, seg_obj, ...
                                  seg_obj.segm_params.filter_max_rand);

    % if multiple segments overlap, only keep the one with the lowest cut
    % ratio
    [curr_segments] = remove_repeated_segments(curr_segments, seg_obj, ...
                                               gp_obj);

    % if user specified specified seeds, discard anything that doesn't
    % include the seed location itself (which can happen with color unaries
    % for instance)
    if strcmp(seg_obj.segm_params.graph_seed_gen_method,'gen_user_seeds')
        % find segments which cover all the seed superpixels (supposes that
        % there is only one seed in seed_sets)
        seed_cover = ...
            curr_segments.cut_segs(seg_obj.precomputed_seeds.seed_sets,:);
        conn = (sum(seed_cover,1) ...
            == sum(seg_obj.precomputed_seeds.seed_sets));
        [curr_segments] = cherry_pick_segments(curr_segments, conn);
    end

    seg_obj.timings.seg_filtering_time = ...
        [seg_obj.timings.seg_filtering_time, toc(t_fltr)];
end

function [curr_segments] = separate_conn_comp(curr_segments, seg_obj, ...
                                              min_npixels)
    fprintf('\t\tSeparating connected components ... ');

    t_conn = tic;

    if(nargin == 1)
        min_npixels = 25;
    end

    % separate each segment into its connected components by
    % finding connected components on the induced graph. Also remove the
    % components which are below min_npixels size
    [segms, num_comps] = sp_conncomp_mex(curr_segments.cut_segs, ...
                                         seg_obj.sp_data.edgelet_sp, ...
                                         seg_obj.sp_data.sp_seg_szs, ...
                                         min_npixels);

    % make the mapping which is an array of the length of the total
    % number of new segments (each of which forms a connected
    % component) where each array location gives the index/id of
    % the original segment returned by the min-cut
    temp = duplicateElems(1:length(num_comps), num_comps);
    curr_segments.segs_meta_info.seg_mapping_final_to_orig = ...
        curr_segments.segs_meta_info.seg_mapping_final_to_orig(temp);

    curr_segments.cut_segs = segms;

    seg_obj.num_segs.after_splitting_conncomp = ...
        [seg_obj.num_segs.after_splitting_conncomp, sum(num_comps)];

    time_util(seg_obj, 'init_filter_time', t_conn, 1, 1);
end

function [curr_segments, t_energy] = compute_energies(curr_segments, ...
                                                      seg_obj, gp_obj)
    fprintf('\t\tComputing energies ... ');

    t_energy = tic;

    cut_ratio = zeros(1,size(curr_segments.cut_segs,2));
    for i = 1:length(cut_ratio)
        links_across = ...
            gp_obj.pairwise_graph(~curr_segments.cut_segs(:,i), ...
                                  curr_segments.cut_segs(:,i));
        cut = full(sum(links_across(:)));
        % cut ratio (minimizing it is called the sparsest cut problem)
        n_edges_across = nnz(links_across);
        cut_ratio(i) = cut / n_edges_across;
    end
    curr_segments.segs_meta_info.energies = cut_ratio;

    fprintf('%.2fs\n', toc(t_energy));
end

function [curr_segments] = filter_energies(curr_segments, seg_obj, ...
                                           max_energy, t_energy)
    fprintf('\t\tRemoving high energy solutions ... ');

    t_energy_fltr = tic;

    % minimum number of segments to keep
    min_n_segms = 5;

    [sorted_cut_ratio, sorted_ind] = ...
        sort(curr_segments.segs_meta_info.energies, 'ascend');
    last_acceptable = find(sorted_cut_ratio <= max_energy, 1, 'last');
    reject_segs_ind = sorted_ind(last_acceptable+1:end);
    % if the remaining number of segments would be less
    num_remain_segs = length(sorted_cut_ratio) - length(reject_segs_ind);
    if num_remain_segs < min_n_segms
        num_keep_more = min_n_segms - num_remain_segs;
        num_keep_more = min(num_keep_more, length(reject_segs_ind));
        reject_segs_ind(1:num_keep_more) = [];
    end

    % remove segments not wanted from curr_segments
    [curr_segments] = cherry_pick_segments(curr_segments, ...
                                           setdiff(1:length(sorted_ind), ...
                                                   reject_segs_ind));

    seg_obj.num_segs.after_energy_filtering = ...
        [seg_obj.num_segs.after_energy_filtering, ...
         size(curr_segments.cut_segs,2)];

    fprintf('%.2fs\n', toc(t_energy_fltr));

    time_util(seg_obj, 'energy_filter_time', t_energy, 1, 0);
end

function [curr_segments] = filter_rand(curr_segments, seg_obj, ...
                                       max_rand_pick)
    fprintf('\t\tPick %d random segments ... ', max_rand_pick);

    t_rand = tic;

    if size(curr_segments.cut_segs,2) > max_rand_pick
        randn = randperm(size(curr_segments.cut_segs,2));
        curr_segments = cherry_pick_segments(curr_segments, ...
                                             randn(1:max_rand_pick));
    end

    seg_obj.num_segs.after_random_picking = ...
        [seg_obj.num_segs.after_random_picking, ...
         size(curr_segments.cut_segs,2)];

    time_util(seg_obj, 'rand_filter_time', t_rand, 1, 1);
end

function [curr_segments] = remove_repeated_segments(curr_segments, ...
                                                    seg_obj, gp_obj)
    fprintf('\t\tRemoving repeated segments ... ');

    OVERLAP_THRESH = 0.95;

    t_repeat = tic;

    seg_sel = [];

    % divide segments by their graph sub-methods
    ub = cumsum(gp_obj.graph_sets_per_method);
    lb = [1, ub(1:end-1)+1];
%     % if you don't want division by graph sub-methods (comment out above)
%     ub = inf;
%     lb = 1;

    % find sols_to_unary_mapping for the segments remaining
    sols_to_unary_map = curr_segments.segs_meta_info.sols_to_unary_mapping;
    seg_mapping = curr_segments.segs_meta_info.seg_mapping_final_to_orig;
    curr_unary_mapping = sols_to_unary_map(seg_mapping);
    for gsubm = 1:length(ub)
        subm_idx = curr_unary_mapping >= lb(gsubm) & ...
                   curr_unary_mapping <= ub(gsubm);
        cut_segs = curr_segments.cut_segs(:,subm_idx);

        % smart way to compute pairwise overlaps btw segments if above 0.95
        overlap_mat = overlap_over_threshold(cut_segs, OVERLAP_THRESH);

        % find segment sets which are stringed together with high overlap
        bw_mat = overlap_mat >= OVERLAP_THRESH;
        [num_conn, conncomps_t] = graphconncomp(sparse(bw_mat));
        conncomps = zeros(size(subm_idx));
        conncomps(subm_idx) = conncomps_t;
        this_sel = zeros(1,num_conn);
        % for each segment set, select one segment with lowest energy
        for i = 1:num_conn
            s1 = find(conncomps == i);
            [~,b] = min(curr_segments.segs_meta_info.energies(conncomps == i));
            this_sel(i) = s1(b);
        end
        % collate to the list of segments to be cherry picked
        seg_sel = [seg_sel, this_sel];
    end

    % keep only segments which have lowest energy amongst similar segments
    [curr_segments] = cherry_pick_segments(curr_segments, seg_sel);

    seg_obj.num_segs.after_repeat_remove = ...
        [seg_obj.num_segs.after_repeat_remove, length(seg_sel)];

    time_util(seg_obj, 'seg_similar_filter_time', t_repeat, 1, 1);
end

function [curr_segments] = cherry_pick_segments(curr_segments, to_pick_idxs)
% this function removes segments from curr_segments structure

    curr_segments.cut_segs = curr_segments.cut_segs(:, to_pick_idxs);
    curr_segments.segs_meta_info.seg_mapping_final_to_orig = ...
        curr_segments.segs_meta_info.seg_mapping_final_to_orig(:, to_pick_idxs);

    if isfield(curr_segments.segs_meta_info, 'energies') && ...
            ~isempty(curr_segments.segs_meta_info.energies)
        curr_segments.segs_meta_info.energies = ...
            curr_segments.segs_meta_info.energies(to_pick_idxs);
    end
end


function C = construct_winning_matrix(M, num_method)
% -------------------------------------------------------------------------
%   Description:
%       function to construct a winning matrix C ftom vote matrix M
%
%   Input:
%       - M: N x 4 vote matrix, N is the number of images
%            M(i, 1) is image id
%            M(i, 2) is method1 id
%            M(i, 3) is method2 id
%            M(i, 4) is compared result, 1 if user chooses method1, 2 if user chooses method2
%
%       - num_method: the number of evaluated methods
%
%   Output:
%       - C: winning matrix, C(i, j) is the number of times that user choose i over j
%
%   Citation:
%       A Comparative Study for Single Image Blind Deblurring
%       Wei-Sheng Lai, Jia-Bin Huang, Zhe Hu, Narendra Ahuja, and Ming-Hsuan Yang
%       IEEE Conference on Computer Vision and Pattern Recognition (CVPR), 2016
%
%   Contact:
%       Wei-Sheng Lai
%       wlai24@ucmerced.edu
%       University of California, Merced
% -------------------------------------------------------------------------

    C = zeros(num_method, num_method);

    for i = 1:size(M, 1)

        img_id  = M(i, 1);
        method1 = M(i, 2);
        method2 = M(i, 3);
        result  = M(i, 4);

        if( result == 1 )
            C(method1, method2) = C(method1, method2) + 1;
        elseif( result == 2 )
            C(method2, method1) = C(method2, method1) + 1;
        end

    end

end


% Defines the class headModel for solving forward/inverse problem of the EEG.
% This class is part of MoBILAB software.
% For more details visit:  https://code.google.com/p/mobilab/
%
% Author: Alejandro Ojeda, SCCN/INC/UCSD, Jan-2012

classdef headModel < handle
    properties(GetAccess=public, SetAccess=public,SetObservable)
        channelSpace = [];     % xyz coordinates of the sensors.

        fiducials = [];        % xyz of the fiducial landmarks: nassion, lpa, rpa, vertex, and inion.

        surfaces = [];         % Pointer to the file where the surfaces representing different layers
                               % of tissue are stored. The surfaces must be in an array of MATLAB patches
                               % in the following order: 1) scalp, 2) skull, 3) brain (gray matter or
                               % average between gray and white matter)

        atlas                  % Atlas that labels each vertex in the most internal surface (gray matter).

        leadFieldFile = [];    % Pointer to the file where the lead field matrix was stored.

        channelLabel = []
    end
    properties(GetAccess = private, SetAccess = private)
        label;
        F = [];
    end
%     properties(Dependent)
%     end
    methods
        function obj = headModel(varargin)
            if length(varargin)==1, varargin = varargin{1};end

            if ~iscell(varargin) && ischar(varargin) && exist(varargin,'file')
                [obj.channelSpace,obj.label,obj.fiducials] = readMontage(varargin);
                return
            end

            ind = find(ismember(varargin(1:2:length(varargin)-1),'channelSpace'));
            if ~isempty(ind), obj.channelSpace = varargin{ind*2};end

            ind = find(ismember(varargin(1:2:length(varargin)-1),'surfaces'));
            if ~isempty(ind), obj.surfaces = varargin{ind*2};end
            if ~isempty(obj.surfaces)
                [~,~,e] = fileparts(obj.surfaces);
                if isempty(e), obj.surfaces = [obj.surfaces,'.mat'];end
            end
            ind = find(ismember(varargin(1:2:length(varargin)-1),'atlas'));
            if ~isempty(ind)
                tmpAtlas = varargin{ind*2};
                if isfield(tmpAtlas,'color'),
                    tmpAtlas.colorTable = tmpAtlas.color;
                    tmpAtlas = rmfield(tmpAtlas,'color');
                end
                obj.atlas = tmpAtlas;
            end
            ind = find(ismember(varargin(1:2:length(varargin)-1),'fiducials'));
            if ~isempty(ind), obj.fiducials = varargin{ind*2};end

            ind = find(ismember(varargin(1:2:length(varargin)-1),'leadFieldFile'));
            if ~isempty(ind), obj.leadFieldFile = varargin{ind*2};end
            ind = find(ismember(varargin(1:2:length(varargin)-1),'label'));
            if ~isempty(ind),
                obj.channelLabel = varargin{ind*2};
                obj.label = varargin{ind*2};
            else
                N = size(obj.channelSpace,1);
                labels = num2str((1:N)');
                obj.label =num2cell(labels',[N,1])';
                for it=1:N, obj.label{it} = deblank(obj.label{it});end
            end
        end
        function labels = getChannelLabels(obj)
            labels = obj.label;
            warning('This method will be deprecated in the future, instead you can access directly the property channelLabel.')
        end
        function channelLabel = get.channelLabel(obj)
            channelLabel = obj.label;
        end
        function set.channelLabel(obj, val)
            obj.label = val;
        end
        function dropChannels(obj, ind)
            obj.label(ind) = [];
            obj.channelSpace(ind, :) = [];
        end
        %%
        function [roiname,roinumber] = labelDipole(obj,dipole)
            if isempty(obj.F)
                load(obj.surfaces)
                obj.F = scatteredInterpolant(surfData(end).vertices(:,1),...
                    surfData(end).vertices(:,2),surfData(end).vertices(:,3),...
                    obj.atlas.colorTable,'nearest');
            end
            roinumber = obj.F(dipole(:,1),dipole(:,2),dipole(:,3));
            roiname = obj.atlas.label(roinumber);
        end
        %%
        function chanlocs = makeChanlocs(obj)
            % make EEGLAB chanlocs structure from channel locations and
            % labels
            if isempty(which('convertlocs'))
                error('EEGLAB function convertlocs.m is missing.');
            end
            for k=1:length(obj.label)
                chanlocs(k) = struct('labels',obj.label{k}, ...
                                     'ref','', ...
                                     'theta',[], ...
                                     'radius',[], ...
                                     'X',obj.channelSpace(k,1), ...
                                     'Y',obj.channelSpace(k,2), ...
                                     'Z',obj.channelSpace(k,3), ...
                                     'sph_theta', [], ...
                                     'sph_phi',[], ...
                                     'sph_radius',[], ...
                                     'type', 'EEG', ...
                                     'urchan', []);
            end
            chanlocs = convertlocs( chanlocs, 'cart2all');
        end
        %%
        function h = plotHeadModel(obj,~) % do not remove the circumflex, I'm passging a second arguments when this method is called from MoBILAB's gui
            % Plots the different layers of tissue, the sensor positions, and their labels.
            % It colors different regions of the cortical surface according to a defined
            % anatomical atlas. Several interactive options for customizing the figure are
            % available.

            if isempty(obj.channelSpace) || isempty(obj.label) || isempty(obj.surfaces);
                error('Head model is incomplete or missing.');
            end
            h = headModelViewerHandle(obj,obj.label);
        end
       %%
        function h = plotMontage(obj,showNewfig)
            % Plots a figure with the xyz distribution of sensors, fiducial landmarks, and
            % coordinate axes.

            if isempty(obj.channelSpace) || isempty(obj.label);error('MoBILAB:noChannelSpace','Channel space is empty.');end
            if nargin < 2, showNewfig = true;end

            if isa(obj,'eeg')
                color = [0.93 0.96 1];
            else
                color = [0.76 0.77 1];
            end
            if showNewfig, figure('Color',color);end
            h = scatter3(obj.channelSpace(:,1),obj.channelSpace(:,2),obj.channelSpace(:,3),'filled',...
                'MarkerEdgeColor','k','MarkerFaceColor','y','parent',gca);

            hold on;
            N = length(obj.label);
            k = 1.1;
            for it=1:N, text('Position',k*obj.channelSpace(it,:),'String',obj.label{it});end
            mx = max(obj.channelSpace);
            k = 1.2;
            line([0 k*mx(1)],[0 0],[0 0],'LineStyle','-.','Color','b','LineWidth',2)
            line([0 0],[0 k*mx(2)],[0 0],'LineStyle','-.','Color','g','LineWidth',2)
            line([0 0],[0 0],[0 k*mx(3)],'LineStyle','-.','Color','r','LineWidth',2)
            text('Position',[k*mx(1) 0 0],'String','X','FontSize',12,'FontWeight','bold','Color','b')
            text('Position',[0 k*mx(2) 0],'String','Y','FontSize',12,'FontWeight','bold','Color','g')
            text('Position',[0 0 k*mx(3)],'String','Z','FontSize',12,'FontWeight','bold','Color','r')

            try %#ok
                scatter3(obj.fiducials.nasion(1),obj.fiducials.nasion(2),obj.fiducials.nasion(3),'filled','MarkerEdgeColor','k','MarkerFaceColor','K');
                text('Position',1.1*obj.fiducials.nasion,'String','Nas','FontSize',12,'FontWeight','bold','Color','k');

                scatter3(obj.fiducials.lpa(1),obj.fiducials.lpa(2),obj.fiducials.lpa(3),'filled','MarkerEdgeColor','k','MarkerFaceColor','K');
                text('Position',1.1*obj.fiducials.lpa,'String','LPA','FontSize',12,'FontWeight','bold','Color','k');

                scatter3(obj.fiducials.rpa(1),obj.fiducials.rpa(2),obj.fiducials.rpa(3),'filled','MarkerEdgeColor','k','MarkerFaceColor','K');
                text('Position',1.1*obj.fiducials.rpa,'String','RPA','FontSize',12,'FontWeight','bold','Color','k');

                scatter3(obj.fiducials.vertex(1),obj.fiducials.vertex(2),obj.fiducials.vertex(3),'filled','MarkerEdgeColor','k','MarkerFaceColor','K');
                text('Position',1.1*obj.fiducials.vertex,'String','Ver','FontSize',12,'FontWeight','bold','Color','k');

                scatter3(obj.fiducials.inion(1),obj.fiducials.inion(2),obj.fiducials.inion(3),'filled','MarkerEdgeColor','k','MarkerFaceColor','K');
                text('Position',1.1*obj.fiducials.inion,'String','Ini','FontSize',12,'FontWeight','bold','Color','k');
            end

            % box on;
            hold off;
            axis equal
            axis vis3d
            grid on;
        end
       %%
        function individualHeadModelFile = warpTemplate2channelSpace(obj,headModelFile,individualHeadModelFile)
            % Warps a template head model to the space defined by the sensor positions (channelSpace). It uses Dirk-Jan Kroon's
            % nonrigid_version23 toolbox.
            %
            % For more details see: http://www.mathworks.com/matlabcentral/fileexchange/20057-b-spline-grid-image-and-point-based-registration
            %
            % Input arguments:
            %       headModelFile:           pointer to the template head model file. To see an example of
            %                                templates see the folder mobilab/data/headModelXX.mat
            %       individualHeadModelFile: pointer to the warped head model (output file)
            %
            % Output arguments:
            %       individualHeadModelFile: pointer to the warped head model (same as the second input argument)
            %
            % References:
            %    D. Rueckert et al. "Nonrigid Registration Using Free-Form Deformations: Application to Breast MR Images".
            %    Seungyong Lee, George Wolberg, and Sung Yong Shing, "Scattered Data interpolation with Multilevel B-splines"

            if nargin < 2, error('Reference head model is missing.');end
            if nargin < 3, individualHeadModelFile = [tempname '.mat'];end
            if isempty(obj.channelSpace) || isempty(obj.label), error('Channel space or labels are missing.');end
            if ~exist(headModelFile,'file'), error('The file you''ve entered does not exist.');end

            template = load(headModelFile);
            gTools = geometricTools;
            th = norminv(0.90);
            % mapping source to target spaces: S->T
            % target space: individual geometry

            try
                T = [obj.fiducials.nasion;...
                    obj.fiducials.lpa;...
                    obj.fiducials.rpa];

                % source space: template
                S = [template.fiducials.nasion;...
                    template.fiducials.lpa;...
                    template.fiducials.rpa;...
                    template.fiducials.vertex];

                % estimates vertex if is missing
                if isfield(obj.fiducials,'vertex')
                    if numel(obj.fiducials.vertex) == 3
                        T = [T;obj.fiducials.vertex];
                    else
                        point = 0.5*(obj.fiducials.lpa + obj.fiducials.rpa);
                        point = ones(50,1)*point;
                        point(:,3) = linspace(point(3),1.5*max(obj.channelSpace(:,3)),50)';
                        [~,d] = gTools.nearestNeighbor(obj.channelSpace,point);
                        [~,loc] = min(d);
                        point = point(loc,:);
                        T = [T;point];
                    end
                else
                    point = 0.5*(obj.fiducials.lpa + obj.fiducials.rpa);
                    point = ones(50,1)*point;
                    point(:,3) = linspace(point(3),1.5*max(obj.channelSpace(:,3)),50)';
                    [~,d] = gTools.nearestNeighbor(obj.channelSpace,point);
                    [~,loc] = min(d);
                    point = point(loc,:);
                    T = [T;point];
                end

                if isfield(obj.fiducials,'inion')
                    if numel(obj.fiducials.vertex) == 3
                        T = [T;obj.fiducials.inion];
                        S = [S;template.fiducials.inion];
                    end
                end
            catch
                disp('Fiducials are missing in the individual head model, selecting the common set of points based on the channel labels.')
                [~,loc1,loc2] = intersect(channelLabel,template.label,'stable');
                T = obj.channelSpace(loc1,:);
                S = template.channelSpace(loc2,:);
            end
            try obj.initStatusbar(1,8,'Co-registering...');end %#ok

            % affine co-registration
            [Aff,~,scale] = gTools.affineMapping(S,T);
            if isa(obj,'eeg'), obj.statusbar(1);end

            % b-spline co-registration (only fiducial landmarks)
            options.Verbose = true;
            options.MaxRef = 2;
            surfData = template.surfData;
            Ns = length(surfData);
            for it=1:Ns
                surfData(it).vertices = gTools.applyAffineMapping(template.surfData(it).vertices,Aff);
            end
            Saff = gTools.applyAffineMapping(S,Aff);
            [Def,spacing,offset] = gTools.bSplineMapping(Saff,T,surfData(1).vertices,options);
            try obj.statusbar(2);end %#ok

            % b-spline co-registration (second pass)
            for it=1:Ns
                surfData(it).vertices = gTools.applyBSplineMapping(Def,spacing,offset,surfData(it).vertices);
            end
            T = obj.channelSpace;
            T(T(:,3) <= min(surfData(1).vertices(:,3)),:) = [];
            [S,d] = gTools.nearestNeighbor(surfData(1).vertices,T);
            z = zscore(d);
            S(abs(z)>th,:) = [];
            T(abs(z)>th,:) = [];
            [Def,spacing,offset] = gTools.bSplineMapping(S,T,surfData(1).vertices,options);
            try obj.statusbar(3);end %#ok

            % b-spline co-registration (third pass)
            for it=1:Ns
                surfData(it).vertices = gTools.applyBSplineMapping(Def,spacing,offset,surfData(it).vertices);
            end
            T = obj.channelSpace;
            T(T(:,3) <= min(surfData(1).vertices(:,3)),:) = [];
            [S,d] = gTools.nearestNeighbor(surfData(1).vertices,T);
            z = zscore(d);
            S(abs(z)>th,:) = [];
            T(abs(z)>th,:) = [];
            Tm = 0.5*(T+S);
            [Def,spacing,offset] = gTools.bSplineMapping(S,Tm,surfData(1).vertices,options);
            try obj.statusbar(4);end %#ok

            % apply the final transformation
            for it=1:Ns
                surfData(it).vertices = gTools.applyBSplineMapping(Def,spacing,offset,surfData(it).vertices);
                surfData(it).vertices = gTools.smoothSurface(surfData(it).vertices,surfData(it).faces);
            end

            % fixing topological defects
            try obj.container.container.statusBar.setText('Fixing topological defects...');end %#ok
            dmax = ones(Ns-1,1)*5;
            dmax(1) = 8;
            dmax = dmax*scale;
            ind = fliplr(1:Ns);
            for it=1:Ns-1
                surfData(ind(it+1)).vertices = gTools.repareIntersectedSurface(surfData(ind(it)),surfData(ind(it+1)),dmax(it));
                try obj.statusbar(it+5);end %#ok
            end

            ind =  obj.channelSpace(:,3) > min(surfData(1).vertices(:,3));
            T = gTools.nearestNeighbor(surfData(1).vertices,obj.channelSpace);
            channelSpace = obj.channelSpace; %#ok
            channelSpace(ind,:) = T(ind,:);  %#ok
            [~,loc] = unique(channelSpace,'rows');%#ok
            indInterp = setdiff(1:size(obj.channelSpace,1),loc);
            if ~isempty(indInterp)
                x = setdiff(channelSpace,channelSpace(indInterp,:),'rows');%#ok
                xi = gTools.nearestNeighbor(x,channelSpace(indInterp,:));%#ok
                channelSpace(indInterp,:) = 0.5*(xi + channelSpace(indInterp,:));%#ok
            end
            obj.channelSpace = channelSpace; %#ok

            if isfield(template,'atlas'),
                if isfield(template.atlas,'color')
                    colorTable = template.atlas.color;
                    template.atlas = rmfield(template.atlas,'color');
                    template.atlas.colorTable = colorTable;
                end
                obj.atlas = template.atlas;
            end
            if exist(obj.surfaces,'file'), delete(obj.surfaces);end
            obj.surfaces = individualHeadModelFile;
            save(obj.surfaces,'surfData');
            try obj.statusbar(8);end %#ok
            disp('Done!')
        end
       %%
        function hFigureObj = plotOnModel(obj,J,V,figureTitle)
            % Plots cortical/topographical maps onto the cortical/scalp surface.
            %
            % Input parameters:
            %       J:           cortical map size number of vertices of the cortical surface by number of time points
            %       V:           topographic map size number of vertices of the scalp surface by number of time points;
            %                    if V is empty, a single color is used simulating the color of the skin
            %       figureTitle: title of the figure (optional)
            %
            % Output argument:
            %       hFigure:     figure handle

            if nargin < 2, error('Not enough input arguments');end
            if nargin < 3, V = [];end
            if nargin < 4, figureTitle = '';end
            if isa(obj,'pcdStream'), channelLabels = obj.parent.channelLabel;else channelLabels = obj.channelLabel;end
            hFigureObj = currentSourceViewer(obj,J,V,figureTitle,channelLabels);
        end
       %%
        function Aff = warpChannelSpace2Template(obj,headModelFile,individualHeadModelFile,regType)
            % Estimates a mapping from channel space to a template's head. It uses Dirk-Jan Kroon's
            % nonrigid_version23 toolbox.
            %
            % For more details see: http://www.mathworks.com/matlabcentral/fileexchange/20057-b-spline-grid-image-and-point-based-registration
            %
            % Input arguments:
            %       headModelFile:           pointer to the template head model file. To see an example
            %                                of templates see the folder mobilab/data/headModelXX.mat
            %       individualHeadModelFile: pointer to the warped head model (output file)
            %       regType:                 co-registration type, could be 'affine' or 'bspline'. In case
            %                                of 'affine' only the affine mapping is estimated (rotation,
            %                                traslation, and scaling). 'bspline' starts from the affine
            %                                mapping and goes on to estimate a non-linear defformation
            %                                field that captures better the shape of the head.
            %
            % Output arguments:
            %       Aff: affine matrix
            %
            % References:
            %    D. Rueckert et al. "Nonrigid Registration Using Free-Form Deformations: Application to Breast MR Images".
            %    Seungyong Lee, George Wolberg, and Sung Yong Shing, "Scattered Data interpolation with Multilevel B-splines"

            if nargin < 2, error('Reference head model is missing.');end
            if nargin < 3, individualHeadModelFile = ['surfaces_' num2str(round(1e5*rand)) '.mat'];end
            if nargin < 4, regType = 'bspline';end
            if isempty(obj.channelSpace) || isempty(obj.label) || isempty(obj.fiducials), error('Channel space or fiducials are missing.');end
            if ~exist(headModelFile,'file'), error('The file you''ve entered does not exist.');end

            template = load(headModelFile);
            surfData = template.surfData;
            gTools = geometricTools;
            th = norminv(0.90);
            % mapping source to target spaces: S->T
            % target space: template
            T = [template.fiducials.nasion;...
                template.fiducials.lpa;...
                template.fiducials.rpa;...
                template.fiducials.vertex];

            % source space: individual geometry
            S = [obj.fiducials.nasion;...
                obj.fiducials.lpa;...
                obj.fiducials.rpa];

            % estimates vertex if is missing
            if isfield(obj.fiducials,'vertex')
                if numel(obj.fiducials.vertex) == 3
                    S = [S;obj.fiducials.vertex];
                else
                    point = 0.5*(obj.fiducials.lpa + obj.fiducials.rpa);
                    point = ones(50,1)*point;
                    point(:,3) = linspace(point(3),1.5*max(obj.channelSpace(:,3)),50)';
                    [~,d] = gTools.nearestNeighbor(obj.channelSpace,point);
                    [~,loc] = min(d);
                    point = point(loc,:);
                    S = [S;point];
                end
            else
                point = 0.5*(obj.fiducials.lpa + obj.fiducials.rpa);
                point = ones(50,1)*point;
                point(:,3) = linspace(point(3),1.5*max(obj.channelSpace(:,3)),50)';
                [~,d] = gTools.nearestNeighbor(obj.channelSpace,point);
                [~,loc] = min(d);
                point = point(loc,:);
                S = [S;point];
            end

            if isfield(obj.fiducials,'inion')
                if numel(obj.fiducials.vertex) == 3
                    S = [S;obj.fiducials.inion];
                    T = [T;template.fiducials.inion];
                end
            end
            if isa(obj,'eeg')
                obj.initStatusbar(1,8,'Co-registering...');
            else
                disp('Co-registering...');
            end

            % affine co-registration
            Aff = gTools.affineMapping(S,T);
            if isa(obj,'eeg'), obj.statusbar(1);end

            obj.channelSpace = gTools.applyAffineMapping(obj.channelSpace,Aff);
            obj.fiducials.lpa = gTools.applyAffineMapping(obj.fiducials.lpa,Aff);
            obj.fiducials.rpa = gTools.applyAffineMapping(obj.fiducials.rpa,Aff);
            obj.fiducials.nasion = gTools.applyAffineMapping(obj.fiducials.nasion,Aff);

            if ~strcmp(regType,'affine')
                % b-spline co-registration (only fiducial landmarks)
                options.Verbose = true;
                options.MaxRef = 2;
                Saff = gTools.applyAffineMapping(S,Aff);
                [Def,spacing,offset] = gTools.bSplineMapping(Saff,T,obj.channelSpace,options);
                if isa(obj,'eeg'), obj.statusbar(2);end

                % b-spline co-registration (second pass)
                obj.channelSpace = gTools.applyBSplineMapping(Def,spacing,offset,obj.channelSpace);
                obj.fiducials.lpa = gTools.applyBSplineMapping(Def,spacing,offset,obj.fiducials.lpa);
                obj.fiducials.rpa = gTools.applyBSplineMapping(Def,spacing,offset,obj.fiducials.rpa);
                obj.fiducials.nasion = gTools.applyBSplineMapping(Def,spacing,offset,obj.fiducials.nasion);

                T = template.surfData(1).vertices;
                S = obj.channelSpace;
                S(S(:,3) <= min(T(:,3)),:) = [];
                [S,d] = gTools.nearestNeighbor(S,T);
                z = zscore(d);
                S(abs(z)>th,:) = [];
                T(abs(z)>th,:) = [];
                [Def,spacing,offset] = gTools.bSplineMapping(S,T,obj.channelSpace,options);
                if isa(obj,'eeg'), obj.statusbar(3);end

                % b-spline co-registration (third pass)
                obj.channelSpace = gTools.applyBSplineMapping(Def,spacing,offset,obj.channelSpace);
                obj.fiducials.lpa = gTools.applyBSplineMapping(Def,spacing,offset,obj.fiducials.lpa);
                obj.fiducials.rpa = gTools.applyBSplineMapping(Def,spacing,offset,obj.fiducials.rpa);
                obj.fiducials.nasion = gTools.applyBSplineMapping(Def,spacing,offset,obj.fiducials.nasion);

                T = template.surfData(1).vertices;
                S = obj.channelSpace;
                S(S(:,3) <= min(T(:,3)),:) = [];
                [S,d] = gTools.nearestNeighbor(S,T);
                z = zscore(d);
                S(abs(z)>th,:) = [];
                T(abs(z)>th,:) = [];
                Tm = 0.5*(T+S);
                [Def,spacing,offset] = gTools.bSplineMapping(S,Tm,obj.channelSpace,options);
                if isa(obj,'eeg'), obj.statusbar(4);end

                % apply the final transformation
                obj.channelSpace = gTools.applyBSplineMapping(Def,spacing,offset,obj.channelSpace);
                obj.fiducials.lpa = gTools.applyBSplineMapping(Def,spacing,offset,obj.fiducials.lpa);
                obj.fiducials.rpa = gTools.applyBSplineMapping(Def,spacing,offset,obj.fiducials.rpa);
                obj.fiducials.nasion = gTools.applyBSplineMapping(Def,spacing,offset,obj.fiducials.nasion);
            end

            % fixing topological defects
            if isa(obj,'eeg')
                obj.statusbar.setText('Fixing topological defects...');
            else
                disp('Fixing topological defects...');
            end
            Ns = length(surfData);
            dmax = ones(Ns-1,1)*5;
            dmax(1) = 8;
            ind = fliplr(1:Ns);
            for it=1:Ns-1
                surfData(ind(it+1)).vertices = gTools.repareIntersectedSurface(surfData(ind(it)),surfData(ind(it+1)),dmax(it));
                if isa(obj,'eeg'), obj.statusbar(it+5);end
            end

            ind =  obj.channelSpace(:,3) > min(surfData(1).vertices(:,3));
            T = gTools.nearestNeighbor(surfData(1).vertices,obj.channelSpace);
            channelSpace = obj.channelSpace; %#ok
            channelSpace(ind,:) = T(ind,:);  %#ok
            [~,loc] = unique(channelSpace,'rows');%#ok
            indInterp = setdiff(1:size(obj.channelSpace,1),loc);
            if ~isempty(indInterp)
                x = setdiff(channelSpace,channelSpace(indInterp,:),'rows');%#ok
                xi = gTools.nearestNeighbor(x,channelSpace(indInterp,:));%#ok
                channelSpace(indInterp,:) = 0.5*(xi + channelSpace(indInterp,:));%#ok
            end
            obj.channelSpace = channelSpace; %#ok

            if isfield(template,'atlas'), obj.atlas = template.atlas;end
            if exist(obj.surfaces,'file'), delete(obj.surfaces);end
            obj.surfaces = individualHeadModelFile;
            save(obj.surfaces,'surfData');
            if isa(obj,'eeg'), obj.statusbar(8);end
        end
       %%
        function computeLeadFieldBEM(obj, conductivity,orientation)
            % Computes the lead field matrix interfacing OpenMEEG toolbox [1].
            %
            % Input arguments:
            %       conductivity: conductivity of each layer of tissue, scalp - skull - brain,
            %                     default: 0.33-0.022-0.33 S/m. See [2, 3, 4] for details.
            %        orientation: if true, computes the orientation free lead field, otherwise
            %                     it constrain the dipoles to be normal to the cortical surface
            %
            % The computed lead field is stored inside the object in obj.leadFieldFile.
            %
            % References:
            %   [1] Gramfort, A., Papadopoulo, T., Olivi, E., & Clerc, M. (2010).
            %         OpenMEEG: opensource software for quasistatic bioelectromagnetics.
            %         Biomedical engineering online, 9, 45. doi:10.1186/1475-925X-9-45
            %   [2] Vald??s-Hern??ndez, P.A., Von Ellenrieder, N., Ojeda-Gonzalez, A., Kochen, S.,
            %         Alem??n-G??mez, Y., Muravchik, C., & A Vald??s-Sosa, P. (2009). Approximate
            %         average head models for EEG source imaging. Journal of Neuroscience Methods,
            %         185(1), 125???132.
            %   [3] Wendel, K., Malmivuo, J., 2006. Correlation between live and post mortem skull
            %         conductivity measurements. Conf Proc IEEE Eng Med Biol Soc 1, 4285-4288.
            %   [4] Oostendorp, T.F., Delbeke, J., Stegeman, D.F., 2000. The conductivity of the
            %         human skull: Results of in vivo and in vitro measurements. Ieee Transactions
            %         on Biomedical Engineering 47, 1487-1492.

            dispCommand = false;
            if nargin < 2, conductivity = [0.33 0.022 0.33];end
            if nargin < 3, orientation = true;end
            if isempty(obj.channelSpace) || isempty(obj.label) || isempty(obj.surfaces);
                error('Head model is incomplete or missing.');
            end
            if any(conductivity == -1)
                prefObj = [...
                    PropertyGridField('conductivity',[0.33 0.022 0.33],'DisplayName','Conductivity','Description',sprintf('Conductivity values are taken from Valdes-Hernandez et al., 2006, check \nalso Oostendrop TF, 2000; Wendel and Malmivuo, 2006. \nbrain and scalp: 0.33 S/m\nskull: 0.022 S/m'))...
                    PropertyGridField('orientation',true,'DisplayName','Orientation free','Description','If true, computes the LF matrix with orientation free dipoles, resulting in a matris Nsensors X 3*Nvertices. If false the LF matrix is computed with dipoles normal to the cortical surface, then the size would be Nsensors X Nvertices')...
                    ];
                hFigure = figure('MenuBar','none','Name','OpenMEEG solver','NumberTitle', 'off','Toolbar', 'none','Units','pixels','Color',obj.container.container.preferences.gui.backgroundColor,...
                    'Resize','off','userData',0);
                position = get(hFigure,'position');
                set(hFigure,'position',[position(1:2) 303 250]);
                hPanel = uipanel(hFigure,'Title','','BackgroundColor','white','Units','pixels','Position',[0 55 303 175],'BorderType','none');
                g = PropertyGrid(hPanel,'Properties', prefObj,'Position', [0 0 1 1]);
                uicontrol(hFigure,'Position',[72 15 70 21],'String','Cancel','ForegroundColor',obj.container.container.preferences.gui.fontColor,...
                    'BackgroundColor',obj.container.container.preferences.gui.buttonColor,'Callback',@cancelCallback);
                uicontrol(hFigure,'Position',[164 15 70 21],'String','Ok','ForegroundColor',obj.container.container.preferences.gui.fontColor,...
                    'BackgroundColor',obj.container.container.preferences.gui.buttonColor,'Callback',@okCallback);
                uiwait(hFigure);
                if ~ishandle(hFigure), return;end
                if ~get(hFigure,'userData'), close(hFigure);return;end
                close(hFigure);
                drawnow;
                val = g.GetPropertyValues();
                conductivity = val.conductivity;
                orientation = val.orientation;
                dispCommand = true;
            end

            if dispCommand
                disp('Running:');
                if isa(obj,'coreStreamObject')
                    itemIndex = obj.container.findItem(obj.uuid);
                    fprintf('  mobilab.allStreams.item{%i}.computeLeadFieldBEM( [ %i %i %i ], %i );\n',itemIndex,conductivity(1),conductivity(2),conductivity(3),orientation);
                else fprintf('  obj.computeLeadFieldBEM( [ %i %i %i ], %i );\n',conductivity(1),conductivity(2),conductivity(3),orientation);
                end
            end

            if ~exist(obj.surfaces,'file'), error('The file containing the surfaces is missing.');end
            status = system('which om_assemble');
            existOM = ~status;
            if ~existOM
                try
                    mobilab = evalin('base','mobilab');
                    mobilabPath = mobilab.path;
                catch
                    mobilabPath = which('mobilabApplication');
                    if ~isempty(mobilabPath), mobilabPath = fileparts(mobilabPath);
                    else error('OpenMEEG is not intalled. Please download and install the sources you need from https://gforge.inria.fr/frs/?group_id=435.');
                    end
                end
                openmeegDir = [mobilabPath filesep 'dependency' filesep 'openmeeg'];

                %---
                % Approach taken from Brainstorm's function bst_openmeeg,
                % Francois Tadel & Alexandre Gramfort, 2011
                %---
                if ~ispc
                    if ismember(computer, {'GLNX86','GLNXA64'}), varname = 'LD_LIBRARY_PATH';
                    else varname = 'DYLD_LIBRARY_PATH';
                    end
                    libpath = getenv(varname);
                    if ~isempty(libpath), libpath = [libpath ':'];end
                    if isempty(strfind(lower(libpath),'openmeeg')), setenv(varname, [libpath openmeegDir]);end
                end
                % Set number of cores used
                try numcores = feature('numcores');
                catch
                    numcores = 4;
                end
                setenv('OMP_NUM_THREADS', num2str(numcores));
                %---
            end

            load(obj.surfaces);
            Ns = length(surfData); %#ok
            gTools = geometricTools;
            rootDir = fileparts(obj.surfaces);
            if isempty(rootDir), rootDir = pwd;end
            binDir = fileparts(which('libmatio.a'));
            [~,rname] = fileparts(tempname);
            headModelGeometry = fullfile(rootDir,[rname '.geom']);
            try %#ok
                copyfile( which('head_model.geom'),headModelGeometry,'f');
                c1 = onCleanup(@()delete(headModelGeometry));
            end

            headModelConductivity = fullfile(rootDir,[rname '.cond']);
            fid = fopen(headModelConductivity,'w');
            fprintf(fid,'# Properties Description 1.0 (Conductivities)\n\nAir         0.0\nScalp       %.3f\nBrain       %0.3f\nSkull       %0.3f',...
                conductivity(1),conductivity(3),conductivity(2));
            fclose(fid);
            c2 = onCleanup(@()delete(headModelConductivity));

            dipolesFile = fullfile(rootDir,[rname '_dipoles.txt']);
            normalsIn = false;
            [normals,surfData(Ns).faces] = gTools.getSurfaceNormals(surfData(Ns).vertices,surfData(Ns).faces,normalsIn);

            normalityConstrained = ~orientation;
            if normalityConstrained, sourceSpace = [surfData(Ns).vertices normals];
            else One = ones(length(normals(:,2)),1);
                Zero = 0*One;
                sourceSpace = [surfData(Ns).vertices One Zero Zero;...
                    surfData(Ns).vertices Zero One Zero;...
                    surfData(Ns).vertices Zero Zero One];
            end
            dlmwrite(dipolesFile, sourceSpace, 'precision', 6,'delimiter',' ')
            c3 = onCleanup(@()delete(dipolesFile));

            electrodesFile = fullfile(rootDir,[rname '_elec.txt']);
            dlmwrite(electrodesFile, obj.channelSpace, 'precision', 6,'delimiter',' ')
            c4 = onCleanup(@()delete(electrodesFile));

            normalsIn = true;
            brain = fullfile(rootDir,'brain.tri');
            if Ns == 4
                [normals,surfData(3).faces] = gTools.getSurfaceNormals(surfData(3).vertices,surfData(3).faces,normalsIn);
                om_save_tri(brain,surfData(3).vertices,surfData(3).faces,normals)
            else
                [normals,surfData(2).faces] = gTools.getSurfaceNormals(surfData(2).vertices,surfData(2).faces,normalsIn);
                csfSurf = surfData(2);
                csfSurf.vertices     = surfData(2).vertices + 1.05*normals;
                surfData(2).vertices = surfData(2).vertices - 1.05*normals;
                csfSurf.vertices     = gTools.repareIntersectedSurface(surfData(end),csfSurf,1);
                surfData(2).vertices = gTools.repareIntersectedSurface(csfSurf,surfData(2),2);
                surfData(1).vertices = gTools.repareIntersectedSurface(surfData(2),surfData(1),2);
                [normals,csfSurf.faces] = gTools.getSurfaceNormals(csfSurf.vertices,csfSurf.faces,normalsIn);
                om_save_tri(brain,csfSurf.vertices,csfSurf.faces,normals)
            end
            c5 = onCleanup(@()delete(brain));

            skull = fullfile(rootDir,'skull.tri');
            [normals,surfData(2).faces] = gTools.getSurfaceNormals(surfData(2).vertices,surfData(2).faces,normalsIn);
            om_save_tri(skull,surfData(2).vertices,surfData(2).faces,normals)
            c6 = onCleanup(@()delete(skull));

            head = fullfile(rootDir,'head.tri');
            [normals,surfData(1).faces] = gTools.getSurfaceNormals(surfData(1).vertices,surfData(1).faces,normalsIn);
            om_save_tri(head,surfData(1).vertices,surfData(1).faces,normals)
            c7 = onCleanup(@()delete(head));

            hmFile    = fullfile(rootDir,'hm.bin');    c8  = onCleanup(@()delete(hmFile));
            hmInvFile = fullfile(rootDir,'hm_inv.bin');c9  = onCleanup(@()delete(hmInvFile));
            dsmFile   = fullfile(rootDir,'dsm.bin');   c10 = onCleanup(@()delete(dsmFile));
            h2emFile  = fullfile(rootDir,'h2em.bin');  c11 = onCleanup(@()delete(h2emFile));
            lfFile    = fullfile(rootDir,[rname '_LF.mat']);

            if ~existOM
                runHere = './';
                wDir = pwd;
                cd(binDir);
            else runHere = '';
            end
            try
                out = system([runHere 'om_assemble -HM "' headModelGeometry '" "' headModelConductivity '" "' hmFile '"']);
                if out, error('An unexpected error occurred running OpenMEEG binaries. Report this to alejandro@sccn.ucsd.edu');end

                out = system([runHere 'om_minverser "' hmFile '" "' hmInvFile '"']);
                if out, error('An unexpected error occurred running OpenMEEG binaries. Report this to alejandro@sccn.ucsd.edu');end

                out = system([runHere 'om_assemble -DSM "' headModelGeometry '" "' headModelConductivity '" "' dipolesFile '" "' dsmFile '"']);
                if out, error('An unexpected error occurred running OpenMEEG binaries. Report this to alejandro@sccn.ucsd.edu');end

                out = system([runHere 'om_assemble -H2EM "' headModelGeometry '" "' headModelConductivity '" "' electrodesFile '" "' h2emFile '"']);
                if out, error('An unexpected error occurred running OpenMEEG binaries. Report this to alejandro@sccn.ucsd.edu');end

                out = system([runHere 'om_gain -EEG "' hmInvFile '" "' dsmFile '" "' h2emFile '" "' lfFile '"']);
                if out, error('An unexpected error occurred running OpenMEEG binaries. Report this to alejandro@sccn.ucsd.edu');end
            catch ME
                if strcmp(pwd,binDir), cd(wDir);end
                ME.rethrow;
            end
            if strcmp(pwd,binDir), cd(wDir);end
            if ~exist(lfFile,'file'), error('An unexpected error occurred running OpenMEEG binaries. Report this to alejandro@sccn.ucsd.edu');end

            load(lfFile);
            K = linop;
            clear linop;

            %-- Remove extreme values due to numerical instability
            z = zscore(K(:));
            a = 0.00001;
            ind = find(z<norminv(a) | z>norminv(1-a));
            ind_i = setdiff(1:numel(K),ind);
            K(ind) = interp1(ind_i,K(ind_i),ind,'nearest','extrap');
            %--

            if exist(lfFile,'file'), delete(lfFile);end
            if exist(obj.leadFieldFile,'file'), delete(obj.leadFieldFile);end
            if isa(obj,'coreStreamObject'), lfFile = fullfile(obj.container.mobiDataDirectory,['lf_' obj.name '_' obj.uuid '_' obj.sessionUUID '.mat']);end
            obj.leadFieldFile = lfFile;
            save(obj.leadFieldFile,'K');
            if isa(obj,'coreStreamObject'), saveProperty(obj,'leadFieldFile',obj.leadFieldFile);end
            disp('Done.')
        end
       %%
        function [sourceSpace,K,L,rmIndices] = getSourceSpace4PEB(obj,structName, rmIndices)
            if isempty(obj.surfaces) || isempty(obj.leadFieldFile), error('Head model or leadfield are missing.');end
            if nargin < 2
                structName = {'Thalamus_L' 'Thalamus_R'};
                disp('Undefined structure to remove. Opening the surface by the Thalamus.')
            end
            if nargin < 3, rmIndices = [];end
            maxNumVertices2rm = 10;
            load(obj.surfaces,'-mat');
            sourceSpace = surfData(end); %#ok
            load(obj.leadFieldFile,'-mat');
            if ~exist('L','var'),
                disp('Computing the Laplacian operator...')
                L = geometricTools.getSurfaceLaplacian(sourceSpace.vertices,sourceSpace.faces);
                save(obj.leadFieldFile,'K','L','-mat')
            end

            try
                [sourceSpace,rmIndices] = obj.removeStructureFromSourceSpace(structName,maxNumVertices2rm, rmIndices);
            catch ME
                warning(ME.message);
                disp('Doing my best to open the surface.')
                n = size(sourceSpace.vertices,1);
                rmIndices = fix(n/2)-maxNumVertices2rm/2:fix(n/2)+maxNumVertices2rm/2;
            end
            dim = size(K); %#ok
            L(rmIndices,:) = [];
            L(:,rmIndices) = [];
            if dim(2)/3 == size(surfData(end).vertices,1) %#ok
                K = reshape(K,[dim(1) dim(2)/3 3]);
                K(:,rmIndices,:) = [];
                % K = permute(K,[1 3 2]);
                K = reshape(K,[dim(1) (dim(2)/3-length(rmIndices))*3]);
                L = kron(eye(3),L);
            else
                K(:,rmIndices) = [];
            end
        end
       %%
        function indices = indices4Structure(obj,structName)
            if nargin < 2, error('Not enough input arguments.');end
            ind = find(ismember(obj.atlas.label,structName));
            if isempty(ind), error('MoBILAB:noStructureMatched','The structure you want to remove is not defined in this atlas.');end
            indices = bsxfun(@eq,obj.atlas.colorTable,ind');
        end
       %%
        function xyz = getCentroidROI(obj,ROInames)
            if nargin < 2, error('Not enough input arguments.');end
            if isempty(obj.atlas) || isempty(obj.surfaces), error('Head model or atlas are missing.');end
            if ~iscell(ROInames), ROInames = {ROInames}; end
            N = length((ROInames));
            xyz = nan(N,3);
            load(obj.surfaces);
            for it=1:N
                try indices = obj.indices4Structure(ROInames{it});
                    xyz(it,:) = mean(surfData(end).vertices(indices,:));
                end
            end
        end
       %%
        function [FP,S] = getForwardProjection(obj,xyz)
            if nargin < 2, error('Not enough input arguments.');end
            if isempty(obj.atlas) || isempty(obj.surfaces), error('Head model or atlas are missing.');end
            if ~exist(obj.surfaces,'file'), error('Head model is missing.');end
            if isempty(obj.leadFieldFile), error('Lead field is missing.');end
            if ~exist(obj.leadFieldFile,'file'), error('Lead field is missing.');end
            load(obj.leadFieldFile);
            if ~exist('K','var'), error('Lead field is missing.');end

            load(obj.surfaces);
            [~,~,loc] = geometricTools.nearestNeighbor(surfData(end).vertices,xyz);
            dim = size(K);
            if size(surfData(end).vertices,1) == dim(2)/3, K = reshape(K,[dim(1) dim(2)/3 3]);end
            FP = sum(K(:,loc,:),3);
            S = geometricTools.simulateGaussianSource(surfData(end).vertices,xyz,0.01);
        end
       %%
        function hFigureObj = plotDipoles(obj,xyz,ecd,dipoleLabel,figureTitle)
            if nargin < 2, error('Not enough input arguments.');end
            if isempty(obj.surfaces), error('Head model is missing.');end
            N = size(xyz,1);
            if nargin < 3, ecd = 3*ones(N,3);end
            if isempty(ecd), ecd = 3*ones(N,3);end
            if nargin < 4, dipoleLabel = [];end
            if nargin < 5, figureTitle = '';end
            hFigureObj = equivalentCurrentDipoleViewer(obj,xyz,ecd,dipoleLabel,figureTitle);
        end
        function hFigureObj = plotDipolesForwardProjection(obj,xyz,figureTitle)
            [FP,S] = getForwardProjection(obj,xyz);
            hFigureObj = obj.plotOnModel(S,FP);
        end
       %%
        function [sourceSpace,rmIndices] = removeStructureFromSourceSpace(obj,structName,maxNumVertices2rm, structIndices)
            if isempty(obj.atlas) || isempty(obj.surfaces), error('Head model or atlas are missing.');end
            if nargin < 2, error('Not enough input arguments.');end
            if nargin < 3, maxNumVertices2rm = [];end
            if nargin < 4, structIndices = [];end
            if ~iscell(structName), structName = {structName}; end

            load(obj.surfaces,'-mat');
            sourceSpace = surfData(end);%#ok

            tmpIndices = indices4Structure(obj,structName);
            if ~any(tmpIndices(:)) && isempty(structIndices),
                error('The structure you want to remove is not defined in this atlas.');
            end

            if ~isempty(structIndices)
                % concatenate elements of structIndices into a single column vector
                structIndices = cellfun(@(x)x(:),structIndices,'UniformOutput',false)';
                structIndices = cell2mat(structIndices);
            end
            if ~isempty(maxNumVertices2rm) && any(sum(tmpIndices) > maxNumVertices2rm+1)
                I = [];
                maxNumVertices2rm = fix(maxNumVertices2rm/size(tmpIndices,2));
                for it=1:size(tmpIndices,2)
                    ind = find(tmpIndices(:,it));
                    if length(ind) > maxNumVertices2rm
                        I = [I; ind(1:maxNumVertices2rm)];
                    else
                        I = [I; ind];
                    end
                end
                tmpIndices = I;
            end
            rmIndices = unique_bc([tmpIndices(:) ; structIndices]);

            [nVertices,nFaces] = geometricTools.openSurface(sourceSpace.vertices,sourceSpace.faces,rmIndices);
            sourceSpace.vertices = nVertices;
            sourceSpace.faces = nFaces;
        end
       %%
        function saveToFile(obj,file)
            metadata = struct(obj);
            if exist(metadata.surfaces,'file')
                metadata.surfData = load(metadata.surfaces);
            else metadata.surfData = [];
            end
            if exist(metadata.leadFieldFile,'file')
                metadata.leadField = load(metadata.leadFieldFile);
            else metadata.leadField = []; %#ok
            end
            save(file,'metadata','-mat');
        end
        function delete(obj)
            if exist(obj.surfaces,'file')
                [~,filename] = fileparts(obj.surfaces);
                if filename(1) == '.', delete(obj.surfaces);end
            end
            if exist(obj.leadFieldFile,'file')
                [~,filename] = fileparts(obj.leadFieldFile);
                if filename(1) == '.', delete(obj.leadFieldFile);end
            end
        end
    end
    methods(Static)
        function obj = loadFromFile(file)
            metadata = load(file,'-mat');
            if isfield(metadata,'metadata')
                metadata = metadata.metadata;
            end
            if ~isempty(metadata.surfData)
                surfData = metadata.surfData;
                if isfield(surfData,'surfData'), surfData = surfData.surfData;end%#ok
                % [~,filename] = fileparts(tempname);
                % metadata.surfaces = [getHomeDir filesep '.' filename '.mat'];
                metadata.surfaces = [tempname '.mat'];
                save(metadata.surfaces,'surfData');
            end
            if isfield(metadata,'leadField') && ~isempty(metadata.leadField)
                % [~,filename] = fileparts(tempname);
                % metadata.leadFieldFile = [getHomeDir filesep '.' filename '.mat'];
                metadata.leadFieldFile = [tempname '.mat'];
                if isfield(metadata.leadField,'K')
                    K = metadata.leadField.K; %#ok
                else
                    K = metadata.leadField; %#ok
                end
                if isfield(metadata.leadField,'L')
                    L = metadata.leadField.L; %#ok
                    save(metadata.leadFieldFile,'K','L');
                else save(metadata.leadFieldFile,'K');
                end
            else
                metadata.leadFieldFile = [];
            end
            obj = headModel('channelSpace',metadata.channelSpace,'fiducials',metadata.fiducials,'surfaces',metadata.surfaces,...
                'atlas',metadata.atlas,'leadFieldFile',metadata.leadFieldFile,'label',metadata.label);
        end
    end
end

%--
function [elec,labels,fiducials] = readMontage(file)
[eloc, labels] = readlocs(file);
elec = [cell2mat({eloc.X}'), cell2mat({eloc.Y}'), cell2mat({eloc.Z}')];
Nl = length(labels);
count = 1;
lowerLabels = lower(labels);
rmThis = false(Nl,1);
for it=1:Nl
    if ~isempty(strfind(lowerLabels{it},'fidnz')) || ~isempty(strfind(lowerLabels{it},'nasion')) || ~isempty(strfind(lowerLabels{it},'Nz'))
        fiducials.nasion = elec(it,:);
        rmThis(it) = true;
        count = count+1;
    elseif ~isempty(strfind(lowerLabels{it},'fidt9')) || ~isempty(strfind(lowerLabels{it},'lpa')) || ~isempty(strfind(lowerLabels{it},'LPA'))
        fiducials.lpa = elec(it,:);
        rmThis(it) = true;
        count = count+1;
    elseif ~isempty(strfind(lowerLabels{it},'fidt10')) || ~isempty(strfind(lowerLabels{it},'rpa')) || ~isempty(strfind(lowerLabels{it},'RPA'))
        fiducials.rpa = elec(it,:);
        rmThis(it) = true;
        count = count+1;
    elseif ~isempty(strfind(lowerLabels{it},'fidt10')) || ~isempty(strfind(lowerLabels{it},'vertex'))
        fiducials.vertex = elec(it,:);
        rmThis(it) = true;
        count = count+1;
    end
    if count > 4, break;end
end
elec(rmThis,:) = [];
labels(rmThis) = [];
end

%% unique_bc - unique backward compatible with Matlab versions prior to 2013a
function [C,IA,IB] = unique_bc(A,varargin);

errorFlag = error_bc;

v = version;
indp = find(v == '.');
v = str2num(v(1:indp(2)-1));
if v > 7.19, v = floor(v) + rem(v,1)/10; end;

if nargin > 2
    ind = strmatch('legacy', varargin);
    if ~isempty(ind)
        varargin(ind) = [];
    end;
end;

if v >= 7.14
    [C,IA,IB] = unique(A,varargin{:},'legacy');
    if errorFlag
        [C2,IA2] = unique(A,varargin{:});
        if ~isequal(C, C2) || ~isequal(IA, IA2) || ~isequal(IB, IB2)
            warning('backward compatibility issue with call to unique function');
        end;
    end;
else
    [C,IA,IB] = unique(A,varargin{:});
end
end

%% ismember_bc - ismember backward compatible with Matlab versions prior to 2013a
function [C,IA] = ismember_bc(A,B,varargin);

errorFlag = error_bc;

v = version;
indp = find(v == '.');
v = str2num(v(1:indp(2)-1));
if v > 7.19, v = floor(v) + rem(v,1)/10; end;

if nargin > 2
    ind = strmatch('legacy', varargin);
    if ~isempty(ind)
        varargin(ind) = [];
    end;
end;

if v >= 7.14
    [C,IA] = ismember(A,B,varargin{:},'legacy');
    if errorFlag
        [C2,IA2] = ismember(A,B,varargin{:});
        if (~isequal(C, C2) || ~isequal(IA, IA2))
            warning('backward compatibility issue with call to ismember function');
        end;
    end;
else
    [C,IA] = ismember(A,B,varargin{:});
end
end

%%
function res = error_bc
res = false;
end


function model = vargplvmModelInit(model, globalOpt)

% VARGPLVMMODELINIT Initialise a vargplvm model given global demo options
% COPYRIGHT: Andreas C. Damianou, 2012
% SEEALSO: vargplvmCreate
% VARGPLVM

%if globalOpt.DgtN
   % model.mOrig = model.m;
%    model = vargplvmParamInit(model, model.mOrig, model.X);
%else
    model = vargplvmParamInit(model, model.y, model.X);
%end

% Lengthscales
if strcmp(model.kern.type, 'rbfardjit')
    model.kern.inputScales = globalOpt.invWidthMult./(((max(model.X)-min(model.X))).^2); % Default 5
else
    model.kern.comp{1}.inputScales = globalOpt.invWidthMult./(((max(model.X)-min(model.X))).^2); % Default 5
end

model.vardist.covars = 0.5*ones(size(model.vardist.covars)) + 0.001*randn(size(model.vardist.covars));
%model.kern.comp{1}.variance = max(var(Y)); %%%

if model.N > 50 && globalOpt.enableParallelism
    fprintf('# Parallel computations w.r.t the datapoints!\n');
    model.vardist.parallel = 1;
end

%%%
if ~isfield(globalOpt, 'beta') || isempty(globalOpt.betaInit)
    if model.DgtN
        model.beta = 1/((1/globalOpt.initSNR * var(model.mOrig(:))));
    else
        model.beta = 1/((1/globalOpt.initSNR * var(model.m(:))));
    end
else
    model.beta = globalOpt.betaInit;
end

if model.beta < 1e-7
    warning('Beta was too small... Setting beta to 1e-7')
    model.beta = 1e-7;
elseif model.beta > 1e+7
    warning('Beta was too big... Setting beta to 1e+7')
    model.beta = 1e+7;
end

% Check for inconsistencies
if isfield(model, 'dynamics') && ~isempty(model.dynamics)
    if isfield(model.dynamics, 'reoptimise') && model.dynamics.reoptimise
        if isfield(model, 'fixInducing') && model.fixInducing
            msg = sprintf('Reoptimising inducing points for test and fixing ind. points are incompatible!\nSetting reoptimising to false.');
            warning(msg);
            model.dynamics.reoptimise = false;
            globalOpt.testReoptimise = false;
        end
    end
end

model.dataSetInfo.dataSetName = globalOpt.dataSetName;

params = vargplvmExtractParam(model);
model = vargplvmExpandParam(model, params);

model.date = date;
model.globalOpt = globalOpt;


function strDate = str_date(s, dateFormat)
% STR_Date: Reformat date string to dd-MMM-yyyy.

% @=============================================================================
% This function is part of the Brainstorm software:
% https://neuroimage.usc.edu/brainstorm
%
% Copyright (c) University of Southern California & McGill University
% This software is distributed under the terms of the GNU General Public License
% as published by the Free Software Foundation. Further details on the GPLv3
% license can be found at http://www.gnu.org/copyleft/gpl.html.
%
% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE
% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY
% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY
% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE.
%
% For more information type "brainstorm license" at command prompt.
% =============================================================================@
%
% Authors: Francois Tadel, 2018

% Parse inputs
if (nargin < 2) || isempty(dateFormat)
    dateFormat = [];
end
% Clean string
if ischar(s)
    s = strtrim(strrep(s, char(0), ''));
end
% Check various input formats
try
    if ~isempty(dateFormat) && strcmpi(dateFormat, 'posix')
        strDate = datestr(double(s) ./ 86400 + datenum(1970,1,1,0,0,0), 'dd-mmm-yyyy');
    elseif ~isempty(dateFormat)
        strDate = datestr(datenum(s, dateFormat), 'dd-mmm-yyyy');
    elseif isequal(find(s == '/'), [3 6]) && ((length(s) == 10) || (length(s) == 19))
        strDate = datestr(datenum(s, 'dd/mm/yyyy'), 'dd-mmm-yyyy');
    elseif isequal(find(s == '/'), [3 6]) && ((length(s) == 8) || (length(s) == 17))
        strDate = datestr(datenum(s, 'dd/mm/yy'), 'dd-mmm-yyyy');
    elseif isequal(find(s == '.'), [3 6]) && ((length(s) == 10) || (length(s) == 19))
        strDate = datestr(datenum(s, 'dd.mm.yyyy'), 'dd-mmm-yyyy');
    elseif isequal(find(s == '.'), [3 6]) && ((length(s) == 8) || (length(s) == 17))
        strDate = datestr(datenum(s, 'dd.mm.yy'), 'dd-mmm-yyyy');
    elseif isequal(find(s == '-'), [3 6]) && ((length(s) == 10) || (length(s) == 19))
        strDate = datestr(datenum(s, 'dd-mm-yyyy'), 'dd-mmm-yyyy');
    elseif isequal(find(s == '-'), [3 7]) && (length(s) == 11)
        strDate = datestr(datenum(s, 'dd-mmm-yyyy'), 'dd-mmm-yyyy');
    elseif isequal(find(s == '-'), [5 8]) && ((length(s) == 10) || (length(s) == 19))
        strDate = datestr(datenum(s, 'yyyy-mm-dd'), 'dd-mmm-yyyy');
    else
        strDate = [];
    end
catch
    strDate = [];
end


function [ fHdl ] = plot( obj, subjects )
% Plot cluster and subject-level estimation result from HUGE model.
%
% INPUTS:
%   obj - A tapas_Huge object containing estimation results.
%
% OPTIONAL INPUTS:
%   subjects - A vector containing indices of subjects for which to plot
%              detailed results.
%
% OUTPUTS:
%   fHdl - Handle of first figure.
%
% See also tapas_Huge.ESTIMATE
%

% Author: Yu Yao (yao@biomed.ee.ethz.ch)
% Copyright (C) 2019 Translational Neuromodeling Unit
%                    Institute for Biomedical Engineering,
%                    University of Zurich and ETH Zurich.
%
% This file is part of TAPAS, which is released under the terms of the GNU
% General Public Licence (GPL), version 3. For further details, see
% <https://www.gnu.org/licenses/>.
%
% This software is provided "as is", without warranty of any kind, express
% or implied, including, but not limited to the warranties of
% merchantability, fitness for a particular purpose and non-infringement.
%
% This software is intended for research only. Do not use for clinical
% purpose. Please note that this toolbox is under active development.
% Considerable changes may occur in future releases. For support please
% refer to:
% https://github.com/translationalneuromodeling/tapas/issues
%

if nargout > 0
    fHdl = figure( );
else
    figure( );
end
if nargin < 2
    subjects = [];
end

tickLabels = obj.parse_labels( obj.dcm, obj.labels, obj.idx );

%% assignments/boxplot
% figure;
if obj.K > 1
    % subject assignment
    bar(obj.posterior.q_nk,'stacked');
    axis([0 obj.N+1 0 1])
    title('assignments')
    ylabel('q_{nk}')
    xlabel('subject index')
    title('Assignment');
else
    % boxplot of MAP estimates of DCM parameters
    hold on
    line([0 obj.idx.P_c + obj.idx.P_h], [0 0], 'color', 'k')
    boxplot(obj.posterior.mu_n)
    ylabel('\mu_n')
    title('Empirical Bayes');
    set(gca,'XTick',1:obj.idx.P_c + obj.idx.P_h, 'XTickLabelRotation', 60, ...
        'XTickLabel', tickLabels, 'TickLabelInterpreter', 'none');
end

%% cluster
figure
hold on
% plot posterior cluster mean and 95% marginal credible intervals
legends = cell(obj.K,1);
for k = 1:obj.K
    xOffset = ((k-1)/max(1,(obj.K-1)) - .5)/4;
    switch obj.posterior.method
        case 'VB'
            clMean = obj.posterior.m(k,:);
            clStd = sqrt(diag(obj.posterior.S(:,:,k))'/...
                (obj.posterior.tau(k)*...
                (obj.posterior.nu(k) - obj.idx.P_c + 1)));
            s = tinv(1-0.025, obj.posterior.nu(k));
            errorbar((1:obj.idx.P_c) + xOffset, clMean, s*clStd, 'd');
        case 'MH'
            clMean = obj.posterior.mean.mu(k,:);
            [~,i1] = min(abs(obj.posterior.quantile.levels - .025));
            [~,i2] = min(abs(obj.posterior.quantile.levels - .975));
            neg = clMean - obj.posterior.quantile.mu(k,:,i1);
            pos = obj.posterior.quantile.mu(k,:,i2) - clMean;
            errorbar((1:obj.idx.P_c) + xOffset, clMean, neg, pos, 'd');
    end
    legends{k} = ['cluster ' num2str(k)];
end
line([0 obj.idx.P_c+1], [0 0], 'color', 'k')
xlim([0 obj.idx.P_c+1])
ylabel('\mu_k')
set(gca,'XTick',1:obj.idx.P_c, 'XTickLabelRotation', 60, ...
    'XTickLabel', tickLabels(1:obj.idx.P_c), 'TickLabelInterpreter', 'none');
legend(legends)
title('Clusters')

%% DCM
% plot 25 samples from posterior over (noise free) BOLD response
nSmp = 25;
for n = subjects(:)'
    figure
    hold on
    % draw samples from posterior over DCM parameters
    switch obj.posterior.method
        case 'VB'
            postMean = obj.posterior.mu_n(n,:);
            postStd = chol(obj.posterior.Sigma_n(:,:,n));
            postSmp = randn(nSmp,obj.idx.P_c + obj.idx.P_h);
            postSmp = bsxfun(@plus, postSmp*postStd, postMean);
        case 'MH'
            nTrace = length(obj.trace.smp);
            nSmp = min(nSmp, nTrace);
            idx = randsample(nTrace, nSmp);
            tmp = [reshape([obj.trace.smp(idx).theta_c], obj.N, obj.idx.P_c, []), ...
                reshape([obj.trace.smp(idx).theta_h], obj.N, obj.idx.P_h, [])];
            postSmp = permute(tmp(n,:,:), [3 2 1]);
    end
    legends = {'measured'};
    plot(obj.data(n).bold(:),'k')
    % plot ground truth if available
    if ~isempty(obj.model)
        [ ~, epsilon ] = obj.gen_bold( n, obj.model.theta(n,:) );
        plot(obj.data(n).bold(:) - epsilon(:),'r')
        legends = [legends, {'ground truth'}]; %#ok<AGROW>
    end
    legends = [legends ,{'posterior samples'}]; %#ok<AGROW>
    for iSmp = 1:nSmp
        [ ~, epsilon ] = obj.gen_bold( n, postSmp(iSmp,:) );
        plot(obj.data(n).bold(:) - epsilon(:),'b')
    end
    legend(legends);
    title(['Subject ' num2str(n)]);
    ylabel('BOLD')
    xlabel('sample index')
end

end


function [engine, loglik] = enter_evidence(engine, evidence, varargin)
% ENTER_EVIDENCE Add the specified evidence to the network (frontier)
% [engine, loglik] = enter_evidence(engine, evidence, ...)
%
% evidence{i,t} = [] if if X(i,t) is hidden, and otherwise contains its observed value (scalar or column vector)
%
% The following optional arguments can be specified in the form of name/value pairs:
% [default value in brackets]
%
% maximize - if 1, does max-product (not yet supported), else sum-product [0]
% filter -   if 1, do filtering, else smoothing [0]
%
% e.g., engine = enter_evidence(engine, ev, 'maximize', 1)

maximize = 0;
filter = 0;

% parse optional params
args = varargin;
nargs = length(args);
if nargs > 0
  for i=1:2:nargs
    switch args{i},
     case 'maximize', maximize = args{i+1};
     case 'filter', filter = args{i+1};
     otherwise,
      error(['invalid argument name ' args{i}]);
    end
  end
end

assert(~maximize);

[ss T] = size(evidence);
bnet = bnet_from_engine(engine);
onodes = find(~isemptycell(evidence));
cnodes = unroll_set(bnet.cnodes(:), ss, T);
pot_type = determine_pot_type(bnet, onodes);

CPDpot = convert_dbn_CPDs_to_pots(bnet, evidence, pot_type);

[engine.fwdback, loglik, engine.fwd_frontier, engine.back_frontier] = ...
    enter_soft_evidence(engine, CPDpot, onodes, pot_type, filter);