% PHASECOHER - Implements inter-trial amp/coherence using Gaussian wavelets.
%                Returns same data length as input frames.
%                Plots results when nargin>6. Outputs have flat ends 
%                at data indices [1:halfwin] and [frames-halfwin:frames].
% Usage:
%     >> [amps,cohers ] = phasecoher(data,frames,srate,freq,cycles); 
%     >> [amps,cohers,cohsig,ampsig,allamps,allphs] ...
%                 = phasecoher(data,frames,...
%                                srate,freq,cycles,...
%                                  alpha,times,titl,...
%                                    warpframes, events);
% Inputs:
%   data   = input data, (1,frames*trials) or NB: (frames,trials) 
%   frames = frames per trial
%   srate  = sampling rate (in Hz)
%   freq   = frequency to work on (in Hz)
%   cycles = cycles in Gaussian wavelet window (float) {3}
%   alpha  = (0 0.1] significance probability threshold. Requires 
%            >=3 output arguments. alpha=0 -> no signif {default: 0}.
%   times  = vector of latencies (in ms) for plotting {default: no plot}
%   titl   = [string] plot title {default none}
%   warpframes = frame numbers of warped events (below)
%   events = matrix of events in each trial, size (nevents, trials)
%            as frame numbers.
% Outputs:
%   amps    = mean amplitude at each time point
%   cohers  = phase coherence at each time point [0,1]
%   cohsig  = coherence significance threshold (bootstrap, alpha level)
%   ampsig  = amplitude significance thresholds [lo high] (bootstrap, alpha level)
%   allamps = amplitudes at each trial and time point (frames,trials)
%   allphs  = phase (deg) at each trial and time point (frames,trials)
%
% Author: Scott Makeig, SCCN/INC/UCSD, La Jolla, 5-5-98 
%
% See also: ERPIMAGE

% Copyright (C) 5-5-98 Scott Makeig, SCCN/INC/UCSD, scott@sccn.ucsd.edu
%
% 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.

% 5-7-98 added frames, made input data format one-row -sm
% 5-8-98 added MIN_AMP, times, PLOT_IT -sm
% 10-27-98 added cohsig, alpha -sm
% 07-24-99 added allamps -sm
% 02-29-00 added ampsig -sm
% 12-05-00 changed complex ABS to sqrt( .^2+ .^2) to avoid possible i ambiguity -sm & tpj
% 12-31-00 added ...20...40... frames fprinting -sm
% 08-17-01 added allphs option -sm
% 08-18-01 debugged cohsig plotting line (302) -sm
% 01-25-02 reformated help & license, added links -ad 

function [amps,cohers,cohsig,ampsig,allamps,allphs] = ...
      phasecoher(data, frames, srate, freq, cycles, alpha, times, titl, timeStretchRef, timeStretchMarks)

MIN_AMP = 10^-6;
DEFAULT_ALPHA = nan;% no bootstrap computed
COHSIG_REPS = 500;  % default number of bootstrap surrogate coherence values 
                    % to compute 
TITLEFONT= 18;
TEXTFONT = 16;
TICKFONT = 14;
DEFAULT_CYCLES=3;
ampsig = []; % initialize for null output
cohsig = [];

if nargin<4
  help phasecoher
  return
end
if nargin<5
  cycles = DEFAULT_CYCLES;
end

if nargin < 8 || isempty(titl)
   titl = '';
end

if nargin < 7 || isempty(titl) || isempty(times)
  PLOT_IT = 0;
elseif length(times) ~= frames
  fprintf('phasecoher(): times vector length must be same as frames.\n')
  return
else
  PLOT_IT = 1;
end
 
if nargin < 6
    alpha = nan; % no alpha given
end
if nargout > 2 && isnan(alpha) % if still want cohsig
  alpha = DEFAULT_ALPHA; 
elseif nargout > 2
  if alpha < 0 || alpha > 0.1
    help phasecoher
    fprintf('phasecoher(): alpha out of bounds.\n');
    return
  end
  if alpha==0
    alpha = nan; % no bootstrap
  end
  if alpha*COHSIG_REPS < 5
    COHSIG_REPS = ceil(5/alpha);
    fprintf('   Computing %d bootstrap replications.\n',COHSIG_REPS);
  end
elseif ~isnan(alpha)
  alpha = nan; % no cohsig calculation
end

if length(frames)>1
   help phasecoher
   fprintf('phasecoher(): frames should be a single integer.\n');
   return
end
if frames == 0,
   frames = size(data,1);
end
trials = size(data,1)*size(data,2)/frames;
if floor(trials) ~= trials
   fprintf('phasecoher(): data length not divisible by %d frames.\n',frames);
   return
end

fprintf('phasecoher(): Analyzing %d data trials of %d frames ',trials,frames);
if trials < 10
  fprintf(...
  'Low number of trials (%d) may not give accurate coherences.\n',trials)
end

if size(freq,1)*size(freq,2)~=1
   fprintf('\nphasecoher(): only one frequency can be analyzed at a time.\n');
   help phasecoher
   return
end

if size(data,1) == 1
  data = reshape(data,frames,trials); % trials are columns
end
window = gauss(ceil(srate/freq*cycles),2); % gauss(std,+/-stds)
winlength = length(window);
halfwin = floor(winlength/2);
fprintf('\n  with a moving %d-frame analysis window...',winlength);

if frames < winlength
  fprintf(...
  '\nProblem: Epoch length (%d frames) too short for analysis with %g cycles!\n',...
                frames,                                 cycles);
  return
end

%
% Extend the data to minimize edge effects
%
data = [data([halfwin+1:-1:1],:); ...
                data; ...
        data([frames:-1:frames+1-(winlength-halfwin)],:)];
%
% Remove epoch means
%
%data = data - ones(frames+winlength+1,1)* mean(data); % remove column means

angleinc = cycles*2*pi/winlength;
cosx = cos(-cycles*pi:angleinc:cycles*pi); % sinusoids
cosx = cosx(1:winlength);
sinx = sin(-cycles*pi:angleinc:cycles*pi);
sinx = sinx(1:winlength);
coswin = window.*cosx;               % window sinusoids
sinwin = window.*sinx;
coswin = coswin/(coswin*cosx');      % normalize windowed sinusoids
sinwin = sinwin/(sinwin*sinx');

% figure;plot(coswin,'r');hold on; plot(sinwin,'b');
% iang = -cycles*pi:angleinc:cycles*pi;
% iang = iang(1:winlength);
% figure;plot(iang,[sinwin;coswin]);
amps    = zeros(1,frames);
if nargout > 3
  allamps = zeros(frames,trials);
end
if nargout > 5
  allphs = zeros(frames,trials);
end
cohers  = zeros(1,frames);
ix = 0:winlength-1;
% nsums = zeros(1,frames);  % never called

C = [];

for f = 1:frames %%%%%%%%%%%%%%% frames %%%%%%%%%%%%%%%%%%%%
  epoch = data(ix+f,:);
  epoch = epoch - ones(winlength,1)*mean(epoch); % remove epoch means
  if rem(f,50)== 0
    fprintf(' %d',f)
  end
  for t = 1:trials  %%%%%%%%%%%%%%% trials %%%%%%%%%%%%%%%%%%%
    realpart = coswin*epoch(:,t);
    imagpart = sinwin*epoch(:,t);
    C(f,t) = complex(realpart, imagpart);
  end
end

allamps = sqrt(C.*conj(C)); %compute all amplitudes for all frames, all trials
allphs = angle(C); %get the phase

if exist('timeStretchRef') && exist('timeStretchMarks') && ...
    length(timeStretchRef) > 0 & length(timeStretchMarks) > 0 %Added -Jean
  for t=1:trials
    M = timewarp(timeStretchMarks(:,t)', timeStretchRef');
    allamps(:,t) = M*allamps(:,t);
    allphs(:,t) = angtimewarp(timeStretchMarks(:,t)', timeStretchRef', ...
                              allphs(:,t));
  end
end

[amps, cohers, nsums]=getAmpCoh(allamps, allphs, MIN_AMP);

% Old routine, for archeological purposes
% $$$ realcoh = zeros(1,frames);
% $$$ imagcoh = zeros(1,frames);
% $$$ for f = 1:frames %%%%%%%%%%%%%%% frames %%%%%%%%%%%%%%%%%%%%
% $$$   epoch = data(ix+f,:);
% $$$   %epoch = epoch - ones(winlength,1)*mean(epoch); % remove epoch means
% $$$   if rem(f,50)== 0
% $$$     fprintf(' %d',f)
% $$$   end
% $$$   for t = 1:trials  %%%%%%%%%%%%%%% trials %%%%%%%%%%%%%%%%%%%
% $$$     realpart = coswin*epoch(:,t);
% $$$     imagpart = sinwin*epoch(:,t);
% $$$     amp = sqrt(realpart.*realpart+imagpart.*imagpart);
% $$$     if amp >= MIN_AMP
% $$$       amps(f) = amps(f) + amp; % sum of amps
% $$$       realcoh(f) = realcoh(f) + realpart/amp;
% $$$       imagcoh(f) = imagcoh(f) + imagpart/amp;
% $$$       nsums(f) = nsums(f)+1;
% $$$     end
% $$$     if nargout > 3 
% $$$       if amp < MIN_AMP
% $$$         amp = MIN_AMP;
% $$$       end
% $$$       allamps(f,t) = amp;
% $$$     end
% $$$     if nargout > 5
% $$$       allphs(f,t) = 180/pi*angle(realpart+i*imagpart);
% $$$     end
% $$$   end
% $$$   if nsums(f)>0
% $$$     amps(f) = amps(f)/nsums(f);
% $$$     realcoh(f) = realcoh(f)/nsums(f);
% $$$     imagcoh(f) = imagcoh(f)/nsums(f);
% $$$   else
% $$$     amps(f) = 0;
% $$$     realcoh(f) = 0;
% $$$     imagcoh(f) = 0;
% $$$   end
% $$$ end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% $$$ cohers = sqrt(realcoh.^2+imagcoh.^2);

fprintf('\n');




cohsig = [];

if ~isnan(alpha)  %%%%%%%%%%%%%% Compute cohsig/ampsig %%%%%%%%%%%%%%
 % ix = 0:winlength-1;  % never called
 bootcoher = zeros(1,COHSIG_REPS);
 bootamp   = zeros(1,COHSIG_REPS);
 
 bootallamps = zeros(COHSIG_REPS, trials); %Added -Jean
 bootallphs = zeros(COHSIG_REPS, trials); %Added -Jean
 
 fprintf('Computing %d bootstrap coherence values... ',COHSIG_REPS); 
 for f = 1:COHSIG_REPS %%%%%%%%%%%%%%% Bootstrap replications %%%%%%%%%%%
  if rem(f,50) == 0
    fprintf('%d ',f);
  end
  randoff = floor(rand(1,trials)*(frames-winlength))+1; % random offsets
  
  %Create randomized time-stretched allamps and allphs arrays (see above)
  for t = 1:trials
    bootallamps(f, t) = allamps(randoff(t), t);
    bootallphs(f, t) = allphs(randoff(t), t);
  end
 end

 [bootamp, bootcoher]=getAmpCoh(bootallamps, bootallphs, MIN_AMP);
 
 fprintf('\n');

 bootcoher = sort(bootcoher); % sort low to high
 cohsig = bootcoher(round(COHSIG_REPS*(1.0-alpha)));

 bootamp = sort(bootamp); % sort low to high
 ampsig = [bootamp(round(COHSIG_REPS*(alpha))) ...
           bootamp(round(COHSIG_REPS*(1.0-alpha)))];
% keyboard

end %%%%%%%%%%%%%%%%%%%%%%%%%%%% end cohsig %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

for f=1:halfwin                 % pad amps, cohers to front of input data
  amps(f) = amps(halfwin+1);
  cohers(f) = cohers(halfwin+1);
end
for f=frames:-1:frames-halfwin  % pad amps, cohers to end of input data
  amps(f) = amps(frames-halfwin);
  cohers(f) = cohers(frames-halfwin);
end

if PLOT_IT %%%%%%%%%%%%%% make two-panel plot of results %%%%%%%%

  subplot(2,1,1);plot(times,amps');
  title(titl,'fontsize',TITLEFONT,'fontweight','bold');
     ylabel(['Amplitude (' num2str(freq) ' Hz)'],...
           'fontsize',TEXTFONT,'fontweight','bold');
     ax = axis; 
     hold on; plot([0 0],[0 1],'k');   % vertical line at time 0
     axis([ax(1) ax(2) ax(3) ax(4)*1.25]);
     set(gca,'FontSize',TICKFONT);
     set(gca,'FontWeight','bold');

  subplot(2,1,2);plot(times,cohers','r');
     ylabel(['Intertrial Coherence (' num2str(freq) ' Hz)'],...
           'fontsize',TEXTFONT,'fontweight','bold');
     xlabel('Time (ms)','fontsize',TEXTFONT,'fontweight','bold');
     hold on
  winstframe = floor(frames/7);
  winframes = [winstframe:winstframe+winlength-1];
  wintimes = times(winframes);
  ax = axis; 
  plot(wintimes,0.8+window*0.1,'k');
  plot(wintimes,0.8-window*0.1,'k');
  % ax2 = axis;     % never called
  hold on; plot([0 0],[0 1000],'k');   % vertical line at time 0
  axis([ax(1) ax(2) 0 1]);
     set(gca,'fontSize',TICKFONT);
     set(gca,'FontWeight','bold');
     alpha
  if ~isnan(alpha)                      % plot coher significance
    plot([wintimes(1) wintimes(end)],[cohsig cohsig],'r'); 
       % was [times(1) times(winframes)] !??
  end                                  
end



function [amps, cohers, nsums] = getAmpCoh(allamps, allphs, MIN_AMP)
  minampfilter = allamps >= MIN_AMP;
  nsums = sum(minampfilter,2);

  amps(find(nsums == 0)) = 0; %zero the amplitude if no trial shows
                              %significant power at that frame
  cohers(find(nsums == 0)) = 0; %zero the coherence too if no trial shows
                                %significant power at that frame
                                %Now average out amplitudes over trials
  % allminamps is never used. TF 04/02/2007                              
  %allminamps = allamps;
  % nargout is never greater than 3. Bug 262. TF 04/02/2007
  %if nargout > 3
  %  allminamps(~minampfilter) = MIN_AMP;
  %end
  allzeramps = allamps .* minampfilter;
  allzeramps = allzeramps(find(nsums ~= 0),:);
  amps(find(nsums ~= 0)) = sum(allzeramps,2) ./ nsums(find(nsums ~= 0));

  %Convert angles to complex for summing
  allzerphs = complex(cos(allphs), sin(allphs)) .* minampfilter;
  allzerphs = allzerphs(find(nsums ~= 0), :);
  cohers(find(nsums ~= 0)) = sum(allzerphs,2) ./ nsums(find(nsums ~= 0));
  cohers = sqrt(cohers .* conj(cohers));

  function outvec = gauss(frames,sds)

outvec = [];
if nargin < 2
  help gauss
  return
end
if sds <=0 || frames < 1
  help gauss
  return
end

incr = 2*sds/(frames-1);
outvec = exp(-(-sds:incr:sds).^2);
