├── LICENSE ├── README.md ├── code ├── CFCestimatorFactory.m ├── applyFilterBankThenHT_filtfilt.m ├── cmd.m ├── demo1.m ├── demo2.m ├── estimateCFC_EAA_Tort2008.m ├── estimateCFC_ESC_Bruns2004.m ├── estimateCFC_MCS_Canolty2006.m ├── estimateCFC_PLV_Lachaux1999.m ├── estimateCFC_R2R_Penny2008.m ├── estimateCFC_template.m ├── filterBank_bal.m ├── filterBank_prop.m ├── filterBank_uniform.m ├── generateSurrogateIndices.m ├── surrogateStats.m └── visualizeFilterBank.m └── doc ├── demo2.png ├── mcs_example.png └── signal_snippet.png /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Il Memming Park 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cross-frequency coupling estimators 2 | 3 | ## Example signal 4 | ![nonlinearly coupled signal snippet](doc/signal_snippet.png) 5 | 6 | ## Example result 7 | ![MCS example heat map](doc/mcs_example.png) 8 | 9 | ## References 10 | - Canolty, R. T., Edwards, E., Dalal, S. S., Soltani, M., Nagarajan, S. S., Kirsch, H. E., Berger, M. S., Barbaro, N. M., and Knight, R. T. (2006). High gamma power is Phase-Locked to theta oscillations in human neocortex. _Science_, 313(5793):1626-1628. 11 | - Penny, W. D., Duzel, E., Miller, K. J., and Ojemann, J. G. (2008). Testing for nested oscillation. _Journal of Neuroscience Methods_, 174(1):50-61. 12 | - Tort, A. B. L., Komorowski, R., Eichenbaum, H., and Kopell, N. (2010). Measuring Phase-Amplitude coupling between neuronal oscillations of different frequencies. _Journal of Neurophysiology_, 104(2):1195-1210. 13 | - Tort, A. B. L., Kramer, M. A., Thorn, C., Gibson, D. J., Kubota, Y., Graybiel, A. M., and Kopell, N. J. (2008). Dynamic cross-frequency couplings of local field potential oscillations in rat striatum and hippocampus during performance of a t-maze task. _Proceedings of the National Academy of Sciences_, 105(51):20517-20522. 14 | - Bruns, A. and Eckhorn, R. (2004). Task-related coupling from high- to low-frequency signals among visual cortical areas in human subdural recordings. _International Journal of Psychophysiology_, 51(2):97-116. 15 | - Lachaux, J.-P., Rodriguez, E., Martinerie, J., and Varela, F. J. (1999). Measuring phase synchrony in brain signals. _Human Brain Mapping_, 8(4):194-208. 16 | -------------------------------------------------------------------------------- /code/CFCestimatorFactory.m: -------------------------------------------------------------------------------- 1 | function estimators = CFCestimatorFactory(names) 2 | % TODO: general interface with the estimators 3 | 4 | if nargin < 1 || isempty(names) 5 | estimators = {'MCS', 'PLV', 'ESC', 'R2R', 'EAA'}; 6 | return 7 | end 8 | 9 | if ischar(names) 10 | if strcmpi(names, 'all') 11 | names = {'MCS', 'PLV', 'ESC', 'R2R', 'EAA'}; 12 | else 13 | estimators = resolveOne(names); 14 | return 15 | end 16 | end 17 | 18 | for k = 1:numel(names) 19 | estimators(k) = resolveOne(names{k}); 20 | end 21 | 22 | end % CFCestimatorFactory 23 | 24 | function estimator = resolveOne(name) 25 | 26 | switch(upper(name)) 27 | case {'MCS', 'CANOLTY2006'} 28 | estimator.ID = 'MCS'; 29 | estimator.ref = 'Canolty2006'; 30 | estimator.desc = 'magnitude of mean compound signal'; 31 | estimator.handle = @estimateCFC_MCS_Canolty2006; 32 | case {'PLV', 'LACHAUX1999'} 33 | estimator.ID = 'PLV'; 34 | estimator.ref = 'Lachaux1999'; 35 | estimator.desc = 'phase locking value'; 36 | estimator.handle = @estimateCFC_PLV_Lachaux1999; 37 | case {'ESC', 'BRUNS2004'} 38 | estimator.ID = 'ESC'; 39 | estimator.ref = 'Bruns2004'; 40 | estimator.desc = 'envelope-signal-correlation'; 41 | estimator.handle = @estimateCFC_ESC_Bruns2004; 42 | case {'R2R', 'Penny2008'} 43 | estimator.ID = 'R2R'; 44 | estimator.ref = 'Penny2008'; 45 | estimator.desc = 'r^2 of phase-amplitude-regression'; 46 | estimator.handle = @estimateCFC_R2R_Penny2008; 47 | case {'EAA', 'Tort2008'} 48 | estimator.ID = 'EAA'; 49 | estimator.ref = 'Tort2008'; 50 | estimator.desc = 'Entropy of average amplitude per phase'; 51 | estimator.handle = @estimateCFC_EAA_Tort2008; 52 | otherwise 53 | error('No such estimator [%s]', name); 54 | end 55 | 56 | end % resolveOne 57 | -------------------------------------------------------------------------------- /code/applyFilterBankThenHT_filtfilt.m: -------------------------------------------------------------------------------- 1 | function [x_filtered, x_analytic] = applyFilterBankThenHT_filtfilt(b, a, x, nBoundaryRemoval) 2 | % Use filtfilt to do zero-phase distortion, acausal bandpass filtering 3 | % followed by Hilbert transform. If the bandpass is not narrow enough, 4 | % Hilbert transform might suffer to extract envelop and phases correctly. 5 | % 6 | % [x_filtered, x_analytic] = applyFilterBankThenHT_filtfilt(b, a, x, 7 | % nBoundaryRemoval); 8 | % 9 | % Input 10 | % b,a: {N x 1} transfer function coefficients 11 | % x: [T+2*nBoundaryRemoval x 1] broadband signal to be filtered 12 | % nBoundaryRemoval: [1] number of time bins to trim off from both ends 13 | % for removing filter artifacts on the boundary 14 | % 15 | % Output 16 | % x_filtered: [T x N; real] bandpass filtered output 17 | % x_analytic: [T x N; complex] result of hilbert transform on output 18 | 19 | T = size(x, 1); 20 | x_filtered = zeros(T - 2*nBoundaryRemoval, numel(b)); 21 | x_analytic = complex(x_filtered, 0); 22 | 23 | for kCenter = 1:numel(b) 24 | xTemp = filtfilt(b{kCenter}, a{kCenter}, x); 25 | x_filtered(:, kCenter) = xTemp(nBoundaryRemoval+1:end-nBoundaryRemoval); 26 | xTemp = hilbert(xTemp); 27 | x_analytic(:, kCenter) = xTemp(nBoundaryRemoval+1:end-nBoundaryRemoval); 28 | end -------------------------------------------------------------------------------- /code/cmd.m: -------------------------------------------------------------------------------- 1 | % rand('seed', 20150827); randn('seed', 20150827); 2 | 3 | %% Setup frequencies of interest and corresponding filter banks 4 | fs = 1000; % sampling frequency 5 | f1 = 10 / fs; 6 | f2 = 50 / fs; 7 | df = 4 / fs; % pass band band-width 8 | 9 | nTap = ceil(2/f1); %ceil(2/df); % For an FIR, nTap should be longer than one cycle of lowest freq 10 | removeBoundaryHandle = @(z) z(nTap+1:end-nTap); 11 | 12 | designBPhandle = @(f) firls(ceil(2/f), [0, f-df, f-df/2, f+df/2, f+df, 1/2] * 2, [0 0 1 1 0 0]); 13 | b1 = designBPhandle(f1); 14 | b2 = designBPhandle(f2); 15 | 16 | %% Simulate some random signals 17 | T = ceil(10 * fs); 18 | TT = T + 2 * nTap; 19 | 20 | switch(2) 21 | case 1 22 | e = randn(T + 2 * nTap, 1); 23 | e1 = filtfilt(b1, 1, e); 24 | e2 = filtfilt(b2, 1, e); 25 | x = e1 .* sin((1:numel(e))'/T*8) + e2 .* e1; 26 | 27 | case 2 28 | e1 = cos(2 * pi * f1 * (1:TT) + 0.2 * cumsum(randn(1, TT))); 29 | e2 = exp(-e1) .* cos(2 * pi * f2 * (1:TT)); 30 | x = e1 + e2; 31 | x = x(:); 32 | x = zscore(x) + 0.01 * randn(TT, 1); 33 | end 34 | 35 | figRaw = figure(4017); clf; plot(removeBoundaryHandle(x)); title('x(t)'); 36 | %% band-pass filter x 37 | xLow = filtfilt(b1, 1, x); 38 | xHigh = filtfilt(b2, 1, x); 39 | 40 | %% Hilbert transform 41 | X1 = hilbert(xLow); 42 | X2 = hilbert(xHigh); 43 | 44 | %% remove boundaries 45 | x = removeBoundaryHandle(x); 46 | xLow = removeBoundaryHandle(xLow); 47 | xHigh = removeBoundaryHandle(xHigh); 48 | X1 = removeBoundaryHandle(X1); 49 | X2 = removeBoundaryHandle(X2); 50 | 51 | %% Extract amplitude and phase from the analytical signal 52 | aLow = abs(X1); phiLow = angle(X1); 53 | aHigh = abs(X2); phiHigh = angle(X2); 54 | 55 | %% Plot them 56 | fig = figure(62789); clf; 57 | subplot(2,2,1); plot(xLow); 58 | hold all; plot(aLow); 59 | subplot(2,2,3); plot(phiLow); 60 | subplot(2,2,2); plot(xHigh); 61 | hold all; plot(aHigh) 62 | subplot(2,2,4); plot(phiHigh); 63 | linkaxes(get(fig, 'Children'), 'x'); 64 | 65 | % Parameters for the surrogate generation 66 | nSurrogate = 999; % number of shuffled surrogates requested 67 | minTimeShift = 1 * fs; % minimum time to be separated to have phase decoherence 68 | rpIdxAll = generateSurrogateIndices(T, minTimeShift, nSurrogate); 69 | 70 | %% Inspect decoherence 71 | % the phase autocorrelation should decay significantly by minTimeShift 72 | [xcLow, lags] = xcorr(phiLow, ceil(min(T/4, minTimeShift*8)), 'coeff'); 73 | xcHigh = xcorr(phiHigh, ceil(min(T/4, minTimeShift*8)), 'coeff'); 74 | figure(4125); clf; hold all; title('phase autocorrelation'); 75 | plot(lags(lags>=0), xcLow (lags>=0)); 76 | plot(lags(lags>=0), xcHigh(lags>=0)); 77 | line(minTimeShift * [1, 1], [-1, 1], 'Color', 'k'); 78 | lh = legend('Low freq', 'High freq', 'Location', 'SouthEast'); 79 | set(lh, 'box', 'off'); 80 | xlabel('time lag (samples)'); 81 | 82 | %% Generate surrogates 83 | xx1 = repmat(xLow(:), 1, nSurrogate+1); 84 | xx2 = xHigh(rpIdxAll); 85 | xaLow = repmat(aLow(:), 1, nSurrogate+1); 86 | xphiLow = repmat(phiLow(:), 1, nSurrogate+1); 87 | xaHigh = aHigh(rpIdxAll); 88 | xphiHigh = phiHigh(rpIdxAll); 89 | 90 | %% 91 | CFC = cell(5, 1); 92 | CFC{1} = estimateCFC_MCS_Canolty2006(f1, f2, xx1, xx2, xaLow, xphiLow, xaHigh, xphiHigh); 93 | CFC{2} = estimateCFC_PLV_Lachaux1999(f1, f2, xx1, xx2, xaLow, xphiLow, xaHigh, xphiHigh); 94 | CFC{3} = estimateCFC_ESC_Bruns2004(f1, f2, xx1, xx2, xaLow, xphiLow, xaHigh, xphiHigh); 95 | CFC{4} = estimateCFC_R2R_Penny2008(f1, f2, xx1, xx2, xaLow, xphiLow, xaHigh, xphiHigh); 96 | CFC{5} = estimateCFC_EAA_Tort2008(f1, f2, xx1, xx2, xaLow, xphiLow, xaHigh, xphiHigh); 97 | CFCstr = {'MCS', 'PLV', 'ESC', 'R2R', 'EAA'}; 98 | % mean compound signal 99 | % phase locking value 100 | % envelop-to-signal correlation 101 | % r^2 of regression 102 | % entropy of average amplitude 103 | 104 | %% 105 | for kCFC = 1:numel(CFC) 106 | threshold = quantile(CFC{kCFC}(2:end), 1 - 0.05); 107 | pValue = (sum(CFC{kCFC}(2:end) > CFC{kCFC}(1)) + 1)/nSurrogate; 108 | m = mean(CFC{kCFC}(2:end)); 109 | s = std(CFC{kCFC}(2:end)); 110 | dev = (CFC{kCFC}(1) - m)/s; 111 | fprintf('CFC [%s]: %g\tp-value [%.4f]\tdeviation [%f]\n', CFCstr{kCFC}, CFC{kCFC}(1), pValue, dev); 112 | end 113 | 114 | figure(1408); clf; hold all; 115 | hist(CFC{kCFC}(2:end), 20) 116 | line(CFC{kCFC}(1) * [1, 1], [0, nSurrogate/10], 'Color', 'r'); 117 | line(threshold * [1, 1], [0, nSurrogate/10], 'Color', 'b'); 118 | 119 | return 120 | 121 | %% Estimate simple PACs 122 | rNESC = corr(cos(angle(X1)), abs(X2)) 123 | rAEC = corr(abs(X1), abs(X2)) 124 | 125 | %% Entropy of average amplitude conditioned on phase distribution method 126 | % 127 | amp2 = abs(X2); 128 | nBin = 18; 129 | phaseBins = linspace(-pi, pi, nBin + 1); 130 | phaseBinCenters = (phaseBins(1:end-1) + phaseBins(2:end))/2; 131 | aacpd = zeros(numel(phaseBins)-1, 1); 132 | for kBin = 1:numel(phaseBins)-1 133 | bIdx = phi >= phaseBins(kBin) & phi < phaseBins(kBin+1); 134 | aacpd(kBin) = mean(amp2(bIdx)); 135 | end 136 | figure(957); clf; 137 | bar(phaseBinCenters, aacpd); 138 | aacpd = aacpd / sum(aacpd); 139 | H = @(P) -sum(P(P~=0).*log(P(P~=0))); 140 | Hmax = -log(1/nBin); 141 | (Hmax - H(aacpd))/Hmax 142 | 143 | %% dependence between the filtered signal and the amplitude signal 144 | % I don't like phase signal because of phase slips 145 | % also, don't need to be concerned with phase being circular variable 146 | % the main problem is that overall amplitude fluctuations creates 147 | % spurious PAC estimates. 148 | 149 | % dep(xLow, abs(X2)) 150 | -------------------------------------------------------------------------------- /code/demo1.m: -------------------------------------------------------------------------------- 1 | %% Basic parameters for the demo 2 | fs = 1000; % sampling frequency 3 | plotDelay = 0.01; % delay in sequence designed filter plots (in seconds) 4 | outDir = 'demo_output'; 5 | 6 | warning('off', 'MATLAB:MKDIR:DirectoryExists') 7 | mkdir(outDir); 8 | 9 | %% Design filter bank 10 | switch 7 11 | case 1 12 | [b, a, fCenterList, nTap] = filterBank_uniform(8, 100, 5, 5, fs, 'cheby2', 3); 13 | FB_prefix = 'cb2_bw5_40'; 14 | case 2 15 | [b, a, fCenterList, nTap] = filterBank_uniform(8, 100, 5, 4, fs, 'firls', 3); 16 | FB_prefix = 'firls_bw4'; 17 | case 3 18 | [b, a, fCenterList, nTap] = filterBank_uniform(8, 100, 5, 4, fs, 'fir1', 3); 19 | FB_prefix = 'fir1_bw4'; 20 | case 4 21 | [b, a, fCenterList, nTap] = filterBank_uniform(8, 100, 5, 4, fs, 'firls', 6); 22 | FB_prefix = 'firls_bw4_nC6'; 23 | case 5 24 | [b, a, fCenterList, nTap, fEdges] = filterBank_prop(5, 100, 3, 5, 1000, 'firls'); 25 | FB_prefix = 'pLS_TBP3'; 26 | case 6 27 | [b, a, fCenterList, nTap, fEdges] = filterBank_prop(4, 100, 2, 7, 1000, 'fir1'); 28 | FB_prefix = 'pfir1_TBP2'; 29 | case 7 30 | [b, a, fCenterList, nTap, fEdges] = filterBank_prop(4, 150, 1, 3, 1000, 'fir1', 3); 31 | % uniformly spaced frequency bands 32 | FB_prefix = 'pfir1_TBP1'; 33 | case 8 34 | [b, a, fCenterList, nTap, fEdges] = filterBank_prop(4, 150, 1, 3, 1000, 'cheby2', 3); 35 | FB_prefix = 'pchb2_TBP1'; 36 | case 9 37 | [b, a, fCenterList, nTap, fEdges] = filterBank_bal(4:3:150, 1, 3, 1000, 'fir1'); 38 | FB_prefix = 'bfir1_1'; 39 | end 40 | figVis = visualizeFilterBank(b, a, fCenterList, fs, outDir, FB_prefix); 41 | 42 | %% Make some fake signal with cross-frequency coupling 43 | TT = 5000; 44 | T = TT + 2 * nTap; 45 | 46 | f1 = 10 / fs; 47 | f2 = 75 / fs; 48 | e1 = cos(2 * pi * f1 * (1:T) + 0.1 * cumsum(randn(1, T))); 49 | e2 = exp(-2*e1) .* cos(2 * pi * f2 * (1:T)) / 4; 50 | x = e1 + e2; 51 | x = x(:); 52 | 53 | %% Add spectrally matching noise 54 | % We want to make the Fourier transform magnitude 1 55 | fx = fft(x); 56 | ap = abs(fx); 57 | aq = quantile(ap, 0.95); 58 | ap(ap > aq) = aq; 59 | ap = (aq - ap) / aq; 60 | 61 | % introduce random phase, unit power noise 62 | fnoise = fft(randn(size(x))); 63 | fnoise = fnoise .* ap; 64 | noise = 2 * ifft(fnoise); 65 | x = x + noise; 66 | 67 | clear fx ap aq fnoise 68 | 69 | %% 70 | figure(924); clf; 71 | subplot(4,1,1); hold all; 72 | pwelch(x, [], [], [], fs); 73 | line(fs * f1 * [1, 1], [-30, -20], 'Color', 'r'); 74 | line(fs * f2 * [1, 1], [-30, -20], 'Color', 'r'); 75 | xlim([0 200]); 76 | 77 | subplot(4,1,2); hold all 78 | tRange = nTap + (100:500); 79 | plot(e1, 'LineWidth', 2); 80 | plot(e2, 'LineWidth', 2); 81 | plot(x, 'k'); 82 | xlim([tRange(1), tRange(end)]); 83 | lh = legend(sprintf('slow oscillation (%d Hz)', f1), sprintf('fast oscillation (%d Hz)', f2), 'noisy observation'); 84 | 85 | tRange = tRange - nTap; 86 | 87 | %% 88 | [x_filtered, x_analytic] = applyFilterBankThenHT_filtfilt(b, a, x, nTap); 89 | amplitude = abs(x_analytic); 90 | phase = angle(x_analytic); 91 | 92 | %% Visualize some of the filtered signals 93 | fig = figure(924); 94 | nBand = numel(b); ph = []; 95 | for kBand = 1:nBand 96 | subplot(4,1,1); 97 | [h, w] = freqz(b{kBand}, a{kBand}, 1024); 98 | if ~isempty(ph) 99 | delete(ph); 100 | end 101 | ph = plot(w * fs / 2 / pi, 10*log10(abs(h))); 102 | ylim([-60, 0]); 103 | 104 | subplot(4,1,3); cla; hold all; 105 | plot(tRange, x_filtered(tRange, kBand)); 106 | plot(tRange, amplitude(tRange, kBand)); 107 | xlim([tRange(1), tRange(end)]); 108 | subplot(4,1,4); cla; hold all; 109 | plot(tRange, phase(tRange, kBand)); 110 | title(num2str(fEdges(:, kBand)')) 111 | drawnow 112 | pause(plotDelay); 113 | end 114 | % r = input('Go? ', 's'); if lower(r(1)) ~= 'y'; disp('Aborting'); return; end 115 | 116 | ts = datestr(now,30); 117 | 118 | set(fig, 'PaperSize', [5 8], 'PaperPosition', [0 0 5 8]); 119 | saveas(fig, sprintf('%s/%s_%s_sample.pdf', outDir, ts, FB_prefix)); 120 | 121 | set(figVis, 'PaperSize', [8 5], 'PaperPosition', [0 0 8 5]); 122 | saveas(figVis, sprintf('%s/%s_%s_filters.pdf', outDir, ts, FB_prefix)); 123 | 124 | %% 125 | fLowRange = fCenterList(fCenterList <= 35); nLow = numel(fLowRange); 126 | fHighRange = fCenterList(fCenterList >= 40); nHigh = numel(fHighRange); 127 | highIdxOffset = find(fCenterList == fHighRange(1)) - 1; 128 | assert(nLow > 0); 129 | assert(nHigh > 0); 130 | 131 | %% Get some estimators 132 | estimators = CFCestimatorFactory('all'); 133 | nEstimator = numel(estimators); 134 | 135 | % Parameters for the surrogate generation 136 | nSurrogate = 999; % number of shuffled surrogates requested 137 | minTimeShift = 0.1 * fs; % minimum time to be separated to have phase decoherence 138 | rpIdxAll = generateSurrogateIndices(T - 2*nTap, minTimeShift, nSurrogate); 139 | 140 | CFC = {}; clear CFC 141 | CFC(nEstimator, nLow, nHigh) = surrogateStats(); % initialize results structure 142 | for kLow = 1:nLow 143 | fLow = fLowRange(kLow); 144 | xLow = x_filtered(:, kLow); 145 | aLow = amplitude(:, kLow); 146 | pLow = phase(:, kLow); 147 | for kHigh = 1:nHigh 148 | fHigh = fHighRange(kHigh); 149 | xHigh = x_filtered(:, kHigh + highIdxOffset); 150 | aHigh = amplitude(:, kHigh + highIdxOffset); 151 | pHigh = phase(:, kHigh + highIdxOffset); 152 | 153 | %% Generate surrogates 154 | xxLow = repmat(xLow(:), 1, nSurrogate+1); 155 | xxHigh = xHigh(rpIdxAll); 156 | xaLow = repmat(aLow(:), 1, nSurrogate+1); 157 | xpLow = repmat(pLow(:), 1, nSurrogate+1); 158 | xaHigh = aHigh(rpIdxAll); 159 | xpHigh = pHigh(rpIdxAll); 160 | 161 | for kEstim = 1:nEstimator 162 | CFCtemp = estimators(kEstim).handle(fLow, fHigh, ... 163 | xxLow, xxHigh, xaLow, xpLow, xaHigh, xpHigh); 164 | 165 | stat = surrogateStats(CFCtemp(1), CFCtemp(2:end)); 166 | CFC(kEstim, kLow, kHigh) = stat; 167 | end 168 | end 169 | end 170 | 171 | %% 172 | for kEstim = 1:nEstimator 173 | fig = figure(5877+kEstim); clf; 174 | 175 | subplot(1,3,1); 176 | imagesc(fLowRange, fHighRange, reshape([CFC(kEstim,:,:).value], nLow, nHigh)'); axis xy; colorbar; colormap('jet') 177 | title(estimators(kEstim).desc); 178 | 179 | subplot(1,3,2); 180 | imagesc(fLowRange, fHighRange, reshape([CFC(kEstim,:,:).deviation], nLow, nHigh)'); axis xy; colorbar; colormap('jet') 181 | title('deviation'); 182 | xlabel('Freq (Hz)'); ylabel('Freq (Hz)'); 183 | hold on 184 | plot(f1*fs, f2*fs, 'ro'); 185 | 186 | subplot(1,3,3); 187 | pValueMap = reshape([CFC(kEstim,:,:).pValue], nLow, nHigh)'; % returns rounded up p-value... 188 | significanceMap = (pValueMap <= 0.1) + (pValueMap <= 0.05) + (pValueMap <= 0.001); 189 | imagesc(fLowRange, fHighRange, significanceMap); axis xy; 190 | cbh = colorbar; colormap('jet'); caxis([0 3]); 191 | set(cbh, 'Ticks', [0 1 2 3], 'TickLabels', {'n.s.', 'p < 0.1', 'p < 0.05', 'p < 0.001'}); 192 | title('significance (bootstrap, unadjusted)') 193 | xlabel('Freq (Hz)'); ylabel('Freq (Hz)'); 194 | hold on 195 | plot(f1*fs, f2*fs, 'ro'); 196 | drawnow 197 | 198 | set(fig, 'PaperSize', [8 3], 'PaperPosition', [0 0 8 3]); 199 | saveas(fig, sprintf('%s/%s_%s_%s.pdf', outDir, ts, FB_prefix, estimators(kEstim).ID)); 200 | end 201 | -------------------------------------------------------------------------------- /code/demo2.m: -------------------------------------------------------------------------------- 1 | %% Demonstrate Phase-Amplitude coupling with a clean sample signal 2 | % Purpose is to visualize the concept and provide intuition for the readers 3 | 4 | rand('seed', 20150928); randn('seed', 20150928); 5 | 6 | %% Generate the two signals that are interacting 7 | fs = 1000; 8 | f1 = 10; 9 | f2 = 75; 10 | T = 2000; 11 | sigma1 = 0.1; 12 | sigman = 0.15; 13 | e1 = cos(2 * pi * f1/fs * (1:T)' + sigma1 * cumsum(randn(T, 1))); 14 | e2 = exp(-3*e1) .* cos(2 * pi * f2/fs * (1:T)') / 10; 15 | tr = ((1:T)-1)*1e-3; 16 | x = e1 + e2 + sigman * randn(T, 1); 17 | 18 | %% 19 | [b, a, fCenterList, nTap, fEdges] = filterBank_prop(f1, f2, 1, 3, 1000, 'fir1', f2-f1); 20 | [x_filtered, x_analytic] = applyFilterBankThenHT_filtfilt(b, a, x, nTap); 21 | amplitude = abs(x_analytic); 22 | phase = angle(x_analytic); 23 | 24 | %% 25 | tshift = nTap/fs; 26 | tr2 = tr(1:end-2*nTap); 27 | tr = tr - tshift; 28 | 29 | %% 30 | fig = figure(924); clf; 31 | ax = []; 32 | ax(1) = subplot(4,1,1); cla; hold on 33 | plot(tr, x, 'k', 'LineWidth', 2); 34 | plot(tr, e1, 'b--'); 35 | plot(tr, e2, 'r--'); 36 | xl = [0, 0.3]; 37 | xlim(xl); 38 | title('synthetic nonlinear LFP'); 39 | 40 | %% 41 | ax(2) = subplot(4,1,2); cla; hold on 42 | plot(tr2, x_filtered(:, 1)); 43 | xlim(xl); 44 | plot(tr2, amplitude(:, 1)); 45 | title(sprintf('bandpass at %d Hz', f1)); 46 | 47 | pos = get(ax(2), 'Position'); 48 | pos(4) = pos(4)/20; % 10th of the height of subplot 49 | ax(3) = axes('Position', pos); 50 | imagesc(tr2, 1, phase(:, 1)'); 51 | xlim(xl); 52 | set(ax(3), 'Xtick', [], 'YTick', [], 'box', 'off'); 53 | cmap = colormap('hsv'); 54 | 55 | %% 56 | 57 | ax(4) = subplot(4,1,3); cla; hold on 58 | plot(tr2, x_filtered(:, 2)); 59 | xlim(xl); 60 | plot(tr2, amplitude(:, 2)); 61 | title(sprintf('bandpass at %d Hz', f2)); 62 | 63 | pos = get(ax(4), 'Position'); 64 | pos(4) = pos(4)/20; % 10th of the height of subplot 65 | ax(5) = axes('Position', pos); 66 | imagesc(tr2, 1, phase(:, 2)'); 67 | xlim(xl); 68 | set(ax(5), 'Xtick', [], 'YTick', [], 'box', 'off'); 69 | cmap = colormap('hsv'); 70 | 71 | %% 72 | set(ax(1:5), 'YTick', []); 73 | set(ax(2:5), 'XTickLabel', []); 74 | 75 | %% 76 | compound_signal = amplitude(:, 2) .* exp(1i*phase(:, 1)); 77 | ax(6) = subplot(4,2,7); cla; hold on 78 | plot(exp(1i*linspace(0, 2*pi)), '-', 'Color', 0.7 * [1, 1, 1], 'LineWidth', 2); 79 | for k = 1:numel(compound_signal) 80 | kColor = ceil((1 + angle(compound_signal(k)) / pi) * size(cmap, 1) / 2); 81 | plot(compound_signal(k), '.', 'Color', cmap(kColor,:)) 82 | end 83 | plot(mean(compound_signal), 'xk', 'MarkerSize', 10) 84 | axis equal; grid on 85 | axis([-2,2,-1.2,1.2]) 86 | title('compound signal') 87 | xlabel('real'); 88 | ylabel('imaginary'); 89 | 90 | %% 91 | rpa = amplitude(randperm(size(amplitude, 1)), 2); 92 | compound_signal_shuffled = rpa .* exp(1i*phase(:, 1)); 93 | 94 | ax(7) = subplot(4,2,8); cla; hold on 95 | plot(exp(1i*linspace(0, 2*pi)), '-', 'Color', 0.7 * [1, 1, 1], 'LineWidth', 2); 96 | for k = 1:numel(compound_signal_shuffled) 97 | kColor = ceil((1 + angle(compound_signal_shuffled(k)) / pi) * size(cmap, 1) / 2); 98 | plot(compound_signal_shuffled(k), '.', 'Color', cmap(kColor,:)) 99 | end 100 | plot(mean(compound_signal_shuffled), 'xk', 'MarkerSize', 10); 101 | axis equal; grid on 102 | axis([-2,2,-1.2,1.2]) 103 | title('shuffled surrogate') 104 | 105 | %% 106 | set(fig, 'PaperUnit', 'inches', 'PaperSize', [5, 10], 'PaperPosition', [0, 0, 5, 10]); 107 | saveas(fig, 'demo2.png'); 108 | saveas(fig, 'demo2.pdf'); -------------------------------------------------------------------------------- /code/estimateCFC_EAA_Tort2008.m: -------------------------------------------------------------------------------- 1 | function [CFC, extra] = estimateCFC_EAA_Tort2008(fLow, fHigh, xLow, xHigh, aLow, phiLow, aHigh, phiHigh, param) 2 | % CFC = estimateCFC_EAA_Tort2008(fLow, fHigh, xLow, xHigh, aLow, phiLow, aHigh, phiHigh, param) 3 | % Entropy of Average Amplitude (EAA) conditioned on phase distribution method 4 | % 5 | % Extra parameter: # of phase bins (default 18). 6 | 7 | if nargin > 8 8 | nBin = param; 9 | assert(numel(nBin) == 1, 'single param for number of bins') 10 | assert(res(nBin, 1) == 0, 'number of bins should be integer') 11 | assert(nBin > 1, 'number of bins should be greater than 2'); 12 | else 13 | nBin = 18; 14 | end 15 | 16 | sz = size(xLow); N = prod(sz(2:end)); 17 | H = @(P) -sum(P(P~=0).*log(P(P~=0))); % entropy function 18 | 19 | % The original paper requires z-scoring the filtered signals. It changes 20 | % the normalization of mean amplitude distribution. Subtracting the mean 21 | % can't be emulated, but normalizing by the standard deviation can be done 22 | % even after Hilbert transform. 23 | aHigh = bsxfun(@times, aHigh, 1 ./ std(xHigh)); 24 | 25 | phaseBins = linspace(-pi, pi, nBin + 1); 26 | phaseBinCenters = (phaseBins(1:end-1) + phaseBins(2:end))/2; 27 | 28 | CFC = zeros(N, 1); 29 | aacpd = zeros(numel(phaseBins)-1, N); 30 | for k = 1:N 31 | for kBin = 1:numel(phaseBins)-1 32 | bIdx = phiLow(:,k) >= phaseBins(kBin) & phiLow(:,k) < phaseBins(kBin+1); 33 | aacpd(kBin,:) = mean(aHigh(bIdx,k)); 34 | end 35 | 36 | % normalize to make it a distribution 37 | aacpd = aacpd / sum(aacpd); 38 | 39 | Hmax = -log(1/nBin); 40 | CFC(k) = (Hmax - H(aacpd))/Hmax; 41 | end 42 | 43 | if numel(sz) > 2; CFC = reshape(CFC, sz(2:end)); end 44 | 45 | if nargout > 1 46 | extra.nBin = nBin; 47 | extra.phaseBins = phaseBins; 48 | extra.phaseBinCenters = phaseBinCenters; 49 | extra.aacpd = aacpd; 50 | end 51 | -------------------------------------------------------------------------------- /code/estimateCFC_ESC_Bruns2004.m: -------------------------------------------------------------------------------- 1 | function CFC = estimateCFC_ESC_Bruns2004(fLow, fHigh, xLow, xHigh, aLow, phiLow, aHigh, phiHigh) 2 | % Envolop-to-Signal Correlation 3 | % 4 | % Weakness: can't detect if coupling is at double the frequency 5 | % Weakness: can't detect if amplitude has symmetric deviation (for example 6 | % increased variance) 7 | % Weakness: can't discriminate between common amplidue gain and 8 | % phase-correlation 9 | 10 | sz = size(xLow); N = prod(sz(2:end)); 11 | CFC = zeros(N, 1); 12 | for k = 1:N 13 | CFC(k) = abs(corr(xLow(:,k), aHigh(:,k))); 14 | end 15 | if numel(sz) > 2; CFC = reshape(CFC, sz(2:end)); end 16 | -------------------------------------------------------------------------------- /code/estimateCFC_MCS_Canolty2006.m: -------------------------------------------------------------------------------- 1 | function CFC = estimateCFC_MCS_Canolty2006(fLow, fHigh, xLow, xHigh, aLow, phiLow, aHigh, phiHigh) 2 | % Mean of the Compound Signal (MCS: high-freq amplitude and low-freq phase) 3 | % 4 | % Weakness: can't detect if coupling is at double the frequency 5 | % Weakness: can't detect if amplitude has symmetric deviation (for example 6 | % increased variance) 7 | 8 | sz = size(xLow); 9 | CFC = abs(mean(aHigh .* exp(1i*phiLow))); 10 | CFC = permute(CFC, [2:numel(sz), 1]); -------------------------------------------------------------------------------- /code/estimateCFC_PLV_Lachaux1999.m: -------------------------------------------------------------------------------- 1 | function CFC = estimateCFC_PLV_Lachaux1999(fLow, fHigh, xLow, xHigh, aLow, phiLow, aHigh, phiHigh) 2 | % Phase-locking value (PLV) 3 | % phase of the amplitude of the high frequency should be locked 4 | % with the phase of the low frequency signal. 5 | % 6 | % Weakness: can't detect if coupling is at integer multiples of the lower frequency 7 | % Weakness: when amplitude is small, and phase slip happens, you know. 8 | 9 | % We need a second Hilbert transform to do it. 10 | phiAmpHigh = angle(hilbert(aHigh)); 11 | 12 | sz = size(xLow); 13 | CFC = abs(mean(exp(1i*(phiLow - phiAmpHigh)))); 14 | CFC = permute(CFC, [2:numel(sz), 1]); 15 | -------------------------------------------------------------------------------- /code/estimateCFC_R2R_Penny2008.m: -------------------------------------------------------------------------------- 1 | function CFC = estimateCFC_R2R_Penny2008(fLow, fHigh, xLow, xHigh, aLow, phiLow, aHigh, phiHigh) 2 | % r^2 of the residual from GLM regression of amplitude from sin and cos of phase 3 | 4 | sz = size(xLow); N = prod(sz(2:end)); 5 | CFC = zeros(N, 1); 6 | 7 | T = size(phiLow, 1); 8 | for k = 1:size(xLow, 2) 9 | Xtemp = [cos(phiLow(:,k)), sin(phiLow(:,k)), ones(T, 1)]; 10 | res = Xtemp * (Xtemp \ aHigh(:,k)) - aHigh(:,k); 11 | RSS = sum(res.^2); % residual sum of squares 12 | TSS = sum(aHigh(:,k).^2); % total sum of squares 13 | CFC(k) = 1 - RSS/TSS; % r^2 is the metric 14 | end 15 | 16 | if numel(sz) > 2; CFC = reshape(CFC, sz(2:end)); end -------------------------------------------------------------------------------- /code/estimateCFC_template.m: -------------------------------------------------------------------------------- 1 | function [CFC, extra] = estimateCFC_template(fLow, fHigh, xLow, xHigh, aLow, phiLow, aHigh, phiHigh, varargin) 2 | % Computes a cross-frequency-couping (CFC) index from 2 band-passed 3 | % signals, and their corresponding Hilbert transforms (or equivalent). 4 | % Same operation is applied to each column. 5 | % 6 | % Input 7 | % fLow, fHigh: [1] center frequency for low and high band-pass filters 8 | % The value is [0, 1] where 1 denotes the sampling 9 | % frequency (twice the Nyquist freqeuncy) 10 | % condition: fLow < fHigh 11 | % xLow, xHigh: [T x N] independent length-T band-passed time series 12 | % aLow, aHigh: [T x N] amplitude signals 13 | % phiLow, phiHigh: [T x N] phase signals 14 | % 15 | % Output 16 | % CFC: [N x 1] single index which is higher when CFC is (supposedly) stronger 17 | % extra: anything else that needs to be returned 18 | 19 | assert(fLow < fHigh); 20 | T = size(phiLow, 1); 21 | N = size(phiLow, 2); 22 | assert(all(size(xLow) == [T,N])); 23 | assert(all(size(xHigh) == [T,N])); 24 | assert(all(size(aLow) == [T,N])); 25 | assert(all(size(aHigh) == [T,N])); 26 | assert(all(size(phiHigh) == [T,N])); 27 | CFC = nan(N, 1); 28 | extra = []; 29 | 30 | error('I''m just a template'); 31 | 32 | end 33 | -------------------------------------------------------------------------------- /code/filterBank_bal.m: -------------------------------------------------------------------------------- 1 | function [b, a, fCenterList, suggestedBoundaryRemoval, fEdges] = filterBank_bal(fCenterList, TBP, nCycleBegin, fs, designType) 2 | % Design a balanced filter bank with equal temporal and bandwidth features 3 | % 4 | % filterBank_prop has terrible bandwidth for higher frequencies. We try to 5 | % balance it out by taking more cycles for the higher frequencies. 6 | % Number of cycles is linearly scaled as center frequency increases. 7 | % The resulting filters are of same length, and hence the temporal resolution 8 | % is kept constant, and the bandwidth is kept constant. 9 | % 10 | % Input 11 | % fCenterList: list of center frequencies in ascending order 12 | % TBP: [1] time-bandwidth product 13 | % nCycleBegin: number of cycles to include for the FIR filters (default: 3). 14 | % for the lowest center frequency. 15 | % fs: [1] sampling frequency 16 | % designType: 'fir1' [default], 'firls', or 'cheby2' for filter design 17 | % 18 | % Output 19 | % b, a: {nFilter x 1} filter coefficients 20 | % fCenterList: [nFilter x 1] center pass-band frequencies 21 | % suggestedBoundaryRemoval: [1] FIR boundary effect size 22 | % fEdges: [2 x nFilter] bandpass filter specifications 23 | % 24 | % [b, a, fCenterList, suggestedBoundaryRemoval, fEdges] = filterBank_bal(8:2:100, 1, 3, 1000, 'fir1') 25 | 26 | if nargin < 6 27 | designType = 'fir1'; 28 | end 29 | 30 | assert(fCenterList(1) == min(fCenterList), 'need to be ascending order'); 31 | 32 | nCycleList = nCycleBegin * (fCenterList / fCenterList(1)); 33 | fLenHandle = @(f,nCycle) ceil(fs * nCycle / f); 34 | fBWHandle = @(f,nCycle) TBP * f / nCycle / 2; 35 | 36 | switch lower(designType) 37 | case {'fir1'} 38 | fSpecHandle = @(f, df) [f-df/2, f+df/2]; 39 | designBPhandle = @(f, nCycle) fir1(fLenHandle(f, nCycle), fSpecHandle(f, fBWHandle(f, nCycle)) * 2 / fs, 'bandpass'); 40 | isFIR = true; 41 | 42 | case {'firls'} 43 | % Linear phase with least squares over side lobes of the shape: 44 | % 45 | % ------------------ 46 | % 47 | % 48 | % -------?? ??---------- 49 | % 50 | % 0 f-df f-df/2 f f+df/2 f+df fs/2 51 | fSpecHandle = @(f, df) [0, f-df, f-df/2, f+df/2, f+df, fs/2]; 52 | designBPhandle = @(f, nCycle) firls(fLenHandle(f), fSpecHandle(f, fBWHandle(f, nCycle)) * 2 / fs, [0 0 1 1 0 0]); 53 | isFIR = true; 54 | 55 | case {'cheby2'} 56 | fSpecHandle = @(f, df) [f-df/2, f+df/2]; 57 | designBPhandle = @(f, nCycle) cheby2(4, 40, fSpecHandle(f, fBWHandle(f, nCycle)) * 2 / fs); 58 | isFIR = false; 59 | 60 | otherwise 61 | error('Unknown filter design [%s]', designType); 62 | end 63 | 64 | b = cell(numel(fCenterList),1); 65 | a = cell(numel(fCenterList),1); 66 | fEdges = zeros(2, numel(fCenterList)); 67 | fLenList = zeros(numel(fCenterList), 1); 68 | for kCenter = 1:numel(fCenterList) 69 | fCenter = fCenterList(kCenter); 70 | nCycle = nCycleList(kCenter); 71 | fLenList(kCenter) = fLenHandle(fCenter, nCycle); 72 | fEdges(:, kCenter) = fCenter + fBWHandle(fCenter, nCycle) * [-1, 1]/2; 73 | if isFIR 74 | b{kCenter} = designBPhandle(fCenter, nCycle); 75 | a{kCenter} = 1; 76 | else 77 | [b{kCenter}, a{kCenter}] = designBPhandle(fCenter, nCycle); 78 | end 79 | end 80 | 81 | % The longest filter side-effect 82 | % boundary effect of filtering to be removed for all signals 83 | suggestedBoundaryRemoval = max(fLenList); 84 | -------------------------------------------------------------------------------- /code/filterBank_prop.m: -------------------------------------------------------------------------------- 1 | function [b, a, fCenterList, suggestedBoundaryRemoval, fEdges] = filterBank_prop(fMin, fMax, TBP, nCycle, fs, designType, fStep) 2 | % 3 | % Goal of this design is to produce filter bank whose output captures 4 | % modulation of amplitude in narrow-band oscillations. Due to uncertainty 5 | % principle, we can't really have both narrow band and short-time scale. 6 | % This design focuses on short-time scale, and assigns larger bandwidth 7 | % for higher frequencies, namely, bandwidth = (center freq) * c. 8 | % 9 | % For an FIR, we end up with (similarly to wavelet transform): 10 | % 11 | % Frequency bin: (center freq) +/- (TBP/nCycle) * (center freq) 12 | % Time bin: nCycle / (center freq) 13 | % 14 | % Input 15 | % fMin: [1] minimum center frequency 16 | % fMax: [1] maximum center frequency 17 | % TBP: [1] time-bandwidth product 18 | % nCycle: number of cycles to include for the FIR filters (default: 3). 19 | % length of FIR filter is nCycle * (sampling freq)/(center freq) 20 | % fs: [1] sampling frequency 21 | % designType: 'fir1' [default], 'firls', or 'cheby2' for filter design 22 | % fStep: [1] step-size between center frequencies (uniform grid) 23 | % if fStep == 0, proportional grid is automatically generated 24 | % (default) 25 | % 26 | % Output 27 | % b, a: {nFilter x 1} filter coefficients 28 | % fCenterList: [nFilter x 1] center pass-band frequencies 29 | % suggestedBoundaryRemoval: [1] FIR boundary effect size 30 | % fEdges: [2 x nFilter] bandpass filter specifications 31 | % 32 | % [b, a, fCenterList, suggestedBoundaryRemoval, fEdges] = filterBank_prop(8, 100, 1, 3, 1000, 'fir1') 33 | 34 | if nargin < 6 35 | designType = 'fir1'; 36 | end 37 | 38 | if nargin < 7 39 | fStep = 0; 40 | end 41 | 42 | % If we want the bandwidth to touch each other, then 43 | % f1 + f1 * TBP/nCycle/2 = f2 - f2 * TBP/nCycle/2 44 | % therefore, f2 = f1 * freqIncRatio 45 | freqIncRatio = (1 + TBP/nCycle/2) / (1 - TBP/nCycle/2); 46 | 47 | if fStep == 0 48 | % freqIncRatio^n = fMax/fMin 49 | nBands = ceil(log(fMax/fMin) / log(freqIncRatio)); 50 | fCenterList = fMin * (freqIncRatio.^(0:nBands)); 51 | else 52 | fCenterList = fMin:fStep:fMax; 53 | end 54 | 55 | fLenHandle = @(f) ceil(fs * nCycle / f); 56 | fBWHandle = @(f) TBP * f / nCycle / 2; 57 | 58 | switch lower(designType) 59 | case {'fir1'} 60 | fSpecHandle = @(f, df) [f-df/2, f+df/2]; 61 | designBPhandle = @(f) fir1(fLenHandle(f), fSpecHandle(f, fBWHandle(f)) * 2 / fs, 'bandpass'); 62 | isFIR = true; 63 | 64 | case {'firls'} 65 | % Linear phase with least squares over side lobes of the shape: 66 | % 67 | % ------------------ 68 | % 69 | % 70 | % -------?? ??---------- 71 | % 72 | % 0 f-df f-df/2 f f+df/2 f+df fs/2 73 | fSpecHandle = @(f, df) [0, f-df, f-df/2, f+df/2, f+df, fs/2]; 74 | designBPhandle = @(f) firls(fLenHandle(f), fSpecHandle(f, fBWHandle(f)) * 2 / fs, [0 0 1 1 0 0]); 75 | isFIR = true; 76 | 77 | case {'cheby2'} 78 | fSpecHandle = @(f, df) [f-df/2, f+df/2]; 79 | designBPhandle = @(f) cheby2(4, 40, fSpecHandle(f, fBWHandle(f)) * 2 / fs); 80 | isFIR = false; 81 | 82 | otherwise 83 | error('Unknown filter design [%s]', designType); 84 | end 85 | 86 | b = cell(numel(fCenterList),1); 87 | a = cell(numel(fCenterList),1); 88 | fEdges = zeros(2, numel(fCenterList)); 89 | for kCenter = 1:numel(fCenterList) 90 | fCenter = fCenterList(kCenter); 91 | fEdges(:, kCenter) = fCenter + fBWHandle(fCenter) * [-1, 1]/2; 92 | if isFIR 93 | b{kCenter} = designBPhandle(fCenter); 94 | a{kCenter} = 1; 95 | else 96 | [b{kCenter}, a{kCenter}] = designBPhandle(fCenter); 97 | end 98 | end 99 | 100 | % The longest filter side-effect 101 | % boundary effect of filtering to be removed for all signals 102 | suggestedBoundaryRemoval = fLenHandle(fMin); -------------------------------------------------------------------------------- /code/filterBank_uniform.m: -------------------------------------------------------------------------------- 1 | function [b, a, fCenterList, suggestedBoundaryRemoval] = filterBank_uniform(fMin, fMax, fStep, df, fs, designType, nCycle) 2 | % Generate a filter bank comprised of bandpass digital filters. 3 | % This creates equal-bandwidth filters. If FIR with time resolution 4 | % inversely propotional to frequency is used (for Hilber transform), 5 | % uncertainty principle tells us, we need to sacrifice something... 6 | % If IIR is used, then for higher frequencies we will have longer 7 | % time scale which is not appropriate for envelop & phase extraction. 8 | % 9 | % Input 10 | % fMin: [1] minimum center frequency 11 | % fMax: [1] maximum center frequency 12 | % fs: [1] sampling frequency 13 | % fStep: [1] interval between center frequencies 14 | % df: [1] bandwidth around each center frequency 15 | % designType: 'fir1' [default], 'firls', or 'cheby2' for filter design 16 | % nCycle: number of cycles to include for the FIR filters (default: 3). 17 | % length of FIR filter is nCycle * (sampling freq)/(center freq) 18 | % 19 | % [b, a, fCenterList, nTap] = filterBank_simple(fMin, fMax, fStep, df, fs, designType, nCycle) 20 | % e.g. 21 | % [b, a, fCenterList, nTap] = filterBank_simple(8, 100, 5, 4, 1000, 'fir1', 3); 22 | fCenterList = fMin:fStep:fMax; 23 | 24 | if nargin < 6 25 | designType = 'fir1'; 26 | end 27 | 28 | if nargin < 7 29 | nCycle = 3; 30 | end 31 | 32 | switch lower(designType) 33 | case {'fir1'} 34 | designBPhandle = @(f) fir1(ceil(nCycle/f*fs), [f-df/2, f+df/2] * 2 / fs, 'bandpass'); 35 | isFIR = true; 36 | 37 | case {'firls'} 38 | % Linear phase with least squares over side lobes of the shape: 39 | % 40 | % ------------------ 41 | % 42 | % 43 | % -------?? ??---------- 44 | % 45 | % 0 f-df f-df/2 f f+df/2 f+df fs/2 46 | designBPhandle = @(f) firls(ceil(nCycle/f*fs), [0, f-df, f-df/2, f+df/2, f+df, fs/2] * 2 / fs, [0 0 1 1 0 0]); 47 | isFIR = true; 48 | 49 | case {'cheby2'} 50 | %[n,Ws] = cheb2ord(Wp,Ws,Rp,Rs) 51 | designBPhandle = @(f) cheby2(4, 40, [f-df/2, f+df/2] * 2 / fs); 52 | isFIR = false; 53 | 54 | otherwise 55 | error('Unknown filter design [%s]', designType); 56 | end 57 | 58 | b = cell(numel(fCenterList),1); 59 | a = cell(numel(fCenterList),1); 60 | for kCenter = 1:numel(fCenterList) 61 | fCenter = fCenterList(kCenter); 62 | if isFIR 63 | b{kCenter} = designBPhandle(fCenter); 64 | a{kCenter} = 1; 65 | else 66 | [b{kCenter}, a{kCenter}] = designBPhandle(fCenter); 67 | end 68 | end 69 | 70 | % The longest filter side-effect 71 | % boundary effect of filtering to be removed for all signals 72 | suggestedBoundaryRemoval = ceil(nCycle/fMin*fs); -------------------------------------------------------------------------------- /code/generateSurrogateIndices.m: -------------------------------------------------------------------------------- 1 | function rpIdxAll = generateSurrogateIndices(T, minTimeShift, nSurrogate) 2 | % Generate random block permutation as surrogates. 3 | % 4 | % rpIdxAll = generateSurrogateIndices(T, minTimeShift, nSurrogate) 5 | % 6 | % Generate surrogate shuffles from one continuously recorded time series 7 | % of length T. 8 | % 9 | % If you have enough trials it is probably better to shuffle across trials 10 | % given that inter-trial intervals are long enough. 11 | % 12 | % Input 13 | % T: [1] length of time series 14 | % minTimeShift: [1] block size to be shuffled 15 | % nSurrogate: [1] number of surrogates (default: 999) 16 | % 17 | % Output 18 | % rpIdxAll: [floor(T / minTimeShift) x nSurrogate+1)] 19 | % first column is the original order 20 | % the rest of the columns are randomly shuffled 21 | 22 | nChunk = floor(T / minTimeShift); % number of chunks to be shuffled 23 | if nChunk < 6 % 6! = 720, 7! = 5040 24 | error('Not enough data'); 25 | end 26 | 27 | rpIdxAll = zeros(nChunk * minTimeShift, nSurrogate+1); 28 | rpIdxAll(:, 1) = 1:size(rpIdxAll, 1); % first column is the original ordering 29 | for k = 2:nSurrogate+1 30 | rp = randperm(nChunk); 31 | originalIdx = reshape(1:nChunk * minTimeShift, minTimeShift, nChunk); 32 | rpIdx = originalIdx(:, rp); 33 | rpIdxAll(:, k) = rpIdx(:); 34 | end 35 | -------------------------------------------------------------------------------- /code/surrogateStats.m: -------------------------------------------------------------------------------- 1 | function [stat] = surrogateStats(observedSample, surrogateSamples) 2 | % Extract statistics from surrogate distribution 3 | % stat = surrogateStats(observedSample, surrogateSamples) 4 | % 5 | % Input 6 | % observedSample: [1] observed value 7 | % surrogateSamples: [nSurrogate x 1] surrogate samples from null hypothesis 8 | % 9 | % Output 10 | % stat.value: [1] observedSample itself is returned here 11 | % stat.pValue: [1] estimated p-value 12 | % stat.deviation: [1] deviation from the mean 13 | % stat.smean: [1] mean of the surrogates 14 | % stat.sstd: [1] standard deviation of the surrogates 15 | % stat.thresholds: [4x1] estimated quantiles of the surrogate distribution 16 | % at alpha = 0.01, 0.05, 0.1, 0.5 17 | 18 | if nargin < 1 19 | stat = struct('value', 0, 'pValue', 0, 'deviation', 0, 'smean', 0, 'sstd', 0, 'thresholds', []); 20 | return 21 | end 22 | 23 | nSurrogate = numel(surrogateSamples); 24 | pValue = (sum(surrogateSamples > observedSample) + 1)/(nSurrogate+1); % small bias away from 0 25 | m = mean(surrogateSamples); 26 | s = std(surrogateSamples); 27 | dev = (observedSample - m)/s; 28 | 29 | stat.value = observedSample; 30 | stat.pValue = pValue; 31 | stat.deviation = dev; 32 | stat.smean = m; 33 | stat.sstd = s; 34 | stat.thresholds = quantile(surrogateSamples, 1 - [0.01, 0.05, 0.1, 0.5]); 35 | -------------------------------------------------------------------------------- /code/visualizeFilterBank.m: -------------------------------------------------------------------------------- 1 | function fig = visualizeFilterBank(b, a, fCenterList, fs, outDir, prefix, groupSize) 2 | 3 | nFilter = numel(b); 4 | assert(numel(a) == nFilter); 5 | nPoint = 1024; % frequency resolution 6 | warning('off', 'MATLAB:MKDIR:DirectoryExists') 7 | mkdir(outDir) 8 | 9 | if nargin < 7 || isempty(groupSize) 10 | groupSize = nFilter; 11 | end 12 | 13 | for kCenter = 1:nFilter 14 | if mod(kCenter, groupSize) == 1 15 | fig = figure(43896 + ceil(kCenter/groupSize)); 16 | set(fig, 'Units', 'inches'); 17 | set(fig, 'PaperSize', [10 4], 'PaperPosition', [0 0 10 4]); 18 | clf; hold all 19 | end 20 | 21 | if groupSize ~= nFilter 22 | subplot(groupSize, 2, 2*(mod(kCenter-1, groupSize))+1); 23 | zplane(b{kCenter}, a{kCenter}); 24 | stem(b{kCenter}); axis tight 25 | hold all; stem(a{kCenter} * max(b{kCenter})); 26 | if mod(kCenter, groupSize) == 0 || kCenter == nFilter 27 | xlabel('taps'); ylabel('filter coeff'); 28 | end 29 | subplot(groupSize, 2, 2*(mod(kCenter-1, groupSize))+2); 30 | end 31 | 32 | fCenter = fCenterList(kCenter); 33 | hold on; 34 | line(fCenter * [1, 1], [-60, 0], 'Color', 'r'); 35 | 36 | [h, w] = freqz(b{kCenter}, a{kCenter}, nPoint); 37 | plot(w * fs / 2 / pi, 10*log10(abs(h))); 38 | ylim([-60, 0]); 39 | xlim([0, 200]); 40 | 41 | if kCenter == nFilter % mod(kCenter, groupSize) == 0 || kCenter == nFilter 42 | xlabel('Frequency (Hz)'); 43 | ylabel('gain (dB)'); 44 | title('Filter bank'); 45 | drawnow; 46 | saveas(fig, sprintf('%s/filterBank_%s_%d.pdf', outDir, prefix, ... 47 | ceil(kCenter/groupSize))); 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /doc/demo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catniplab/crossFrequencyCoupling/864efa4fac03b8e15b9bc0ca4f0eabebe665f811/doc/demo2.png -------------------------------------------------------------------------------- /doc/mcs_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catniplab/crossFrequencyCoupling/864efa4fac03b8e15b9bc0ca4f0eabebe665f811/doc/mcs_example.png -------------------------------------------------------------------------------- /doc/signal_snippet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catniplab/crossFrequencyCoupling/864efa4fac03b8e15b9bc0ca4f0eabebe665f811/doc/signal_snippet.png --------------------------------------------------------------------------------