% EEG_PV   - Compute EEG.data 'percent variance ' (pv) of the whole EEG data versus the projections
%              of specified components. 
%              Can omit specified components and channels from the computation. Can draw a plot 
%              of the scalp distribution of pv, or progressively compute the pv for comps
%              1:k, where k = 1 -> the total number of components.  Note: pv's of spatially
%              non-orthogonal independent components may not add to 100%, and individual component 
%              pv could be < 0%.
% Usage:
%              >> [pv] = eeg_pv(EEG,comps);
%              >> [pv,pvs,vars] = eeg_pv(EEG,comps,artcomps,omitchans,fraction,'plot');
% Inputs:
%    EEG       - EEGLAB dataset. Must have icaweights, icasphere, icawinv, icaact.
%    comps     - vector of component indices to sum {default|[] -> progressive mode}
%                In progressive mode, comps is first [1], then [1 2], etc. up to
%                [1:size(EEG.icaweights,2)] (all components); here, the plot shows pv.
%    artcomps  - vector of artifact component indices to remove from data before
%                computing pv {default|[]: none}
%    omitchans - channels to omit from the computation (e.g. off-head, etc) {default|[]: none}
%    fraction  - (0<real<=1) fraction of the data to randomly select {default|[]: 1=all}
%    'plot'    - Plot scalp map of channel pvs. {default: Plot only if no output arguments}
%
% Outputs:
%    pv      - (real) percent total variance accounted for by the summed back-projection of
%                the requested components. If comps is [], a vector of pvs for the sum of 
%                components 1:k (k=1:ncomps).
%    pvs     - (real vector) percent variance accounted for by the summed back-projection of
%                the requested components to each data channel. If comps is [], a matrix of
%                pvs (as for pv above).
%    vars      - variances of the requested channels
%
% Fields:  
%    Assumes existence of the following EEG fields: EEG.data, EEG.pnts, EEG.nbchan, EEG.trials,
%          EEG.icaact, EEG.icaweights, EEG.icasphere, EEG.icawinv, and for plot, EEG.chanlocs
%
% See also:  EEG_PVAF
%
% Author: from EEG_PVAF, Scott Makeig, SCCN/INC/UCSD, 02/04/05

% Copyright (C) Scott Makeig, SCCN/INC/UCSD, 02/04/05
%
% This file is part of EEGLAB, see http://www.eeglab.org
% for the documentation and details.
%
% Redistribution and use in source and binary forms, with or without
% modification, are permitted provided that the following conditions are met:
%
% 1. Redistributions of source code must retain the above copyright notice,
% this list of conditions and the following disclaimer.
%
% 2. Redistributions in binary form must reproduce the above copyright notice,
% this list of conditions and the following disclaimer in the documentation
% and/or other materials provided with the distribution.
%
% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
% THE POSSIBILITY OF SUCH DAMAGE.

function [pv,pvs,pvall] = eeg_pv(EEG,comps,artcomps,omitchans,fraction,plotflag)

if nargin < 1 || nargin > 6
   help eeg_pv
   return
end
numcomps = size(EEG.icaact,1);
plotit = 0;
if nargin>5 || nargout < 1
   plotit = 1;
end
if nargin<5 || isempty(fraction)
  fraction = 1;
end
if fraction>1
  fprintf('eeg_pv(): considering all the data.\n');
  fraction=1;
end
if round(fraction*EEG.pnts*EEG.trials)<1
   error('fraction of data specified too small.')
   return
end
if nargin<4 || isempty(omitchans)
  omitchans = [];
end
if nargin<3|isempty(artcomps)
  artcomps=[];
end

numchans = EEG.nbchan;
chans = 1:numchans;
if ~isempty(omitchans)
 if max(omitchans)>numchans
  help eeg_pv
  error('at least one channel to omit > number of channels in data');
 end
 if min(omitchans)<1
  help eeg_pv
  error('channel numbers to omit must be > 0');
 end
 chans(omitchans) = [];
end

progressive = 0; % by default, progressive mode is off
if nargin < 2 || isempty(comps)|comps==0
  comps = [];
  progressive = 1;  % turn progressive mode on
end

if isempty(EEG.icaweights)
   help eeg_pv
   return
end
if isempty(EEG.icasphere)
   help eeg_pv
   return
end

if isempty(EEG.icawinv)
     EEG.icawinv = pinv(EEG.icaweights*EEG.icasphere);
end
if isempty(EEG.icaact)
    help eeg_pv
    fprintf('EEG.icaact not present.\n');
    % EEG.icaact = EEG.icaweights*EEG.icasphere*EEG.data; % remake it like this
end
if max(comps) > size(EEG.icawinv,1)
    help eeg_pv
   fprintf('Only %d components in this dataset. Cannot project component %d.\n',numcomps,max(comps));
   error('bad comps input');
end
if ~isempty(artcomps) && max(artcomps) > numcomps
    help eeg_pv
   fprintf('Only %d components in this dataset. Cannot project artcomp %d.\n',numcomps,max(artcomps));
   error('bad artcomps input')
end

npts = EEG.trials*EEG.pnts;
allcomps = 1:numcomps;
if progressive
   fprintf('Considering components up to: ');
   cum_pv  = zeros(1,numcomps);
   cum_pvs = zeros(numcomps,numchans);
end

for comp = 1:numcomps %%%%%%%%%%%%%%% progressive mode %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
if progressive
   comps = allcomps(1:comp); % summing components 1 to current comp
   fprintf('%d ',comp)
end

if ~isempty(artcomps)
   [a b c] = intersect_bc(artcomps,comps);
   if ~isempty(a)
      if ~progressive
        if length(a)>1
          fprintf('eeg_pv(): not back-projecting %d comps already in the artcomps.\n',length(c));
        else
          fprintf('eeg_pv(): not back-projecting comp %d already in the artcomps.\n',comps(c));
        end
      end
      comps(c) = [];
   end
end
if ~isempty(artcomps) && min([comps artcomps]) < 1
   error('comps and artcomps must contain component indices');
end

%
%%%%%%%%%%%%%%%%%%%%%%%% compute variance accounted for by specified components %%%%%%%%%%%%%
%
if ~progressive || comp == 1 % pare out omitchans and artcomps from EEG.data
  if ~isempty(artcomps)
    EEG.data = EEG.data(chans,:) - EEG.icawinv(chans,artcomps)*EEG.icaact(artcomps,:);
  else
    EEG.data = EEG.data(chans,:); 
  end

  nsel = round(fraction*npts);
  varpts = randperm(npts);
  varwts = ones(size(varpts));
  if nsel<npts
    varwts(varpts(nsel+1:npts)) = 0;
  end
  pvall = var(EEG.data(:,:)',varwts);
end

chans
comps
size(EEG.icawinv(chans,comps))
size(EEG.icaact(comps,:)')
pvcomp =  var((EEG.icawinv(chans,comps)*EEG.icaact(comps,:))', varwts);

%
%%%%%%%%%%%%%%%%%%%%%%%% compute percent variance %%%%%%%%%%%%%%%
%
pvs =  pvcomp ./ pvall;
pvs = 100*pvs;
pv = sum(pvcomp) ./ sum(pvall);
pv = 100*pv;

if ~progressive
  break
else
  cum_pv(comp) = pv;
  cum_pvs(comp,:) = pvs;
end

end %%%%%%%%%%%%%% end progressive forloop %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

if progressive % output accumulated results
  fprintf('\n');
  pv = cum_pv;
  pvs = cum_pvs;
  if plotit
     plot(1:numcomps,pv);
     xl = xlabel('Components Included (1:n)');
     yl = ylabel('Percent Variance Accounted For (pv)');
     set(xl,'fontsize',15);
     set(yl,'fontsize',15);
     set(gca,'fontsize',14);
  end
elseif plotit 
%
%%%%%%%%%%%%%%%%%%%%%%%% plot the scalp distribution of pv %%%%%%%%%%%%%
%
 if isfield(EEG,'chanlocs')
   chanlocs = EEG.chanlocs;
   if ~isempty(omitchans)
     chanlocs(omitchans) = [];
   end
   topoplot(pvs',chanlocs);  % plot pv here

   if length(comps)>5        % add text legend
     if length(artcomps)>3
        tlstr=sprintf('Pvaf by %d comps in data minus %d comps',length(comps),length(artcomps));
     elseif isempty(artcomps)
        tlstr=sprintf('Pvaf by %d comps in data',length(comps));
     elseif length(artcomps)==1 % < 4 artcomps, list them
        tlstr=sprintf('Pvaf by %d comps in data (less comp ',length(comps));
        tlstr = [tlstr sprintf('%d ',artcomps) ')'];
     else
        tlstr=sprintf('Pvaf by %d comps in data (less comps ',length(comps));
        tlstr = [tlstr sprintf('%d ',artcomps) ')'];
     end
   else %  < 6 comps, list them
     if length(comps)>1
        tlstr=sprintf('Pvaf by comps ');
     else
        tlstr=sprintf('Pvaf by comp ');
     end
     if length(artcomps)>3
        tlstr = ...
[tlstr sprintf('%d ',comps) sprintf('in data minus %d comps',length(comps),length(artcomps))];
     else
        if isempty(artcomps)
           tlstr = [tlstr sprintf('%d ',comps) 'in data'];
        elseif length(artcomps)==1
           tlstr = [tlstr sprintf('%d ',comps) 'in data (less comp '];
           tlstr = [tlstr int2str(artcomps) ')'];
        else
           tlstr = [tlstr sprintf('%d ',comps) 'in data (less comps '];
           tlstr = [tlstr sprintf('%d ',artcomps) ')'];
        end
     end
   end
   tl=title(tlstr);
   if max(pvs)>100, 
      maxc=max(pvs)
   else 
      maxc=100; 
   end

   pvstr=sprintf('Total pv: %3.1f%%',pv);
   tx=text(-0.9,-0.6,pvstr);

   caxis([-100 100]);
   cb=cbar('vert',33:64,[0 100]); % color bar showing >0 (green->red) only
 else
    fprintf('EEG.chanlocs not found - not plotting scalp pv\n');
 end
end % plotit


