coherencyc_unequal_length_trials.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. function [Cmn,Phimn,Smn,Smm,f,ConfC,PhiStd,Cerr] = coherencyc_unequal_length_trials( data, movingwin, params, sMarkers )
  2. % This routine computes the average multi-taper coherence for a given set of unequal length segments. It is
  3. % based on modifications to the Chronux routines. The segments are continuously structured in the
  4. % data matrix, with the segment boundaries given by markers. Below,
  5. % movingwin is used in a non-overlaping way to partition each segment into
  6. % various windows. Th coherence is evaluated for each window, and then the
  7. % window coherence estimates averaged. Further averaging is conducted by
  8. % repeating the process for each segment.
  9. %
  10. % Inputs:
  11. %
  12. % data = data( samples, channels )- here segments must be stacked
  13. % as explained in the email
  14. % movingwin = [window winstep] i.e length of moving
  15. % window and step size. Note that units here have
  16. % to be consistent with units of Fs. If Fs=1 (ie normalized)
  17. % then [window winstep]should be in samples, or else if Fs is
  18. % unnormalized then they should be in time (secs).
  19. % sMarkers = N x 2 array of segment start & stop marks. sMarkers(n, 1) = start
  20. % sample index; sMarkers(n,2) = stop sample index for the nth segment
  21. % params = see Chronux help on mtspecgramc
  22. %
  23. % Output:
  24. % Cmn magnitude of coherency - frequencies x iChPairs
  25. % Phimn phase of coherency - frequencies x iChPairs
  26. % Smn cross spectrum - frequencies x iChPairs
  27. % Smm spectrum m - frequencies x channels
  28. % f frequencies x 1
  29. % ConfC 1 x iChPairs; confidence level for Cmn at 1-p % - only for err(1)>=1
  30. % PhiStd frequency x iChPairs; error bars for phimn - only for err(1)>=1
  31. % Cerr 2 x frequency x iChPairs; Jackknife error bars for Cmn - use only for Jackknife - err(1)=2
  32. %
  33. % Here iChPairs = indices corresponding to the off-diagonal terms of the
  34. % lower half matrix. iChPairs = 1 : nChannels*(nChannels-1)/2. So,
  35. % iChPairs=1,2,3,4,...correspond to C(2,1), C(3,1), C(3,2), C(4,1), etc.
  36. % The mapping can be obtained as follows:
  37. %
  38. % C(i,j) = Cmn(:,k) where k = j + (1/2)*(i-1)*(i-2)
  39. %
  40. % The above also applies to phimn, Smn
  41. %
  42. % Note:
  43. % segment length >= NW/2 where NW = half bandwidth parameter (see dpss). So the power spectrum will
  44. % be computed only for those segments whose length > NW/2. For that reason, the routine returns the
  45. % indices for segments for which the spectra is computed. This check is
  46. % done here since pSpecgramAvg calls it.
  47. iwAvg = 1; % 0=no weighted average, 1=weighted average
  48. debug = 1; % will display intermediate calcs.
  49. if nargin < 2; error('avgCoherence:: Need data and window parameters'); end;
  50. if nargin < 3; params=[]; end;
  51. [ tapers, pad, Fs, fpass, err, trialave, params ] = getparams( params );
  52. if isempty( sMarkers ), error( 'avgCoherence:: Need Markers...' ); end
  53. % Not designed for "trialave" so set to 0
  54. params.trialave = 0;
  55. [ tapers, pad, Fs, fpass, err, trialave, params ] = getparams( params );
  56. if nargout > 7 && err(1)~=2;
  57. error('avgCoherence:: Cerr computed only for Jackknife. Correct inputs and run again');
  58. end;
  59. if nargout > 5 && err(1)==0;
  60. % Errors computed only if err(1) is nonzero. Need to change params and run again.
  61. error('avgCoherence:: When errors are desired, err(1) has to be non-zero.');
  62. end;
  63. if size(data,2)==1, error('avgCoherence:: Need more than 1 channel to compute coherence'); end
  64. % Set moving window parameters to no-overlapping
  65. if abs(movingwin(2) - movingwin(1)) >= 1e-6, disp( 'avgCoherence:: Warming: Window parameters for averaging should be non-overlapping. Set movingwin(2) = movingwin(1).' ); end
  66. wLength = round( Fs * movingwin(1) ); % number of samples in window
  67. wStep = round( movingwin(2) * Fs ); % number of samples to step through
  68. % Check whether window lengths satify segment length > NW/2
  69. if ( wLength < 2*tapers(1) ), error( 'avgCoherence:: movingwin(1) > 2*tapers(1)' ); end
  70. % Left align segment markers for easier coding
  71. sM = ones( size( sMarkers, 1 ), 2 );
  72. sM( :, 2 ) = sMarkers( :, 2 ) - sMarkers( :, 1 ) + 1;
  73. % min-max segments
  74. Nmax = max( sM(:,2) ); Nmin = min( sM(:,2) );
  75. if ( Nmin < 2*tapers(1) ), error( 'avgCoherence:: Smallest segment length > 2*tapers(1). Change taper settings' ); end
  76. % max time-sample length will be the window length.
  77. nfft = 2^( nextpow2( wLength ) + pad );
  78. [ f, findx ] = getfgrid( Fs, nfft, fpass);
  79. % Precompute all the tapers
  80. sTapers = tapers;
  81. sTapers = dpsschk( sTapers, wLength, Fs ); % compute tapers for window length
  82. nChannels = size( data, 2 );
  83. nSegments = size( sMarkers, 1 );
  84. iChPairs = ceil( nChannels*(nChannels-1)/2 );
  85. if debug
  86. disp( ['Window Length = ' num2str(wLength)] );
  87. disp( ['Window Step = ' num2str(wStep)] );
  88. disp( ' ' );
  89. end
  90. %
  91. % coherr outputs such that:
  92. % confc is has dimensions [1 size(cmn,2)] => confc = 1 x iChPairs
  93. % phistd has dimensions [f size(cmn,2)] => phistd = frequencies x iChPairs = size( cmn )
  94. % cerr has dimensions [2 size(cmn)] => cerr = 2 x frequencies x iChPairs
  95. %
  96. cerr = zeros( 2, length(f), iChPairs ); confc = zeros(1,iChPairs); phistd=zeros( length(f), iChPairs );
  97. Cerr = zeros( 2, length(f), iChPairs ); ConfC = zeros(1,iChPairs); PhiStd=zeros( length(f), iChPairs );
  98. %serr = zeros( 2, length(f), nChannels );
  99. smm = zeros( length(f), nChannels );
  100. smn = zeros( length(f), iChPairs ); cmn=smn; phimn=smn; smn = complex( smn, smn );
  101. Smm = smm; Smn = smn; Cmn = cmn; Phimn = phimn;
  102. nWins = 0;
  103. for sg = 1 : nSegments
  104. % Window lengths & steps fixed above
  105. % For the given segment, compute the positions & number of windows
  106. N = sM(sg,2);
  107. wStartPos = 1 : wStep : ( N - wLength + 1 );
  108. nWindows = length( wStartPos );
  109. if nWindows
  110. nWins = nWins + nWindows; % for averaging purposes
  111. w=zeros(nWindows,2);
  112. for n = 1 : nWindows
  113. w(n,:) = [ wStartPos(n), (wStartPos(n) + wLength - 1) ]; % nWindows x 2. just like segment end points
  114. end
  115. % Shift window limits back to original sample-stamps
  116. w(:, 1) = w(:,1) + (sMarkers( sg, 1 ) - 1);
  117. w(:, 2) = w(:,2) + (sMarkers( sg, 1 ) - 1);
  118. if debug
  119. disp( ['Segment Start/Stop = ' num2str( w(1,1) ) ' ' num2str( w(end,2) ) ] );
  120. disp( ['Min / Max Window Positions = ' num2str( min(w(:,1)) ) ' ' num2str( max(w(:,1)) ) ] );
  121. disp( ['Total Number of Windows = ' num2str(nWindows) ]);
  122. disp( ' ' );
  123. end
  124. % Pile up window segments similar to segment pileup
  125. wData = zeros( wLength, nChannels, nWindows ); %initialize to avoid fragmentation
  126. for n = 1:nWindows
  127. %wData( :, :, n ) = detrend( data( w(n,1):w(n,2), : ), 'constant' );
  128. wData( :, :, n ) = detrend( data( w(n,1):w(n,2), : ) );
  129. end
  130. % J1 = frequency x taper x nWindows
  131. % J2 = frequency x taper x nWindows x nChannels
  132. J2 = zeros( length(f), tapers(2), nWindows, nChannels ); J2 = complex( J2, J2 );
  133. for c = 1 : nChannels
  134. J1 = mtfftc( squeeze(wData( :, c, : )), sTapers, nfft, Fs ); % FFT for the tapered data
  135. J2( :, :, :, c ) = J1(findx,:,:);
  136. end
  137. % J2 = frequency x taper x nWindows x nChannels
  138. % Inner mean = Average over tapers => frequency x nWindows x nChannels
  139. % Outer mean = Average over windows => frequency x nChannels
  140. % smm = diagonal terms, ie power spectrum
  141. %
  142. dim1 = [length(f), nWindows, nChannels];
  143. dim2 = [length(f), nChannels];
  144. % s = frequency x nChannels
  145. smm = reshape( squeeze( mean( reshape( squeeze( mean( conj(J2).*J2, 2 ) ), dim1), 2 ) ), dim2 );
  146. %
  147. % Compute only the lower off-diagonal terms
  148. % smn = Cross Spectrum terms = complex
  149. % cmn = abs( coherence ); phimn = phase( coherence )
  150. %
  151. %
  152. % coherr outputs such that:
  153. % confc is has dimensions [1 size(cmn,2)] => confc = 1 x iChPairs
  154. % phistd has dimensions [f size(cmn,2)] => phistd = frequencies x iChPairs = size( cmn )
  155. % cerr has dimensions [2 size(cmn)] => cerr = 2 x frequencies x iChPairs
  156. %
  157. cerr = zeros( 2, length(f), iChPairs ); confc = zeros(1,iChPairs); phistd=zeros( length(f), iChPairs );
  158. dim = [length(f), tapers(2), nWindows];
  159. id = 1;
  160. for m=2:nChannels
  161. Jm = reshape( squeeze( J2(:,:,:,m) ), dim ); % frequency x taper x nWindows
  162. for n=1:m-1 % since we want off-diagonal terms only
  163. Jn = reshape( squeeze( J2(:,:,:,n) ), dim ); % frequency x taper x nWindows
  164. %
  165. % Average the Cross-Spectrum, Smn, over the windows
  166. % smn = complex
  167. % First average over tapers, then over windows
  168. %
  169. smn(:,id) = squeeze( mean( squeeze( mean( conj(Jm).*Jn, 2 ) ), 2 ) ); % frequency x iChPairs
  170. %
  171. % Coh = Coherence = complex = size( smn ) = frequency x iChPairs
  172. %
  173. Coh = smn(:,id) ./ sqrt( smm(:,m) .* smm(:,n) );
  174. cmn(:,id) = abs(Coh); % frequencies x iChPairs
  175. phimn(:,id) = angle(Coh); % frequencies x iChPairs
  176. % Since we've averaged over segments, set trialave = 1
  177. %
  178. % coherr outputs:
  179. % confc is has dimensions [1 size(Cmn(:,1),2)] => confC = 1 x iChPairs
  180. % phierr has dimensions [f size(Cmn(:,1),2)] => phistd = frequencies x iChPairs = size( Cmn )
  181. % cerr has dimensions [2 size(Cmn(:,1))] => Cerr = 2 x frequencies x iChPairs
  182. %
  183. % Now treat the various "windowed data" as "trials"
  184. [ cconfc, cphistd, ccerr ] = coherr( cmn(:,id), Jm, Jn, err, 1 );
  185. cerr(:,:,id ) = ccerr;
  186. confc(id) = cconfc;
  187. %size(cphistd), size(phistd)
  188. phistd(:,id) = cphistd; % frequencies x iChPairs
  189. id = id + 1;
  190. end
  191. end
  192. if iwAvg
  193. % Segment Weighted error estimates.
  194. Smm = Smm + nWindows*smm;
  195. Smn = Smn + nWindows*smn;
  196. Cmn = Cmn + nWindows*cmn;
  197. Phimn = Phimn + nWindows*phimn;
  198. PhiStd = PhiStd + nWindows*phistd;
  199. ConfC = ConfC + nWindows*confc;
  200. Cerr = Cerr + nWindows*cerr;
  201. else
  202. Smm = Smm + smm;
  203. Smn = Smn + smn;
  204. Cmn = Cmn + cmn;
  205. Phimn = Phimn + phimn;
  206. PhiStd = PhiStd + phistd;
  207. ConfC = ConfC + confc;
  208. Cerr = Cerr + cerr;
  209. end
  210. else
  211. if debug, disp(['avgCoherence:: Zero windows for segment: ' num2str(sg) ]); end
  212. end
  213. end
  214. % Segment Weighted error estimates.
  215. % Only over those that had non-zero windows
  216. if nWins && iwAvg
  217. Smn=Smn/nWins; Smm=Smm/nWins; Cmn=Cmn/nWins; Phimn=Phimn/nWins; PhiStd=PhiStd/nWins; ConfC=ConfC/nWins; Cerr=Cerr/nWins;
  218. end
  219. if ~nWins
  220. if debug, disp(['avgCoherence:: No segment long enough with movingwin parameters found. Reduce movingwin.' ]); end
  221. end