Skip to content

Commit c14e05c

Browse files
author
caccavanoap
committed
update to phase-amplitude coupling. time analysis should now work. exports aveStats. UI reworked to work better with low resolution displays.
1 parent 148271e commit c14e05c

File tree

8 files changed

+165
-84
lines changed

8 files changed

+165
-84
lines changed

analyzeLFPBatch.m

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ function analyzeLFPBatch(param, dataFolder, saveFolder, expEvFolder, expDataFold
6060
% param.xFreqBin = Frequency bin size for n x n PAC analysis (Default = 5 Hz)
6161
% param.xFreqLow = cell: low frequency band for x-freq (Theta, Alpha, Beta, SW)
6262
% param.morlWidth = width/number of cycles of the morlet wavelet filter, default = 7
63+
% param.nShuffle = # shuffles to calculate Z-value for total PAC - does not due for nxn or time PAC, very computationally expensive. (default = 200)
6364
% param.winLength = time binning for phase-amplitude analysis (s). Dictates min low freq (=1/winLength), so default = 0.5s results in min freq. of 2Hz
6465
% param.winOverlap = Amount to overlap time bins (default = 0.2s)
6566
% param.importStimOption = option to import stim file from pClamp (default = 0)
@@ -140,6 +141,7 @@ function analyzeLFPBatch(param, dataFolder, saveFolder, expEvFolder, expDataFold
140141
if ~isfield(param,'xFreqBin'); param.xFreqBin = 5; end % [Hz]
141142
if ~isfield(param,'xFreqLow'); param.xFreqLow = 'Theta'; end
142143
if ~isfield(param,'morlWidth'); param.morlWidth = 7; end
144+
if ~isfield(param,'nShuffle'); param.nShuffle = 200; end
143145
if ~isfield(param,'winLength'); param.winLength = 0.5; end
144146
if ~isfield(param,'winOverlap'); param.winOverlap = 0.2; end
145147
if ~isfield(param,'importStimOption'); param.importStimOption = 0; end

analyzeLFPFile.m

Lines changed: 57 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
% param.xFreqOption = boolean flag to perform cross-frequency analysis
6868
% param.xFreqBin = Frequency bin size for n x n PAC analysis (Default = 5 Hz)
6969
% param.xFreqLow = cell: low frequency band for x-freq (Theta, Alpha, Beta, SW)
70+
% param.nShuffle = # shuffles to calculate Z-value for total PAC - does not due for nxn or time PAC, very computationally expensive. (default = 200)
7071
% param.morlWidth = width/number of cycles of the morlet wavelet filter, default = 7
7172
% param.winLength = time binning for phase-amplitude analysis (s). Dictates min low freq (=1/winLength), so default = 0.5s results in min freq. of 2Hz
7273
% param.winOverlap = Amount to overlap time bins (default = 0.2s)
@@ -157,6 +158,7 @@
157158
if ~isfield(param,'xFreqBin'); param.xFreqBin = 5; end % [Hz]
158159
if ~isfield(param,'xFreqLow'); param.xFreqLow = 'Theta'; end
159160
if ~isfield(param,'morlWidth'); param.morlWidth = 7; end
161+
if ~isfield(param,'nShuffle'); param.nShuffle = 200; end
160162
if ~isfield(param,'winLength'); param.winLength = 0.5; end % [s]
161163
if ~isfield(param,'winOverlap'); param.winOverlap = 0.2; end % [s]
162164
if ~isfield(param,'importStimOption'); param.importStimOption = 0; end
@@ -853,18 +855,17 @@
853855

854856

855857
%% Cross-Frequency Phase-Amplitude Coupling (PAC)
858+
% Adapted from Canolty et al 2006 and Onslow et al 2011
856859
if param.xFreqOption
857-
% Below is an adaptation/simplification of code written by Author: Angela Onslow, May 2010
858-
fprintf(['calculating phase-amplitude coupling for file ' dataFileName '... ']);
859-
860-
%% Total PAC Analysis for n x n matrix:
861860
% Assign a temp LFP tSeries (it will be trimmed later)
862861
tSeries = data.LFP.tSeries;
863862
nSample = length(data.LFP.tSeries);
864-
nShuffle = 200; % Add to UI?
865863

864+
%% Total PAC Analysis for n x n matrix:
866865
if isfield(param, 'spectLim1') && isfield(param, 'spectLim2')
867866

867+
fprintf(['calculating n x n phase-amplitude coupling (file ' dataFileName ')... ']);
868+
868869
% Initialize data structures:
869870
if ~isfield(data.LFP,'xFreq'); data.LFP.xFreq = struct; end
870871

@@ -876,29 +877,32 @@
876877
nFreq = length(data.LFP.xFreq.morlFreq);
877878

878879
% Initialize phase, amplitude, and PAC arrays
879-
data.LFP.xFreq.modPhase = zeros(nSample, nFreq);
880-
data.LFP.xFreq.pacAmp = zeros(nSample, nFreq);
881-
data.LFP.xFreq.pacMI = zeros(nFreq, nFreq);
880+
data.LFP.xFreq.phsPAC = zeros(nSample, nFreq);
881+
data.LFP.xFreq.ampPAC = zeros(nSample, nFreq);
882+
data.LFP.xFreq.pacMI = zeros(nFreq, nFreq);
882883

883884
% Determine phase and amplitude via Morlet wavelet:
884885
for i = 1 : nFreq
885-
data.LFP.xFreq.modPhase(:, i) = morletPhase(data.LFP.xFreq.morlFreq(i), tSeries, param.Fs, param.morlWidth);
886-
data.LFP.xFreq.pacAmp(:, i) = morletAmp(data.LFP.xFreq.morlFreq(i), tSeries, param.Fs, param.morlWidth);
886+
data.LFP.xFreq.phsPAC(:,i) = morletPhase(data.LFP.xFreq.morlFreq(i), tSeries, param.Fs, param.morlWidth);
887+
data.LFP.xFreq.ampPAC(:,i) = morletAmp(data.LFP.xFreq.morlFreq(i), tSeries, param.Fs, param.morlWidth);
887888
end
888889

889890
% Calculate PAC modulation index (MI):
890-
for i = 1 : nFreq
891-
for j = 1 : nFreq
892-
data.LFP.xFreq = calcPACMI(data.LFP.xFreq.modPhase(:,j), data.LFP.xFreq.pacAmp(:,i), nSample, param.Fs, nShuffle);
893-
end
894-
end
891+
data.LFP.xFreq = calcPACMI(data.LFP.xFreq, param.Fs, 0); % nShuffle > 1 gets very computationally expensive!
892+
893+
% Order structure:
894+
data.LFP.xFreq = orderStruct(data.LFP.xFreq);
895+
896+
fprintf('done\n');
895897
end
896898

897899
%% Total PAC Analysis for each higher frequency band:
898900
% Calculate modulating phase (lower frequency) via Morlet wavelet:
899901
if isfield(data, param.xFreqLow)
902+
fprintf(['calculating Z-corrected phase-amplitude coupling for frequency bands of interest (file ' dataFileName ')... ']);
903+
900904
morlFreqP = data.(param.xFreqLow).lim1 + floor((data.(param.xFreqLow).lim2 - data.(param.xFreqLow).lim1)/2);
901-
modPhase = morletPhase(morlFreqP, tSeries, param.Fs, param.morlWidth);
905+
phsPAC = morletPhase(morlFreqP, tSeries, param.Fs, param.morlWidth);
902906

903907
% Determine available higher phase-modulated frequency(ies) available:
904908
nHi = 0;
@@ -920,63 +924,71 @@
920924
end
921925

922926
if nHi > 0
923-
924-
% Initialize higher frequency arrays:
925-
morlFreqA = zeros(1, nHi);
926-
pacAmp = zeros(nSample, nHi);
927-
928927
for i = 1:nHi
929-
930928
if ~isfield(data.(xFreqHi{i}),'xFreq'); data.(xFreqHi{i}).xFreq = struct; end
931929

932930
% Calculate amplitude of higher frequency(ies) via Morlet wavelet:
933-
morlFreqA(i) = data.(xFreqHi{i}).lim1 + floor((data.(xFreqHi{i}).lim2 - data.(xFreqHi{i}).lim1)/2);
934-
pacAmp(:,i) = morletAmp(morlFreqA(i), tSeries, param.Fs, param.morlWidth);
935-
936-
% Calculate total PAC measure:
937-
data.(xFreqHi{i}).xFreq = calcPACMI(modPhase, pacAmp(:,i), nSample, Fs, nShuffle);
931+
morlFreqA = data.(xFreqHi{i}).lim1 + floor((data.(xFreqHi{i}).lim2 - data.(xFreqHi{i}).lim1)/2);
932+
ampPAC = morletAmp(morlFreqA, tSeries, param.Fs, param.morlWidth);
938933

939934
% Update data structure:
940935
data.(xFreqHi{i}).xFreq.xFreqLow = param.xFreqLow;
941936
data.(xFreqHi{i}).xFreq.xFreqHi = xFreqHi{i};
942937
data.(xFreqHi{i}).xFreq.morlFreqP = morlFreqP;
943-
data.(xFreqHi{i}).xFreq.morlFreqA = morlFreqA(i);
944-
data.(xFreqHi{i}).xFreq.modPhase = modPhase;
945-
data.(xFreqHi{i}).xFreq.pacAmp = pacAmp(:,i);
938+
data.(xFreqHi{i}).xFreq.morlFreqA = morlFreqA;
939+
data.(xFreqHi{i}).xFreq.phsPAC = phsPAC;
940+
data.(xFreqHi{i}).xFreq.ampPAC = ampPAC;
941+
942+
% Calculate total PAC measure:
943+
data.(xFreqHi{i}).xFreq = calcPACMI(data.(xFreqHi{i}).xFreq, param.Fs, param.nShuffle);
946944
end
947945

948946
%% Time PAC Analysis
949947
% Truncate signals to get integer number of time windows
950948
nSampWn = ceil(param.winLength * param.Fs);
951949
nSampOl = ceil(param.winOverlap * param.Fs);
952950
remSample = mod(nSample, nSampWn);
953-
modPhase = modPhase(1 : nSample - remSample);
954-
pacAmp = pacAmp(1 : nSample - remSample, :);
951+
phsPAC = phsPAC(1 : nSample - remSample);
952+
ampPAC = ampPAC(1 : nSample - remSample, :);
955953

956954
% Update nSample
957-
nSample = length(modPhase);
955+
nSample = length(phsPAC);
958956
idx = bsxfun(@plus, (1:nSampWn)', 1+(0:(fix((nSample - nSampOl)/(nSampWn - nSampOl)) - 1))*(nSampWn - nSampOl)) - 1;
959957
nWin = size(idx,2);
960958

961959
% Determine average time of windows:
962960
timingWin = zeros(nWin, 1);
963-
for j = 1:size(idx, 2)
964-
timingWin(j) = mean(data.LFP.timing(idx(:, j)));
961+
for k = 1:size(idx, 2)
962+
timingWin(k) = mean(data.LFP.timing(idx(:, k)));
965963
end
966-
964+
967965
% Calculate windowed time-series PAC:
968-
pacMIWin = zeros(nWin, 1);
966+
pacMIWin = zeros(nWin, 1);
967+
pacMIWin_Len = zeros(nWin, 1);
968+
pacMIWin_Phase = zeros(nWin, 1);
969+
969970
for i = 1:nHi
970-
for j = 1:size(idx, 2)
971+
for k = 1:size(idx, 2)
972+
973+
z = ampPAC(idx(:,k), i) .* exp(1i * phsPAC(idx(:,k))); % Create composite signal
974+
pacMIWin(k) = mean(z); % Compute the mean length of composite signal
975+
pacMIWin_Len(k) = abs(pacMIWin(k));
976+
pacMIWin_Phase(k) = angle(pacMIWin(k));
971977

972-
z = pacAmp(idx(:,j), i) .* exp(1i * modPhase(idx(:,j))); % Create composite signal
973-
pacRaw = mean(z); % Compute the mean length of composite signal
974-
pacMIWin(j) = abs(pacRaw);
975-
976-
% Update data structure:
977-
data.(xFreqHi{i}).xFreq.pacMIWin = pacMIWin;
978-
data.(xFreqHi{i}).xFreq.timingWin = timingWin;
979978
end
979+
980+
% Update data structure:
981+
data.(xFreqHi{i}).xFreq.pacMIWin = pacMIWin;
982+
data.(xFreqHi{i}).xFreq.pacMIWin_Len = pacMIWin_Len;
983+
data.(xFreqHi{i}).xFreq.pacMIWin_Phase = pacMIWin_Phase;
984+
data.(xFreqHi{i}).xFreq.timingWin = timingWin;
985+
986+
% Linear interpolation of pacMIWin_Len to data samplingInt for later correlations
987+
data.(xFreqHi{i}).xFreq.timingI = data.LFP.timing(1:nSample);
988+
data.(xFreqHi{i}).xFreq.pacMIWin_LenI = interp1(timingWin, pacMIWin_Len, data.(xFreqHi{i}).xFreq.timingI);
989+
990+
% Order structure:
991+
data.(xFreqHi{i}).xFreq = orderStruct(data.(xFreqHi{i}).xFreq);
980992
end
981993
end
982994
end

calcPACMI.m

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,46 @@
1-
function xFreq = calcPACMI(modPhase, pacAmp, nSample, Fs, nShuffle)
1+
function xFreq = calcPACMI(xFreq, Fs, nShuffle)
22
% Computes normalized phase-amplitude coupled modulation index
3-
% Adapted from Canolty et al Science 2006
4-
% See also Onslow et al 2011
3+
% Adapted from Canolty et al 2006 and Onslow et al 2011
54

6-
z = pacAmp .* exp(1i * modPhase); % Create composite signal
7-
pacRaw = mean(z); % Compute the mean length of composite signal
8-
xFreq.pacMI_Raw = abs(pacRaw);
9-
pacShuffle = zeros(nShuffle, 1);
5+
nSample = size(xFreq.phsPAC, 1);
6+
nPhs = size(xFreq.phsPAC, 2);
7+
nAmp = size(xFreq.ampPAC, 2);
108

11-
% Compute shuffled values:
12-
for i = 1 : nShuffle
13-
skip = shuffleInd(nSample, Fs, nShuffle);
14-
pacAmpShuffle = [pacAmp(skip(i):end) pacAmp(1:skip(s)-1)];
15-
pacShuffle(i) = abs(mean(pacAmpShuffle .* exp(1i * modPhase)));
16-
end
9+
% Initialize output arrays:
10+
xFreq.pacMI = zeros(nAmp, nPhs);
11+
xFreq.pacMI_Len = zeros(nAmp, nPhs);
12+
xFreq.pacMI_Phase = zeros(nAmp, nPhs);
1713

18-
% Fit gaussian to surrogate data, uses normfit.m from MATLAB Statistics toolbox
19-
[xFreq.shuffleMean, xFreq.shuffleSTD] = normfit(pacShuffle);
14+
if nShuffle > 1
15+
xFreq.pacMIShf_LenAve = zeros(nAmp, nPhs);
16+
xFreq.pacMIShf_LenSTD = zeros(nAmp, nPhs);
17+
xFreq.pacMI_Z = zeros(nAmp, nPhs);
18+
xFreq.pacMI_LenZ = zeros(nAmp, nPhs);
19+
end
2020

21-
% Normalize length using shuffled data (z-score)
22-
xFreq.pacMI_Length = (xFreq.pacMI_Raw - xFreq.shuffleMean) / xFreq.shuffleSTD;
23-
xFreq.pacMI_Phase = angle(pacRaw);
24-
xFreq.pacMI_Norm = pacMI_Length * exp(1i * pacMI_Phase);
21+
for i = 1 : nAmp
22+
for j = 1 : nPhs
23+
z = xFreq.ampPAC(:,i) .* exp(1i * xFreq.phsPAC(:,j)); % Create composite signal
24+
xFreq.pacMI(i,j) = mean(z); % Compute the mean length of composite signal
25+
xFreq.pacMI_Len(i,j) = abs(xFreq.pacMI(i,j));
26+
xFreq.pacMI_Phase(i,j) = angle(xFreq.pacMI(i,j));
27+
28+
if nShuffle > 1
29+
pacMIShf_Len = zeros(nShuffle, 1);
30+
31+
% Compute shuffled values:
32+
for k = 1 : nShuffle
33+
skip = shuffleInd(nSample, Fs, nShuffle);
34+
ampPacShf = vertcat(xFreq.ampPAC(skip(k):end,j), xFreq.ampPAC(1:skip(k)-1,j));
35+
pacMIShf_Len(k) = abs(mean(ampPacShf .* exp(1i * xFreq.phsPAC(:,i))));
36+
end
37+
38+
% Fit gaussian to shuffled data, uses normfit.m from MATLAB Statistics toolbox
39+
[xFreq.pacMIShf_LenAve(i,j), xFreq.pacMIShf_LenSTD(i,j)] = normfit(pacMIShf_Len);
40+
41+
% Normalize length using shuffled data (z-score)
42+
xFreq.pacMI_LenZ(i,j) = (xFreq.pacMI_Len(i,j) - xFreq.pacMIShf_LenAve(i,j)) / xFreq.pacMIShf_LenSTD(i,j);
43+
xFreq.pacMI_Z(i,j) = xFreq.pacMI_LenZ(i,j) * exp(1i * xFreq.pacMI_Phase(i,j));
44+
end
45+
end
46+
end

exportAveStats.m

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,15 +78,22 @@ function exportAveStats(data, saveFile, exportFile)
7878
if isfield(data, 'gamma')
7979
% Total Stats:
8080
if isfield(data.gamma, 'tPower'); outTable = [outTable table(10^6 * data.gamma.tPower, 'VariableNames', {'Gamma_Tot_Power_uV2'})]; end
81-
if isfield(data.gamma, 'FFT')
81+
if isfield(data.gamma, 'FFT')
8282
if isfield(data.gamma.FFT, 'pkFreq'); outTable = [outTable table(data.gamma.FFT.pkFreq, 'VariableNames', {'Gamma_FFT_pkFreq_Hz'})]; end
83-
if isfield(data.gamma.FFT, 'fitMean'); outTable = [outTable table(data.gamma.FFT.fitMean, 'VariableNames', {'Gamma_FFT_fitMean_Hz'})]; end
83+
if isfield(data.gamma.FFT, 'fitMean'); outTable = [outTable table(data.gamma.FFT.fitMean, 'VariableNames', {'Gamma_FFT_fitMean_Hz'})]; end
8484
if isfield(data.gamma.FFT, 'fitSD'); outTable = [outTable table(data.gamma.FFT.fitSD, 'VariableNames', {'Gamma_FFT_fitSD_Hz'})]; end
85-
if isfield(data.gamma.FFT, 'fitFWHM'); outTable = [outTable table(data.gamma.FFT.fitFWHM, 'VariableNames', {'Gamma_FFT_fitFWHM_Hz'})]; end
85+
if isfield(data.gamma.FFT, 'fitFWHM'); outTable = [outTable table(data.gamma.FFT.fitFWHM, 'VariableNames', {'Gamma_FFT_fitFWHM_Hz'})]; end
8686
end
87-
if isfield(data.gamma, 'phase')
87+
if isfield(data.gamma, 'phase')
8888
if isfield(data.gamma.phase, 'nCycle'); outTable = [outTable table(data.gamma.phase.nCycle, 'VariableNames', {'Gamma_Tot_nCycle'})]; end
89-
if isfield(data.gamma.phase, 'phFreq'); outTable = [outTable table(data.gamma.phase.phFreq, 'VariableNames', {'Gamma_Tot_phFreq_Hz'})]; end
89+
if isfield(data.gamma.phase, 'phFreq'); outTable = [outTable table(data.gamma.phase.phFreq, 'VariableNames', {'Gamma_Tot_phFreq_Hz'})]; end
90+
end
91+
if isfield(data.gamma, 'xFreq')
92+
if isfield(data.gamma.xFreq, 'pacMI_Len'); outTable = [outTable table(data.gamma.xFreq.pacMI_Len, 'VariableNames', {'Gamma_pacMI_Len'})]; end
93+
if isfield(data.gamma.xFreq, 'pacMI_Phase'); outTable = [outTable table(data.gamma.xFreq.pacMI_Phase, 'VariableNames', {'Gamma_pacMI_Phase'})]; end
94+
if isfield(data.gamma.xFreq, 'pacMI_LenZ'); outTable = [outTable table(data.gamma.xFreq.pacMI_LenZ, 'VariableNames', {'Gamma_pacMI_LenZ'})]; end
95+
if isfield(data.gamma.xFreq, 'pacMIShf_LenAve'); outTable = [outTable table(data.gamma.xFreq.pacMIShf_LenAve, 'VariableNames', {'Gamma_pacMIShf_LenAve'})]; end
96+
if isfield(data.gamma.xFreq, 'pacMIShf_LenSTD'); outTable = [outTable table(data.gamma.xFreq.pacMIShf_LenSTD, 'VariableNames', {'Gamma_pacMIShf_LenSTD'})]; end
9097
end
9198
% SWR Stats:
9299
if isfield(data.gamma, 'SWR')
@@ -115,6 +122,13 @@ function exportAveStats(data, saveFile, exportFile)
115122
if isfield(data.hgamma.phase, 'nCycle'); outTable = [outTable table(data.hgamma.phase.nCycle, 'VariableNames', {'HGamma_Tot_nCycle'})]; end
116123
if isfield(data.hgamma.phase, 'phFreq'); outTable = [outTable table(data.hgamma.phase.phFreq, 'VariableNames', {'HGamma_Tot_phFreq_Hz'})]; end
117124
end
125+
if isfield(data.hgamma, 'xFreq')
126+
if isfield(data.hgamma.xFreq, 'pacMI_Len'); outTable = [outTable table(data.hgamma.xFreq.pacMI_Len, 'VariableNames', {'HGamma_pacMI_Len'})]; end
127+
if isfield(data.hgamma.xFreq, 'pacMI_Phase'); outTable = [outTable table(data.hgamma.xFreq.pacMI_Phase, 'VariableNames', {'HGamma_pacMI_Phase'})]; end
128+
if isfield(data.hgamma.xFreq, 'pacMI_LenZ'); outTable = [outTable table(data.hgamma.xFreq.pacMI_LenZ, 'VariableNames', {'HGamma_pacMI_LenZ'})]; end
129+
if isfield(data.hgamma.xFreq, 'pacMIShf_LenAve'); outTable = [outTable table(data.hgamma.xFreq.pacMIShf_LenAve, 'VariableNames', {'HGamma_pacMIShf_LenAve'})]; end
130+
if isfield(data.hgamma.xFreq, 'pacMIShf_LenSTD'); outTable = [outTable table(data.hgamma.xFreq.pacMIShf_LenSTD, 'VariableNames', {'HGamma_pacMIShf_LenSTD'})]; end
131+
end
118132
% SWR Stats:
119133
if isfield(data.hgamma, 'SWR')
120134
if isfield(data.hgamma.SWR, 'power'); outTable = [outTable table(10^6 * mean(data.hgamma.SWR.power,'omitnan'), 'VariableNames', {'HGamma_SWR_Power_uV2'})]; end

morletAmp.m

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,6 @@
3333
t = -(width/2)*st : dt : (width/2)*st;
3434
A = 1 / sqrt((st*sqrt(pi)));
3535
m = A * exp(-t.^2/(2*st^2)) .* exp(1i*2*pi*f.*t);
36-
y = conv(s, m');
37-
y = abs(y);
38-
y = y(ceil(length(m)/2) : length(y) - floor(length(m)/2));
36+
y = conv(s, m');
37+
y = abs(y);
38+
y = y(ceil(length(m)/2) : length(y) - floor(length(m)/2));

morletPhase.m

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,6 @@
3333
t = -(width/2)*st : dt : (width/2)*st;
3434
A = 1 / sqrt((st*sqrt(pi)));
3535
m = A * exp(-t.^2/(2*st^2)) .* exp(1i*2*pi*f.*t);
36-
y = conv(s, m');
37-
y = angle(y);
38-
y = y(ceil(length(m)/2) : length(y) - floor(length(m)/2));
36+
y = conv(s, m');
37+
y = angle(y);
38+
y = y(ceil(length(m)/2) : length(y) - floor(length(m)/2));

0 commit comments

Comments
 (0)