function [spectrogram,t,f] = WaveletSpectrogram(lfp,varargin)
%WaveletSpectrogram - Compute LFP wavelet spectrogram
%
% The spectrogram is computed using the wavelet toolbox by C. Torrence and G. Compo.
%
% USAGE
%
% [spectrogram,t,f] = WaveletSpectrogram(lfp,)
%
% lfp unfiltered LFP samples (one channel).
% optional list of property-value pairs (see table below)
%
% =========================================================================
% Properties Values
% -------------------------------------------------------------------------
% 'range' frequency range (in Hz) (default = all)
% 'resolution' desired frequency resolution (sub-octaves per octave, default = 0.05)
% 'step' desired step between successive windows (default = window/2):
% output will be downsampled to this value
% =========================================================================
%
% NOTES
%
% The LFP can be provided either as a time stamped matrix (list of time-voltage
% pairs), or as a voltage vector - in which case the frequency must be specified.
%
% The time displacement between successive short time spectra can be supplied
% either as a 'step' (explicit time difference) or as an 'overlap' (between
% successive time windows).
%
% OUTPUT
%
% spectrogram time-frequency matrix
% t time bins
% f frequency bins
%
% DEPENDENCIES
%
% This function requires the chronux toolbox.
%
% SEE
% See "http://paos.colorado.edu/research/wavelets/"
% See also MTSpectrogram, MTSpectrum, SpectrogramBands, MTCoherence, MTCoherogram, PlotColorMap
%
% Copyright (C) 2018 by Ralitsa Todorova
%
% This program is free software; you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation; either version 3 of the License, or
% (at your option) any later version.
% Check number of parameters
if nargin < 1 || mod(length(varargin),2) ~= 0,
error(['Incorrect number of parameters (type ''help WaveletSpectrogram'' for details).']);
end
% Check parameter sizes
if size(lfp,2) ~= 2,
error(['Parameter ''lfp'' is not a Nx2 matrix (type ''help WaveletSpectrogram'' for details).']);
end
% Defaults
range = [];
pad = 1; % pad the time series with zeroes (recommended)
resolution = 0.05; % this will do (1/resolution) sub-octaves per octave
step = [];
% Parse parameter list
for i = 1:2:length(varargin),
if ~ischar(varargin{i}),
error(['Parameter ' num2str(i+2) ' is not a property (type ''help WaveletSpectrogram'' for details).']);
end
switch(lower(varargin{i})),
case 'range',
range = varargin{i+1};
if ~isdvector(range,'#2','>=0','<'),
error(['Incorrect value for property ''' varargin{i} ''' (type ''help WaveletSpectrogram'' for details).']);
end
case 'step',
step = varargin{i+1};
if ~isdscalar(step,'>0'),
error(['Incorrect value for property ''' varargin{i} ''' (type ''help MTSpectrogram'' for details).']);
end
case 'resolution',
resolution = varargin{i+1};
if ~isdvector(resolution,'#1','>0'),
error(['Incorrect value for property ''' varargin{i} ''' (type ''help WaveletSpectrogram'' for details).']);
end
otherwise,
error(['Unknown property ''' num2str(varargin{i}) ''' (type ''help WaveletSpectrogram'' for details).']);
end
end
t = lfp(:,1);
signal = lfp(:,2);
dt = mode(diff(t));
if isempty(range),
% Provide the maximum computable range possible
s0 = 2*dt; % maximum allowable frequency at least twice the sampling frequency
j1 = ceil(10/resolution); % up to 10 powers-of-two with dj (=resolution) sub-octaves each
else
s0 = 1/range(2);
j1 = ceil(log2(range(2)./range(1))/resolution);
end
% Wavelet transform:
[wave,~,scale,~] = wavelet(signal,dt,pad,resolution,s0,j1,'Morlet');
power = flipud((abs(wave)).^2); % compute wavelet power spectrum
f0 = 1./flipud(scale(:));
% Downsample to desired temporal resolution
if ~isempty(step),
t0 = t;
t = (t0(1):step:t(end))';
power = interp1(t0,power',t)';
end
% Get equally spaced frequencies (rather than logscale)
f = (min(f0):min(diff(f0)):max(f0))';
spectrogram = interp1(f0,power,f);
function [wave,period,scale,coi] = wavelet(Y,dt,pad,dj,s0,J1,mother,param)
if (nargin < 8), param = -1; end
if (nargin < 7), mother = -1; end
if (nargin < 6), J1 = -1; end
if (nargin < 5), s0 = -1; end
if (nargin < 4), dj = -1; end
if (nargin < 3), pad = 0; end
if (nargin < 2)
error('Must input a vector Y and sampling time DT')
end
n1 = length(Y);
if (s0 == -1), s0=2*dt; end
if (dj == -1), dj = 1./4.; end
if (J1 == -1), J1=fix((log(n1*dt/s0)/log(2))/dj); end
if (mother == -1), mother = 'MORLET'; end
%....construct time series to analyze, pad if necessary
x(1:n1) = Y - mean(Y);
if (pad == 1)
base2 = fix(log(n1)/log(2) + 0.4999); % power of 2 nearest to N
x = [x,zeros(1,2^(base2+1)-n1)];
end
n = length(x);
%....construct wavenumber array used in transform [Eqn(5)]
k = [1:fix(n/2)];
k = k.*((2.*pi)/(n*dt));
k = [0., k, -k(fix((n-1)/2):-1:1)];
%....compute FFT of the (padded) time series
f = fft(x); % [Eqn(3)]
%....construct SCALE array & empty PERIOD & WAVE arrays
scale = s0*2.^((0:J1)*dj);
period = scale;
wave = zeros(J1+1,n); % define the wavelet array
wave = wave + i*wave; % make it complex
% loop through all scales and compute transform
for a1 = 1:J1+1
[daughter,fourier_factor,coi,dofmin]=wave_bases(mother,k,scale(a1),param);
wave(a1,:) = ifft(f.*daughter); % wavelet transform[Eqn(4)]
end
period = fourier_factor*scale;
coi = coi*dt*[1E-5,1:((n1+1)/2-1),fliplr((1:(n1/2-1))),1E-5]; % COI [Sec.3g]
wave = wave(:,1:n1); % get rid of padding before returning
function [daughter,fourier_factor,coi,dofmin] = wave_bases(mother,k,scale,param)
mother = upper(mother);
n = length(k);
if (strcmp(mother,'MORLET')) %----------------------------------- Morlet
if (param == -1), param = 6.; end
k0 = param;
expnt = -(scale.*k - k0).^2/2.*(k > 0.);
norm = sqrt(scale*k(2))*(pi^(-0.25))*sqrt(n); % total energy=N [Eqn(7)]
daughter = norm*exp(expnt);
daughter = daughter.*(k > 0.); % Heaviside step function
fourier_factor = (4*pi)/(k0 + sqrt(2 + k0^2)); % Scale-->Fourier [Sec.3h]
coi = fourier_factor/sqrt(2); % Cone-of-influence [Sec.3g]
dofmin = 2; % Degrees of freedom
elseif (strcmp(mother,'PAUL')) %-------------------------------- Paul
if (param == -1), param = 4.; end
m = param;
expnt = -(scale.*k).*(k > 0.);
norm = sqrt(scale*k(2))*(2^m/sqrt(m*prod(2:(2*m-1))))*sqrt(n);
daughter = norm*((scale.*k).^m).*exp(expnt);
daughter = daughter.*(k > 0.); % Heaviside step function
fourier_factor = 4*pi/(2*m+1);
coi = fourier_factor*sqrt(2);
dofmin = 2;
elseif (strcmp(mother,'DOG')) %-------------------------------- DOG
if (param == -1), param = 2.; end
m = param;
expnt = -(scale.*k).^2 ./ 2.0;
norm = sqrt(scale*k(2)/gamma(m+0.5))*sqrt(n);
daughter = -norm*(i^m)*((scale.*k).^m).*exp(expnt);
fourier_factor = 2*pi*sqrt(2./(2*m+1));
coi = fourier_factor/sqrt(2);
dofmin = 1;
else
error('Mother must be one of MORLET,PAUL,DOG')
end