├── README.md ├── LICENSE ├── iterz.m ├── chopper.m ├── blphase.m ├── dbt_causality.m ├── taper.m ├── dbtfocus.m ├── choptf.m ├── spikefilter.m ├── rmbaseline.m ├── dbtpac.m ├── pspect.m ├── pspect2.m ├── dbtcoh.m ├── stft.m ├── dbtDenoise_devel.m ├── dbtDenoise.m ├── dbtbicoh.m ├── dbt.m └── dbtvbw.m /README.md: -------------------------------------------------------------------------------- 1 | # The demodulated band transform (DBT) 2 | 3 | This repository contains DBT spectral analysis scripts for matlab 4 | 5 | For more information please refer to 6 | 7 | Kovach, C. K. & Gander, P. E. The demodulated band transform. Journal of Neuroscience Methods 261, 135-154, doi:http://dx.doi.org/10.1016/j.jneumeth.2015.12.004 (2016). 8 | 9 | A preprint is available here: [arXiv:1510.03113](http://arxiv.org/abs/1510.03113) 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 ckovach 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 | 23 | -------------------------------------------------------------------------------- /iterz.m: -------------------------------------------------------------------------------- 1 | function x = iterz(x,thresh,side) 2 | 3 | % xout = iterz(xin,[threshold=10],[side=0]) 4 | % 5 | % Iterative z-score thresholding. At each iteration a z-score is computed 6 | % on the columns of x, ignoring NaNs, and all values in x above the given z 7 | % threshold are replaced with NaNs until no values in x are above the threshold. 8 | % If side = 0, thresholding is based on the magnitude of the z-score. Thresholding 9 | % is applied to positive values if side = 1 and negative values if side = 10 | % -1. 11 | % 12 | % 13 | 14 | % C Kovach 2017 15 | 16 | if nargin < 2 17 | thresh = 10; 18 | end 19 | if nargin < 3 20 | side = 0; 21 | end 22 | 23 | 24 | if side ==0 25 | zscore = @(x)abs(x-repmat(nanmean(x),size(x,1),1))./repmat(nanstd(x),size(x,1),1); 26 | elseif abs(side)==1; 27 | zscore = @(x)side*(x-repmat(nanmean(x),size(x,1),1))./repmat(nanstd(x),size(x,1),1); 28 | else 29 | error('Side parameter must be 0 (two-sided), 1( high threshold ), or -1 (low threshold)') 30 | end 31 | 32 | z = zscore(x); 33 | 34 | while(any(z(:)>thresh)) 35 | x(z>thresh)=nan; 36 | z = zscore(x); 37 | end 38 | 39 | -------------------------------------------------------------------------------- /chopper.m: -------------------------------------------------------------------------------- 1 | 2 | 3 | function [T,t,Err] = chopper(rg,evtt,fs) 4 | 5 | % [T,t,Err] = chopper(rg,evtt,fs) 6 | % 7 | % Creates a matrix to segment a signal sampled at fs into windows ranging from rg(1) to 8 | % rg(end) around event times specified in evtt. 9 | % 10 | % INPUT VARIABLES: 11 | % 12 | % rg: 2 element array containing start and end times (e.g. -0.5 to 1.0) in seconds 13 | % 14 | % evtt: time stamps (in s) 15 | % 16 | % fs: sampling rate 17 | % 18 | % 19 | % OUTPUT VARIABLE: 20 | % 21 | % T: matrix of indices into a signal sampled at fs. Each column contains sample indices for 22 | % the window surrounding the corresponding event time in evtt, as specified by rg. 23 | % To segment the signal in the vector 'x' into a time-by-event matrix, use X = x(T), 24 | % after correcting for any overlap with the ends of x. One way to implement such a 25 | % correction is 26 | % T(T<1)=1; 27 | % T(T>length(x)) = length(x); 28 | % which clamps values in X falling outside the time range of x to the first and last 29 | % values of x, respectively. 30 | % 31 | % t: vector, timestamps for the rows of T. 32 | % 33 | % Err: Difference between the sampled times and the exact window time. 34 | % 35 | 36 | % C Kovach 2016 37 | 38 | t = (rg(1):1/fs:rg(2))'; 39 | 40 | T = fs*( repmat(t,1,length(evtt)) + repmat(evtt(:)',length(t),1))+1; 41 | 42 | if nargout > 2 43 | Err = (round(T)-T)./fs; %%% Return the rounding error 44 | end 45 | 46 | T = round(T); 47 | -------------------------------------------------------------------------------- /blphase.m: -------------------------------------------------------------------------------- 1 | function [dbout,PH,AMP,dbx]=blphase(x,fs,bw,varargin) 2 | 3 | % 4 | % [dbout,PH,dbx] = blphase(x,fs,bw) 5 | % 6 | % Band limited phase using iterative filtering 7 | % After each normalization the Hilbert transform is reapplied. 8 | % 9 | % dbout is a dbt object containing the normalized signal. H contains 10 | % the reconstructed and amplitude normalized signal for each band at the 11 | % original sampling rate. 12 | % 13 | % 14 | 15 | % C Kovach 2016 16 | 17 | 18 | tol=1e-5; 19 | shoulder = 1; 20 | if isa(x,'dbt') 21 | % fs = x.FSorig; 22 | % bw = x.bandwidth; 23 | % shoulder = x.shoulder; 24 | % x = x.signal; 25 | if x.centerDC 26 | error('DBT input must have centerDC = false') 27 | end 28 | dbx = x; 29 | else 30 | % DBT with hilbert equivalent trsfm 31 | dbx = dbt(x,fs,bw,'centerdc',false,'shoulder',shoulder,varargin{:}); 32 | end 33 | S = dbx.blrep./abs(dbx.blrep); 34 | 35 | niter=0; 36 | del=Inf; 37 | dels=[]; 38 | fprintf('\ndel:%0.6f',0) 39 | while del > tol 40 | 41 | niter=niter+1; 42 | 43 | F = fft(S); 44 | 45 | F(end/(2+2*dbx.fftpad)+1:end,:,:)=0; 46 | 47 | Snew=ifft(F); 48 | 49 | Snew=Snew./abs(Snew); 50 | del = sum(abs(Snew(:)-S(:)).^2)./sum(abs(S(:)).^2); 51 | 52 | S = Snew; 53 | fprintf('\b\b\b\b\b\b\b\b') 54 | fprintf('%0.6f',del) 55 | 56 | % dels(end+1)=del; 57 | end 58 | 59 | dbout=dbx; 60 | dbout.blrep=S; 61 | 62 | 63 | if nargout > 1 64 | PH = zeros(dbx.Norig,length(dbout.frequency)); 65 | AMP = zeros(dbx.Norig,length(dbout.frequency)); 66 | for k = 1:length(dbout.frequency), 67 | ph = dbout.signal(k,1); 68 | ph= ph./abs(ph); 69 | PH(:,k)=ph; 70 | AMP(:,k)= dbx.signal(k)./real(ph); 71 | end 72 | % PH=PH./abs(PH); 73 | 74 | end 75 | -------------------------------------------------------------------------------- /dbt_causality.m: -------------------------------------------------------------------------------- 1 | 2 | function [DSS,SSR,DTF,dbxpast,dbxfut] = dbt_causality(dbx,varargin) 3 | 4 | % This is an experimental application of causal modeling using a crude 5 | % but efficient factorization. 6 | 7 | 8 | 9 | make_plot = true; 10 | 11 | if ~dbx.remodphase 12 | error('dbx.remodphase must be TRUE'); 13 | end 14 | 15 | B = dbx.blrep; 16 | 17 | FB = ifft(B,[],2); 18 | 19 | nfr = length(dbx.frequency); 20 | w = ifftshift((0:nfr-1)-floor(.5*nfr))/nfr; 21 | 22 | Bpast = FB; 23 | Bpast(:,w>=0,:) = 0; 24 | 25 | Bfut = FB; 26 | Bfut(:,w<=0,:) = 0; 27 | 28 | dbxpast = dbx; 29 | dbxpast.blrep = fft(Bpast,[],2); 30 | dbxfut = dbx; 31 | dbxfut.blrep = fft(Bfut,[],2); 32 | 33 | DTF = dbtcoh(dbxpast,dbxfut); 34 | 35 | 36 | for k = 1:length(dbx.frequency); 37 | 38 | Y = squeeze(dbxfut.blrep(:,k,:)); 39 | X = squeeze(dbxpast.blrep(:,k,:)); 40 | ssY = sum(abs(Y).^2); 41 | ssX = sum(abs(X).^2); 42 | 43 | npar = size(X,2); 44 | for kk = 1:npar+1; 45 | getw = (1:npar)~=kk-1; 46 | [glm(k,kk).b,glm(k,kk).dev,glm(k,kk).pval,glm(k,kk).iXX,glm(k,kk).sigma,glm(k,kk).res,glm(k,kk).Yfit] = complexglm(Y,X(:,getw),'intercept',true); 47 | 48 | ssR = diag(ssX)*abs(glm(k,kk).b(1:size(ssX,2),:)).^2*diag(ssY.^-1); 49 | glm(k,kk).ssR =ssR; 50 | glm(k,kk).ssRes = sum(abs(glm(k,kk).res).^2); 51 | 52 | glm(k,kk).dSSR = (glm(k,kk).ssRes-glm(k,1).ssRes)./ssY; 53 | end% if any(ssR(:)>1) 54 | % keyboard 55 | % end 56 | k 57 | end 58 | for k =1:npar 59 | DSS(:,:,k) = cat(1,glm(:,k+1).dSSR); 60 | DSS(:,k,k) = 0; 61 | end 62 | SSR = cat(3,glm(:,1).ssR); 63 | 64 | if make_plot 65 | figure 66 | pln = 1; 67 | yl = minmax(DSS(:)); 68 | for k = 1:npar 69 | for kk = 1:npar 70 | if k~=kk 71 | 72 | subplot(npar,npar,pln) 73 | plot(dbx.frequency,squeeze(DSS(:,kk,k))) 74 | title(sprintf('%i -> %i',k,kk)) 75 | ylim(yl) 76 | end 77 | pln = pln+1; 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /taper.m: -------------------------------------------------------------------------------- 1 | classdef taper 2 | 3 | % The taper class defines some properties of window tapers. 4 | % 5 | %Properties: 6 | % 7 | % generator: Handle defining a function, h, used to generate the taper. 8 | % h is defined over [0 1] and typically has h(0) = 1 and h(1) = 0. 9 | % Default is @(x)1/2*(1+cos(x*pi)). This is not the same as 10 | % the taper function, 11 | % (see the following properties) 12 | % 13 | % summation_order: Require that the taper, f(x), satisfy 14 | % f(x)^p + f(1-x)^p = 1. Default is p = 2. This creates 15 | % a function g(x) = h(x) for x>=0 and g(x) = (1-h(1-abs(x))^p)^1/p for 16 | % x < 0. For p = 0 g(x) = h(x). 17 | % 18 | % symmetric: Require that the taper be symmetric if true. If true, 19 | % then the taper function is 20 | % f(x) = (g(x)+g(-x))/2. 21 | % Default is true. 22 | %Methods: 23 | % 24 | % obj = taper; %Returns taper with default properties 25 | % 26 | % val = obj.make(x). Returns taper values at window points in the 27 | % range [-1 1]. 28 | % 29 | % 30 | 31 | % ----------- SVN REVISION INFO ------------------ 32 | % $URL$ 33 | % $Revision$ 34 | % $Date$ 35 | % $Author$ 36 | % ------------------------------------------------ 37 | 38 | properties 39 | 40 | 41 | generator = @(x)cos(x*pi/2); 42 | 43 | summation_order=2; 44 | 45 | symmetric = false; 46 | end 47 | 48 | methods 49 | 50 | 51 | 52 | function [t,f] = make(me,x) 53 | 54 | h = me.generator; 55 | p = me.summation_order; 56 | if p > 0 57 | % g = @(x) (x>=0).*h(abs(x)) + (x<0).*(1-(h(1-abs(x)).^p)).^(1/p); 58 | g = @(x) (mod(x,2)-1<0 | x==0).*h(mod(x,2)) + (mod(x,2)-1>=0 & x~=0).*(1-h(mod(x,2)-1).^p).^(1/p); 59 | 60 | else 61 | g = h; 62 | end 63 | 64 | if me.symmetric 65 | f = @(x) ((g(x).^p + g(-x).^p)/2).^(1/p); 66 | else 67 | f = g; 68 | end 69 | 70 | t = f(x); 71 | 72 | end 73 | end 74 | end -------------------------------------------------------------------------------- /dbtfocus.m: -------------------------------------------------------------------------------- 1 | 2 | function [blout,w0,time,dbif] = dbtfocus(dbx,focus_factor, upsample_factor) 3 | 4 | % 5 | % [tf,freq,time,IF] = dbtfocus(dbx,focus_factor,upsample_factor) 6 | % 7 | % DBTFOCUS uses information about instantaneous frequency within 8 | % each band to obtain sub-bandwidth frequency resolution for a DBT 9 | % time-frequency decomposition. Upsampled bands are the original TF 10 | % amplitude weighted by a gaussian centered at the instantaneous frequency, 11 | % whose width is dbx.bandwidth./focus_factor. 12 | % 13 | % Input arguments: 14 | % 15 | % dbx: A dbt object. Note that the option 'centerDC', must be true. 16 | % 17 | % focus_factor: Degree of sharpening along the frequency axis. 18 | % 19 | % upsample_factor: how much to upsample along the frequency axis. 20 | % 21 | % Output arguments: 22 | % 23 | % blout: Average band-limited power weighted according to distance from instantaneous frequency. 24 | % 25 | % freq: Frequency axis 26 | % 27 | % time: time axis 28 | % 29 | % IF: Instantaneous frequencies for each band as time series. 30 | % 31 | % 32 | % Example: 33 | % 34 | % 35 | % x = randn(1e4,1) + chirp((1:1e4)'/1e3,10,5,50,'logarithmic') + cos(2*pi*(1:1e4)'/1e3*50); 36 | % dbx = dbt(x,1000,10,'centerDC',false,'upsampleFx',4,'lowpass',100); 37 | % [tf,freq,time,IF] = dbtfocus(dbx,4,2); 38 | % figure, subplot(2,1,1) 39 | % dbx.specgram; 40 | % caxis([-15 15]); 41 | % title('DBT time frequency plot') 42 | % ylim([0 100]) 43 | % ax = axis; 44 | % subplot(2,1,2) 45 | % imagesc(time,freq,20*log10(tf')) 46 | % caxis([-15 15]); 47 | % axis xy 48 | % axis(ax) 49 | % title('DBTFOCUS output') 50 | % hold on, 51 | % plot(time,IF,'k:') 52 | % legend('Instantaneous frequency') 53 | % 54 | % 55 | % See also DBT and BLPHASE 56 | 57 | if nargin < 3 58 | upsample_factor = 5; 59 | end 60 | 61 | wres = dbx.bandwidth./(upsample_factor*(1+dbx.upsampleFx)); 62 | 63 | if nargin < 2 || isempty(focus_factor) 64 | focus_factor = 4; 65 | end 66 | 67 | % Use band-limited phase 68 | use_blphase = true; 69 | 70 | 71 | w = 0:wres:dbx.bandwidth*(1+dbx.shoulder); % demodulated frequencies 72 | w0 = (dbx.bands(1):wres:dbx.bands(end)); 73 | 74 | win = sin((0:length(w)-1)./length(w)*pi); 75 | 76 | %F = ifft(eye(length(dbx.time),length(w))); 77 | arg =@(x)atan2(imag(x),real(x)); 78 | 79 | if use_blphase 80 | % Suppress spectral leakage distortion of phase 81 | dbphn = blphase(dbx); 82 | else 83 | % No bias corretion 84 | dbphn = dbx; 85 | dbphn.blrep = dbx.blrep./abs(dbx.blrep); 86 | end 87 | 88 | blout=zeros([length(dbx.time),length(w0),size(dbx.blrep,3)]); 89 | 90 | 91 | %%% Get instantaneous frequency. 92 | dbif = arg(dbphn.blrep(1:end,:,:).*conj(dbphn.blrep([1 1:end-1],:,:)))/pi*dbx.sampling_rate/2; 93 | 94 | for k = 1:length(dbx.frequency) 95 | 96 | wi = find(w0>=dbx.bands(k,1) & w0 3 109 | dbif = dbif + repmat(ones(size(dbif,1),1)*dbx.bands(:,1)',[1 1 size(dbif,3)]); 110 | end 111 | -------------------------------------------------------------------------------- /choptf.m: -------------------------------------------------------------------------------- 1 | function [CH,tt,T,Err] = choptf(trg,times,db,trref,normalization) 2 | 3 | % [CH,tt] = choptf(trange,evnt_times,db,trref) 4 | % Chop dbt data into epochs and normalize 5 | % 6 | % trange - time epoch relative to... 7 | % evnt_times - event markers 8 | % db - dbt object 9 | % trref - reference range by which to normalize. Normalization is currently 10 | % geometric mean amplitude envelope within the reference period applied to 11 | % each corresponding trial epoch. 12 | % For no normalization leave empty (default) 13 | % see also DBT 14 | 15 | % ----------- SVN REVISION INFO ------------------ 16 | % $URL$ 17 | % $Revision$ 18 | % $Date$ 19 | % $Author$ 20 | % ------------------------------------------------ 21 | 22 | %C. Kovach 2014 23 | 24 | if nargin < 5 || isempty(normalization) 25 | normalization= 'geometric mean'; 26 | %normalization= 'root mean power'; 27 | %normalization= 'mean envelope'; 28 | end 29 | 30 | if nargin < 4 || isempty(trref) 31 | trref = []; 32 | nrm = 1; 33 | end 34 | 35 | adjust_phase = db.remodphase; %Correct phase for rounding error 36 | %if phase information is preserved. 37 | 38 | if db.gpuEnable 39 | gpuarg ={'gpuArray'}; 40 | else 41 | gpuarg ={}; 42 | end 43 | 44 | [T,tt,Err]= chopper(trg,times,db.sampling_rate); 45 | 46 | Err(T<1|T>length(db.time)) = 0; 47 | 48 | T(T<1|T>length(db.time)) = length(db.time)+1; 49 | 50 | 51 | if ~isempty(trref) 52 | 53 | Tref= chopper(trref,times,db.sampling_rate); 54 | Tref(Tref<1|Tref>length(db.time)) = length(db.time)+1; 55 | 56 | else 57 | Tref = []; 58 | end 59 | 60 | if any(T(:)<1|T(:)>length(db.time)) || any(Tref(:)<1|Tref(:)>length(db.time)) 61 | warning('%i time points are out of range of the input vector.\nCorresponding samples will be replaced with ones.',sum(T(:)<1|T(:)>length(db.time))+sum(Tref(:)<1|Tref(:)>length(db.time))) 62 | end 63 | 64 | x = ones(length(db.time)+1,1); 65 | nch = size(db.blrep,3); 66 | CH = zeros([length(tt),length(db.frequency),size(T,2), nch],gpuarg{:}); 67 | for i = 1:nch 68 | for k = 1:length(db.frequency) 69 | 70 | x(1:length(db.time)) = db.blrep(:,k,i); 71 | if ~isempty(trref) 72 | %%% normalizing by mean envelope 73 | nrm = repmat(mean(abs(x(Tref))),length(tt),1); 74 | switch normalization 75 | case 'geometric mean' 76 | tol = 1e-9; 77 | x = x + 0./(abs(x)>tol); %Make values below tolerance nan 78 | nrm = repmat(exp(nanmean(log(abs(x(Tref))))),length(tt),1); 79 | case 'mean envelope' 80 | %%% Trial by trial normalization is biased 81 | % nrm = repmat(mean(abs(x(Tref))),length(tt),1); 82 | nrm = mean(abs(x(Tref(:)))); 83 | case 'root mean power' 84 | %%% Trial by trial normalization is biased 85 | % nrm = repmat(sqrt(nanmean(abs(x(Tref)).^2)),length(tt),1); 86 | nrm = sqrt(nanmean(abs(x(Tref(:))).^2)); 87 | otherwise 88 | error('Unrecognized normalization "%s"',normalization) 89 | end 90 | end 91 | 92 | CH(:,k,:,i) = x(T)./nrm; 93 | end 94 | 95 | end 96 | 97 | 98 | if adjust_phase 99 | % This adjusts phase to compensate for rounding error 100 | adjuster = exp(-1i*2*pi*repmat(permute(Err,[1 3 2]),[1,length(db.frequency),1]).*repmat(db.frequency - (~db.centerDC)*db.bandwidth*(1+db.shoulder)/2,[length(tt) 1 size(T,2)])); 101 | CH = CH.*repmat(adjuster,[1 1 1 size(CH,4)]); 102 | end 103 | 104 | -------------------------------------------------------------------------------- /spikefilter.m: -------------------------------------------------------------------------------- 1 | function [xout,spike] = spikefilter(xin,fs,spike) 2 | 3 | 4 | % Simple script to remove outliers through iterative thresholding. Outliers 5 | % are removed by first applying a z-score transform and rejecting all 6 | % outliers that exceed a given threshold magnitude (defualt is 10), then 7 | % repeating the first step with remaining data points until no outliers 8 | % remain beyond the threshold. Data are windowed by a function that convolves 9 | % a tapered a window given by 10.^(-atten/20*hann(n)), where 'atten' determines the peak 10 | % attenuation, with a train of impulses corresponding to the rejected time points. 11 | % 12 | % Use: [xfilt,out] = spikefilter(x,fs,[options]) 13 | % 14 | % Inputs: 15 | % 16 | % x - A column vector with data 17 | % fs - sampling frequency 18 | % options - A structure with the options. See the documentation below 19 | % for details. 20 | % 21 | % 22 | 23 | % ----------- SVN REVISION INFO ------------------ 24 | % $URL$ 25 | % $Revision$ 26 | % $Date$ 27 | % $Author$ 28 | % ------------------------------------------------ 29 | 30 | if nargin < 3 31 | spike.threshold = 10; % This is the threshold used in detecting spikes. 32 | % Z-score is computed iteratively until 33 | % no points exceed this value. The threshold is set 34 | % high by default because the main purpose here is to avoid 35 | % distortion of kurtosis used in the 36 | % kurtosis-threshold. 37 | spike.smoothwindow = .2;% Apply hanning window of given duration to smooth the spike filter. 38 | 39 | spike.interpolate = false; % If true, interpolates values through a weighted average, 40 | % with a Hann interpolation window twice 41 | % the size of the smoothing window. 42 | % If false, then data are subjected to 43 | % simple windowing, with excluded points 44 | % scaled towards zero. 45 | % If a scalar value is given, then uses a 46 | % Hann window of the given duration. 47 | % A vector is taken directly as the interpolation window. 48 | % If a string, then it uses the matlab 49 | % interp1 function with the specified method 50 | % (eg. 'spline'). 51 | 52 | spike.combine_channels =false; % If true, combine the filter across channels and apply the same filter to all channels. 53 | end 54 | 55 | DB_attenuation = 100; %Peak attenuation for an isolated impulse 56 | 57 | xout = zeros(size(xin)); 58 | if size(xin,2)<2 59 | spike.combine_channels = false; 60 | end 61 | for k = 1:size(xin,2) 62 | x = xin(:,k); 63 | spks = false(size(x)); 64 | newspks = true; 65 | while any(newspks) 66 | z = (x-mean(x(~spks)))/std(x(~spks)); 67 | newspks = abs(z)>spike.threshold &~spks; 68 | spks = newspks | spks; 69 | end 70 | if isscalar(spike.smoothwindow) 71 | win = hanning(ceil(spike.smoothwindow.*fs)); 72 | else 73 | win = spike.smoothwindow; 74 | end 75 | 76 | if isscalar(spike.interpolate) && spike.interpolate 77 | if islogical(spike.interpolate) 78 | %%% Use a default interpolation window with twice the support of the smoothing 79 | %%% window. 80 | interpwin = hanning(round(length(win)*2)); 81 | else 82 | %%% Scalar values are treated as the interpolation window duration 83 | interpwin = hanning(ceil(spike.interpolate.*fs)); 84 | end 85 | elseif ~isscalar(spike.interpolate) 86 | %%% For a non-scalar value, the input is the window. 87 | interpwin = spike.interpolate; 88 | else 89 | interpwin = 0; 90 | end 91 | 92 | 93 | 94 | % spike.filter(:,k) = exp(convn(log(1-spks+eps),win,'same')); 95 | spike.filter(:,k) = 10.^(-DB_attenuation/20*convn(spks,win,'same')); 96 | spike.filter(spike.filter<0,k)=0; 97 | end 98 | 99 | if spike.combine_channels 100 | spike.filter = repmat(prod(spike.filter,2),1,size(xin,2)); 101 | end 102 | 103 | if ~ischar(spike.interpolate) 104 | %interpwin = win; 105 | if ~isequal(interpwin,0) 106 | %%% Smooth x through weighted averaging. 107 | xconv = convn(xin.*spike.filter,interpwin,'same')./(convn(spike.filter,interpwin,'same')+eps); 108 | xinterp = + (1-spike.filter).*xconv; 109 | else 110 | xinterp = 0; 111 | end 112 | 113 | xout = xin.*spike.filter + xinterp; 114 | else 115 | t = (1:size(x,1))'; 116 | xout = interp1(t(spike.filter>.5),xin(spike.filter>.5,:),t,spike.interpolate); 117 | end 118 | 119 | -------------------------------------------------------------------------------- /rmbaseline.m: -------------------------------------------------------------------------------- 1 | 2 | function [out,bl] = rmbaseline(bx,usepts,varargin) 3 | 4 | 5 | % Fits a polynomial to the spectrum and normalizes by the fitted value. 6 | 7 | % C Kovach 2013 8 | % 9 | % ----------- SVN REVISION INFO ------------------ 10 | % $URL$ 11 | % $Revision$ 12 | % $Date$ 13 | % $Author$ 14 | % ------------------------------------------------ 15 | 16 | smoothing_method = 'moving average'; 17 | polyord = 8; 18 | %%% the following apply to moving average and local smoothing 19 | smoothbwn=0; 20 | smoothbw = 10; 21 | smoothwin = 'hann'; 22 | smooth_threshold=3;%Iterative discard points with z-score above this threshold from smoothing. 23 | %%% the following apply to local 24 | local_time_smoothingN = 5; %apply a second smoothing over this many time samples in setting the baseline threshold. 25 | local_time_smoothing_win = 'hann'; 26 | 27 | use_time = true(size(bx.time)); 28 | i = 1; 29 | 30 | while i <= length(varargin) 31 | switch lower(varargin{i}) 32 | 33 | case {'smoothing method'} 34 | smoothing_method = varargin{i+1}; 35 | i = i+1; 36 | case 'polyord' 37 | polyord = varargin{i+1}; 38 | i = i+1; 39 | case {'smoothing bandwidth','smoothbw'} 40 | smoothn = varargin{i+1}; 41 | i = i+1; 42 | case 'smoothing threshold' 43 | smooth_threshold = varargin{i+1}; 44 | i = i+1; 45 | case 'local_time_windowN' 46 | local_time_smoothingN = varargin{i+1}; 47 | i = i+1; 48 | case {'smooth bwn','smoothing bwn'} 49 | smoothbwn = varargin{i+1}; 50 | i = i+1; 51 | case 'use time' 52 | use_time = varargin{i+1}; 53 | i = i+1; 54 | otherwise 55 | if ischar(varargin{i}) 56 | error('Unrecognized keyword %s',varargin{i}); 57 | else 58 | error('Keyword must be a string') 59 | end 60 | end 61 | i = i+1; 62 | end 63 | 64 | if nargin < 2 || isempty(usepts) 65 | usepts = true; 66 | end 67 | % if nargin > 2 && isnumeric(varargin{1}) 68 | % polyord = varargin{1}; 69 | % elseif nargin > 2 70 | % 71 | % % smoothing_method = varargin{1}; 72 | % switch smoothing_method 73 | % case 'moving_average' 74 | % if nargin < 4, 75 | % smoothbw = 10; 76 | % else 77 | % smoothbw = varargin{2}; 78 | % end 79 | % case 'polynomial' 80 | % if nargin >= 4 && ~isempty( varargin{2}) 81 | % polyord = varargin{2}; 82 | % end 83 | % end 84 | % end 85 | % if nargin > 4 86 | % use_time = varargin{3}; 87 | % else 88 | % use_time = true(size(bx.time)); 89 | % end 90 | B = abs(bx.blrep(use_time,:,:)); 91 | switch lower(smoothing_method) 92 | 93 | case 'polynomial' 94 | B = B + 0./B; 95 | mn = exp(nanmean(log(B),1)); 96 | kp = mn~=0 & usepts; p = polyfit(find(kp)./length(mn),log(mn(kp)),polyord); 97 | bl = polyval(p,(1:length(mn))/length(mn)); 98 | BL = repmat(exp(bl),size(bx.blrep,1),1); 99 | case {'moving_average','moving average','local'} 100 | if smoothbwn ==0 101 | smoothbwn = ceil(smoothbw./bx.bandwidth*(bx.upsampleFx+1)); 102 | end 103 | g = window(smoothwin,smoothbwn)'; 104 | g = g./sum(g); 105 | switch lower(smoothing_method) 106 | case {'moving_average','moving average'} 107 | B = B + 0./B; 108 | mn = exp(nanmean(log(B),1)); 109 | kp = mn~=0 & usepts; 110 | 111 | bl = convn(log(mn).*kp,g,'same')./convn(kp,g,'same'); 112 | if smooth_threshold ~=0 && ~isinf(smooth_threshold) 113 | 114 | thr = iterz(bl-log(mn),smooth_threshold); 115 | kp = kp & ~isnan(thr); 116 | bl = convn(log(mn).*kp,g,'same')./convn(kp,g,'same'); 117 | end 118 | BL = repmat(exp(bl),size(bx.blrep,1),1); 119 | case 'local' 120 | B(isnan(B))=0; 121 | kp = B~=0; 122 | if length(usepts)>1 123 | kp = kp& repmat(usepts,size(B,1),1); 124 | end 125 | twin = window(local_time_smoothing_win,local_time_smoothingN); 126 | twin = twin./sum(twin); 127 | MN = convn(log(B),twin(:),'same'); 128 | KP = convn(kp,g,'same'); 129 | 130 | bl = convn(MN.*KP,g,'same')./convn(KP,g,'same'); 131 | if smooth_threshold ~=0 && ~isinf(smooth_threshold) 132 | 133 | thr = iterz(MN'-bl',smooth_threshold)'; 134 | kp = kp & ~isnan(thr); 135 | bl = convn(MN.*kp,g,'same')./convn(kp,g,'same'); 136 | end 137 | BL = zeros(size(bx.blrep)); 138 | BL(use_time,:) = exp(bl); 139 | end 140 | otherwise 141 | error('Unrecognized smoothing method.') 142 | 143 | end 144 | 145 | 146 | out = bx.blrep./BL; 147 | 148 | -------------------------------------------------------------------------------- /dbtpac.m: -------------------------------------------------------------------------------- 1 | function [xypac,dbamp,dbph] = dbtpac(X,Y,fs,varargin) 2 | 3 | % out = dbtpac(x,y,fs,'phasebw',phbw,'ampbw',abw,varargin) 4 | % 5 | % Efficiently computes phase-amplitude coupling, measured as LFP-amplitude 6 | % coherence values, using the dbt transform. 7 | % 8 | % First, band-limited amplitude envelopes are computed from Y with the DBT at 9 | % bandwidth abw (default 40 Hz), then X is downsampled to the amplitude-band 10 | % sampling rate and amplitude-LFP coherence is calculated with DBTCOH at 11 | % bandwidth phasebw (defaults to 50 / T, where T is the duraion 12 | % of the signal). 13 | % 14 | % The output, out, is a struct with phase-amplitude coherence in the field out.PAC. 15 | % out.PAC is a 4-d matrix with the result arranged as channel(phase) x 16 | % channel(amplitude) x amplitude bands x phase bands. 17 | % 18 | % See also DBTCOH, DBT 19 | 20 | 21 | % ----------- SVN REVISION INFO ------------------ 22 | % $URL$ 23 | % $Revision$ 24 | % $Date$ 25 | % $Author$ 26 | % ------------------------------------------------ 27 | 28 | phasebw = []; 29 | ampbw =40; 30 | phaserange = []; 31 | amprange = [0 300]; 32 | 33 | % coherence = false; % compute coherence instead of PAC if true 34 | % smwin = 0; 35 | % anglehist = false; 36 | % partial = false; 37 | % exclude_time = false; 38 | % timerange = []; 39 | % trigger= []; 40 | keep_time = []; 41 | phargs ={}; 42 | get_trf = false; 43 | get_csd = false; 44 | trf = []; 45 | csd =[]; 46 | cohargs = {}; 47 | dbtargs = {}; 48 | do_permtest = false; 49 | aPLV = false; 50 | ampargs = {}; 51 | % if nargin <3 && isa(X,'dbt') 52 | % fs = X.fullFS; 53 | % end 54 | % phasefs=fs; 55 | 56 | i = 1; 57 | while i <= length(varargin) 58 | 59 | switch varargin{i} 60 | 61 | case {'phasebw','phbw'} 62 | phasebw = varargin{i+1}; 63 | i = i+1; 64 | 65 | case 'ampbw' 66 | ampbw = varargin{i+1}; 67 | i = i+1; 68 | % case 'coherence' 69 | % coherence = varargin{i+1}; 70 | % i = i+1; 71 | case 'phase range' 72 | phaserange = varargin{i+1}; 73 | i = i+1; 74 | case 'amp range' 75 | amprange = varargin{i+1}; 76 | i = i+1; 77 | % case 'phasefs' 78 | % phasefs= varargin{i+1}; 79 | % i = i+1; 80 | % case 'smwin' 81 | % smwin= varargin{i+1}; 82 | % i = i+1; 83 | % case 'partial' 84 | % partial= varargin{i+1}; 85 | % i = i+1; 86 | case 'keep time' 87 | keep_time= varargin{i+1}; 88 | i = i+1; 89 | case {'transfer function','trf'} 90 | get_trf= varargin{i+1}; 91 | i = i+1; 92 | case {'csd','cross spectrum'} 93 | get_csd= varargin{i+1}; 94 | i = i+1; 95 | case {'trigger','timerange'} 96 | phargs = [phargs,varargin(i:i+1)]; 97 | i = i+1; 98 | case {'center','subtract mean'} 99 | cohargs = [cohargs,varargin(i:i+1)]; 100 | i = i+1; 101 | case {'dbtargs'} 102 | dbtargs = [dbtargs,varargin{i+1}]; %#ok<*AGROW> 103 | i = i+1; 104 | case 'do_permtest' 105 | do_permtest = varargin{i+1}; 106 | i = i+1; 107 | case {'plv','blplv','blpl','aplv','coh'} 108 | cohargs = [cohargs,{'type',varargin{i}}]; 109 | case {'cohargs'} 110 | cohargs = [cohargs,varargin{i+1}]; 111 | i = i+1; 112 | case {'phargs'} 113 | cohargs = [phargs,varargin{i+1}]; 114 | i = i+1; 115 | case {'ampargs'} 116 | ampargs = [ampargs,varargin{i+1}]; 117 | i = i+1; 118 | otherwise 119 | error('Unrecognized keyword %s',varargin{i}) 120 | end 121 | i = i+1; 122 | end 123 | 124 | 125 | 126 | ampargs = {fs,ampbw,'padding','time','lowpass',min(amprange(2),fs/2),'offset',amprange(1),dbtargs{:},ampargs{:}}; 127 | dbamp = dbt(Y,ampargs{:}); %%% DBT from which band-limited amplitude will be obtained. 128 | ampfs = dbamp.sampling_rate; 129 | 130 | if isempty(phaserange) 131 | phaserange = [0 dbamp.sampling_rate/2]; 132 | end 133 | 134 | if isempty(phasebw) 135 | %%% If no other bandwidth is specified default to a reasonable value 136 | %%% based on the amplitude bandwidth. 137 | phasebw = ampbw/length(dbamp.time)*400; 138 | end 139 | 140 | if isempty(keep_time) 141 | keepT = dbamp.time<=dbamp.Norig./dbamp.fullFS; 142 | else 143 | keepT = resampi(keep_time,fs,ampfs,'linear')>.5; 144 | end 145 | % Downsample data to match the DBT sampling rate for amplitude. 146 | Xrs = resampi(X,fs,ampfs,'fft'); 147 | Xrs(end+1:length(dbamp.time),:,:) =0; 148 | Pperm =[]; 149 | phargs = {phasebw,'padding','time','lowpass',min(phaserange(2),fs/2),'offset',phaserange(1),'keep time',keepT,phargs{:},dbtargs{:}}; %#ok<*CCAT> 150 | fprintf('\nBand: %4i',0) 151 | scalar_trans = @(x)log(x); 152 | for k = 1:length(dbamp.frequency) 153 | fprintf('\b\b\b\b%4i',k) 154 | if get_trf 155 | [PAC(:,:,k,:,:,:),c,phfreq,tt,dbph,trf(:,:,k,:,:)] = dbtcoh(Xrs,scalar_trans(squeeze(abs(dbamp.blrep(:,k,:)))),ampfs,phargs{:},cohargs{:}); 156 | elseif do_permtest 157 | [PAC(:,:,k,:,:,:),c,phfreq,tt,dbph,trf(:,:,k,:,:),Pperm(:,:,k,:,:)] = dbtcoh(Xrs,scalar_trans(squeeze(abs(dbamp.blrep(:,k,:)))),ampfs,phargs{:},cohargs{:}); 158 | else 159 | [PAC(:,:,k,:,:,:),c,phfreq,tt,dbph] = dbtcoh(Xrs,scalar_trans(squeeze(abs(dbamp.blrep(:,k,:)))),ampfs,phargs{:},cohargs{:}); 160 | end 161 | if get_csd 162 | csd(:,:,k,:,:,:) = c; 163 | end 164 | end 165 | 166 | fprintf('\b\b\b\b') 167 | 168 | xypac.PAC = PAC; 169 | % xypac.amp = amp; 170 | % xypac.ph = rmph; 171 | % xypac.PACgram =CMrs; 172 | xypac.ampfreq = dbamp.frequency'; 173 | xypac.phfreq = phfreq; 174 | xypac.fs = fs; 175 | % xypac.cmtime= cmtime; 176 | xypac.args.in = varargin; 177 | xypac.args.dbamp = ampargs; 178 | xypac.args.dbphase = phargs; 179 | xypac.tt = tt; 180 | xypac.trf = trf; 181 | xypac.csd = csd; 182 | xypac.Pperm= Pperm; 183 | -------------------------------------------------------------------------------- /pspect.m: -------------------------------------------------------------------------------- 1 | function [out,pspindices] = pspect(S,varargin) 2 | 3 | %[psp,pspindices] = pspect(S,f,order,[options]) 4 | % 5 | % General function for computing polyspectra of the given order from an 6 | % input time x frequency matrix. 7 | % 8 | % Input arguments: 9 | % S: Matrix of time-frequency coefficients as time x frequency 10 | % f: Frequency labels for the columns of S. 11 | % order: Order of the polyspectrum. 12 | % 13 | % 14 | %Options: Options may either be specified as pairs of keywords and values, 15 | % i.e. pspect(...,'keyword',value,...) or as one or more structs 16 | % with struct.keyword = value. 17 | % Avalailable options are: 18 | % 19 | % lowpass: limit the range of frequencies for each axis to values less- 20 | % than or equal to this. This maybe specified as a scalar or as 21 | % a vector of order-1 length, which applies a separate limit for 22 | % each dimension. 23 | % highpass: limit to frequencies above this value, etc. 24 | % maxfreq: Sum of frequencies across all dimensions must be less-than-or 25 | % -equal to this value. 26 | % full_range: Add negative frequencies if they are not already included 27 | % 28 | % 29 | % Output arguments: 30 | % psp: Struct with the following fields: 31 | % .pspect: Unnormalized polyspectrum as an (order-1)-dimensional 32 | % matrix; 33 | % .fs: Frequuency labels for the dimensions of pspect 34 | % .options Options struct. 35 | % 36 | % pspindices: Struct containing indices into the original data with the 37 | % following fields: 38 | % .findex: index of the frequency for each unqiue term in the 39 | % estimate. 40 | % .conjugate: Terms for which the complex conjugate is taken. 41 | % .reconmat: reconstruct into the same shape and size as psp.pspect. 42 | % 43 | % 44 | % [psp,pspindices] = pspect(dbx,order,[options) 45 | % 46 | % Input may alternatively be a dbt object. 47 | % 48 | % 49 | % See also DBT 50 | 51 | % C. Kovach 2017 52 | 53 | options.lowpass= Inf; 54 | options.maxfreq= Inf; 55 | options.highpass= 0; 56 | options.normalization = 'awplv'; 57 | options.full_range = false; % Add negative frequencies if they are not already included 58 | options.symmetrize = false; 59 | options.round_freq = true; % Round to the nearest frequency band if necessary. 60 | options.tolerance = []; % Rounding tolerance (defaults to min(diff(f))). 61 | options.principal_domain = false; %Only return values in the principal domain. 62 | options.getbias = true; 63 | %options.real_signal=true; 64 | 65 | 66 | if isa(S,'dbt') 67 | f = S.frequency; 68 | S = S.blrep; 69 | order = varargin{1}; 70 | varargin(1)=[]; 71 | else 72 | f = varargin{1}; 73 | order=varargin{2}; 74 | varargin(1:2)=[]; 75 | end 76 | if isempty(options.tolerance) 77 | options.tolerance = min(diff(sort(f))); 78 | end 79 | 80 | 81 | optfld = fieldnames(options); 82 | i = 1; 83 | while i =highpass & abs(f{1})<=lowpass),fs,options.highpass,options.lowpass,'uniformoutput',false); 136 | 137 | 138 | W = fs; 139 | [W{:}] = ndgrid(fs{:}); 140 | % W{order} = -sum(cat(order,W{:}),order); 141 | 142 | principal_domain=true; 143 | for k = 2:length(W) 144 | principal_domain = principal_domain & W{k-1}<=W{k}; 145 | end 146 | 147 | if ~options.symmetrize 148 | WW = cellfun(@(x)x(:),W,'uniformoutput',false); 149 | else 150 | n1 = 1/(order-1); 151 | WW = arrayfun(@(x,a)x{1}(:)-a*W{1}(:),W,n1*(1:order-1>1),'uniformoutput',false); 152 | end 153 | 154 | WW = [WW{:}]; 155 | WW(:,order) = -sum(WW,2); 156 | WW = sort(WW,2); 157 | 158 | % unique combinations only 159 | if options.principal_domain 160 | [wunq] = unique(WW(principal_domain(:),:),'rows'); 161 | else 162 | [wunq] = unique(WW,'rows'); 163 | end 164 | 165 | wunq(any(abs(wunq)>max(abs(f)),2) | abs(wunq(:,order))>options.maxfreq,:)=[]; 166 | [ism,indx] = ismember(WW,wunq,'rows'); 167 | [cpart,cindx] = ismember(sort(-WW(~ism,:),2),wunq,'rows'); 168 | indx(~ism)=cindx; 169 | 170 | [fism,findx] = ismember(round(wunq./options.tolerance),round(f./options.tolerance)); 171 | 172 | [~,findx(~fism)] = ismember(round(-wunq(~fism)./options.tolerance),round(f./options.tolerance)); 173 | 174 | PS = 1; 175 | NORM = 1; 176 | for k = 1:order 177 | 178 | F = S(:,findx(:,k)); 179 | F(:,~fism(:,k)) = conj(F(:,~fism(:,k))); 180 | PS = PS.*F; 181 | 182 | 183 | switch options.normalization 184 | case {'awplv'} 185 | NORM = NORM.*abs(F); 186 | case {'polycoh','coh','coherence','polycoherence','bicoherence'} 187 | options.normalization = 'polycoh'; 188 | if k1 250 | pspindices.findex = resortindex(findx); 251 | pspindices.conjugate = xor(fism,sconj(findx)); 252 | pspindices.reconmat = rmat; 253 | 254 | 255 | pspindices.principal_domain=principal_domain; 256 | 257 | end 258 | 259 | -------------------------------------------------------------------------------- /pspect2.m: -------------------------------------------------------------------------------- 1 | function [out,pspindices] = pspect2(S,varargin) 2 | 3 | %[psp,pspindices] = pspect2({S1,S2,..,Sk},{f1,f2,...,fk},order,[options) 4 | % 5 | % General function for computing polyspectra and cross polyspectra of the 6 | % given order from a cell array of time x frequency matrices. 7 | % 8 | % Input arguments: 9 | % S: 1 x order cell array with matrices of time-frequency 10 | % coefficients as time x frequency. If the length of S is less than 11 | % order, then the last element of S is reduplicated to extend S to 12 | % length order. 13 | % f: 1 x order (or length(S) if it is less than order) cell array of 14 | % vectors with frequency labels for the columns of S. 15 | % 16 | % order: Order of the polyspectrum. 17 | % 18 | % 19 | %Options: Options may either be specified as pairs of keywords and values, 20 | % i.e. pspect(...,'keyword',value,...) or as one or more structs 21 | % with struct.keyword = value. 22 | % Avalailable options are: 23 | % 24 | % lowpass: limit the range of frequencies for each axis to values less- 25 | % than or equal to this. This may be specified as a scalar or as 26 | % a vector of order-1 length, which applies a separate limit for 27 | % each dimension. 28 | % highpass: limit to frequencies above this value, etc. 29 | % maxfreq: Sum of frequencies across all dimensions must be less-than-or 30 | % -equal to this value. 31 | % Output arguments: 32 | % psp: Struct with the following fields: 33 | % .pspect: Unnormalized polyspectrum as an (order-1)-dimensional 34 | % matrix; 35 | % .fs: Frequuency labels for the dimensions of pspect 36 | % .options Options struct. 37 | % 38 | % pspindices: Struct containing indices into the original data with the 39 | % following fields: 40 | % .findex: index of the frequency for each unqiue term in the 41 | % estimate. 42 | % .conjugate: Terms for which the complex conjugate is taken. 43 | % .reconmat: reconstruct into the same shape and size as psp.pspect. 44 | % 45 | % 46 | % [psp,pspindices] = pspect(dbx,order,[options) 47 | % 48 | % Input may alternatively be a 1 x order dbt object array. Note that each 49 | % has to have the same sampling rate, meaning that if bandwidths differ, 50 | % upsampleTx has to be adjusted accordingly. 51 | % 52 | % 53 | % See also DBT, PSPECT 54 | 55 | % C. Kovach 2017 56 | 57 | options.lowpass= Inf; 58 | options.maxfreq= Inf; 59 | options.highpass= 0; 60 | options.normalization = 'awplv'; 61 | options.full_range = false; % Add negative frequencies if they are not already included 62 | options.min_range = false; % Return only one symmetry region 63 | options.symmetrize = false; 64 | options.round_freq = true; % Round to the nearest frequency band if necessary. 65 | options.tolerance = []; % Rounding tolerance (defaults to min(diff(f))). 66 | %options.real_signal=true; 67 | 68 | axes_interchangeable = isnumeric(S) || length(S)==1; 69 | 70 | if isa(S,'dbt') 71 | 72 | order = varargin{1}; 73 | dbx = S; 74 | dbx(end+1:order)=dbx(end); 75 | 76 | fs = {dbx.frequency}; 77 | mint = min(arrayfun(@(d)d.time(end),dbx)); 78 | S={}; 79 | for k = 1:order 80 | S{k} = dbx(k).blrep(dbx(k).time<=mint,:); 81 | end 82 | varargin(1)=[]; 83 | else 84 | f = varargin{1}; 85 | order=varargin{2}; 86 | varargin(1:2)=[]; 87 | if ~iscell(S) 88 | S = repmat({S},1,order); 89 | else 90 | S(end+1:order)=S(end); 91 | end 92 | if ~iscell(f) 93 | fs = repmat({f},1,order-1); 94 | else 95 | fs=f; 96 | fs(end+1:order)=f; 97 | end 98 | 99 | end 100 | 101 | 102 | optfld = fieldnames(options); 103 | i = 1; 104 | while i =highpass & abs(f{1})<=lowpass),fs,options.highpass,options.lowpass,'uniformoutput',false); 139 | 140 | 141 | for k =1:order 142 | Sk=S{k}; 143 | if options.full_range 144 | 145 | sindx = 1:size(Sk,2); 146 | f=fs{k}; 147 | [fs{k},srtf] = unique([f,-f]); 148 | sifull = [sindx,sindx]; 149 | sconj{k} = [false(size(sindx)),true(size(sindx))]; 150 | sconj{k}=sconj{k}(srtf); 151 | 152 | resortindex{k} = sifull(srtf); 153 | Sk = Sk(:,resortindex{k}); 154 | Sk(:,sconj{k}) = conj(Sk(:,sconj{k})); 155 | S{k}=Sk; 156 | else 157 | resortindex{k} = 1:size(Sk,2); %#ok<*AGROW> 158 | sconj{k} = false(size(resortindex{k})); 159 | end 160 | end 161 | 162 | W = fs(1:order-1); 163 | [W{:}] = ndgrid(fs{1:order-1}); 164 | % W{order} = -sum(cat(order,W{:}),order); 165 | 166 | WW = cellfun(@(x)x(:),W,'uniformoutput',false); 167 | WW = [WW{:}]; 168 | 169 | if options.symmetrize 170 | n1 = 1/(order-1); 171 | main_dim = 1; 172 | other_dims = setdiff(1:order-1,main_dim); 173 | WW(:,other_dims) = WW(:,other_dims)-n1*repmat(WW(:,main_dim),1,size(WW,2)-1); 174 | end 175 | 176 | WW(:,order) = -sum(WW,2); 177 | 178 | if axes_interchangeable 179 | WW = sort(WW,2); 180 | end 181 | 182 | % unique combinations only 183 | [wunq] = unique(WW,'rows'); 184 | %wunq = WW(wunqi,:); 185 | mxf = cellfun(@(f)max(abs(f)),fs); 186 | wunq(any(abs(wunq)>repmat(mxf,size(wunq,1),1),2) | abs(wunq(:,order))>options.maxfreq,:)=[]; 187 | [ism,indx] = ismember(WW,wunq,'rows'); 188 | % if options.min_range 189 | % [unqindx,unqi]=unique(indx); 190 | % indx(:)=0; 191 | % indx(unqi)=unqindx; 192 | % ism(:)=false; 193 | % ism(unqi)=true; 194 | % end 195 | 196 | if axes_interchangeable 197 | [~,cindx] = ismember(sort(-WW(~ism,:),2),wunq,'rows'); 198 | indx(~ism)=cindx; 199 | end 200 | 201 | 202 | 203 | PS = 1; 204 | NORM = 1; 205 | for k = 1:order 206 | [fism,findx] = ismember(round(wunq(:,k)./options.tolerance(k)),round(fs{k}./options.tolerance(k))); 207 | 208 | [~,findx(~fism)] = ismember(round(-wunq(~fism,k)./options.tolerance(k)),round(fs{k}./options.tolerance(k))); 209 | 210 | fisms(:,k)=fism; 211 | findxs(:,k)=findx; 212 | 213 | F = S{k}(:,findx); 214 | F(:,~fism) = conj(F(:,~fism)); 215 | PS = PS.*F; 216 | 217 | 218 | switch options.normalization 219 | case {'awplv'} 220 | NORM = NORM.*abs(F); 221 | case {'polycoh','coh','coherence','polycoherence','bicoherence'} 222 | options.normalization = 'polycoh'; 223 | if k1 287 | if axes_interchangeable 288 | pspindices.findex = resortindex{1}(findxs); 289 | pspindices.conjugate = xor(fisms,sconj{1}(findxs)); 290 | else 291 | for k =1:order 292 | pspindices.findex(:,k) = resortindex{k}(findxs(:,k)); 293 | pspindices.conjugate = xor(fisms(:,k),sconj{k}(findxs(:,k))'); 294 | end 295 | end 296 | pspindices.reconmat = rmat; 297 | 298 | end 299 | 300 | -------------------------------------------------------------------------------- /dbtcoh.m: -------------------------------------------------------------------------------- 1 | function [coh,csp,w,tt,dbs,trf,bias,Pperm] =dbtcoh(x,y,varargin) 2 | 3 | 4 | % coh =dbtcoh(x,y,fs,bw) 5 | % Efficiently compute coherences values with dbt transforms. x and y are 6 | % input signals arranged time x channel. fs is sampling rate and bw is the 7 | % bandwidth within which to compute coherence. 8 | % 9 | % If x and y are dbt objects (see DBT), then fs and bw can be ommitted. 10 | % 11 | % [coh,csp,w] =dbtcoh(x,y,fs,bw) 12 | % Returns the cross-spectral matrix and vector of center frequencies for each band, w. 13 | % 14 | % [coh,csp,w,tt] =dbtcoh(x,y,fs,bw,'trigger',trig,'timerange',trange) 15 | % 16 | % Computes event-related coherence by averaging over windows specified by 17 | % trigger and timerange. Trigger is a vector of event times and time range 18 | % is a vector, trange = [startt endt], specificying window onset and end 19 | % relative to events in trig. tt is a vector sample times for the window. 20 | % 21 | % See also DBT 22 | 23 | % ----------- SVN REVISION INFO ------------------ 24 | % $URL$ 25 | % $Revision$ 26 | % $Date$ 27 | % $Author$ 28 | % ------------------------------------------------ 29 | 30 | i = 1; 31 | keep_time=[]; 32 | trigger = []; 33 | timerange=[]; 34 | event_type = []; 35 | tt=0; 36 | rm0lag = false; %remove zero phase lag component through a frequency domain highpass filter 37 | % If rm0lag = true, then subtracts the mean from the cross 38 | % spectrum over frequencies. 39 | % If rm0lag is a real scalar, then rm0lag applies a 40 | % 'highpass' frequency domain filter, removing the 41 | % contribution within a time lag of the given value. 42 | subtract_mean = true; % Subtract the average as with an ordinary correlation. 43 | % This generally should make little difference on 44 | % simple coherence analyses but may be important for 45 | % event-related PAC. 46 | % permtest = nargout>7; % Run a permutation test if true 47 | nperm = 0; % number of permutations 48 | 49 | dbtargs = {'remodphase',subtract_mean}; 50 | 51 | gpuEnable = false; 52 | 53 | % aPLV = false; %Compute amplitude-weighted phase locking rather than coherence. 54 | type = 'coh'; 55 | resolve_time = true; 56 | 57 | if nargin>3 && isnumeric(varargin{2}) && isnumeric(y) 58 | fs = varargin{1}; 59 | bw = varargin{2}; 60 | varargin(1:2) =[]; 61 | elseif nargin > 2 && isscalar(y) && isnumeric(y) 62 | fs = y; 63 | bw = varargin{1}; 64 | varargin(1) = []; 65 | end 66 | 67 | while i <= length(varargin) 68 | 69 | 70 | switch varargin{i} 71 | 72 | case 'keep time' 73 | keep_time = varargin{i+1}; 74 | i = i+1; 75 | case 'trigger' 76 | trigger = varargin{i+1}; 77 | i = i+1; 78 | case 'timerange' 79 | timerange = varargin{i+1}; 80 | i = i+1; 81 | case {'subtract mean','center'} 82 | subtract_mean = varargin{i+1}; 83 | dbtargs = [dbtargs,{'remodphase',subtract_mean}]; %#ok<*AGROW> 84 | i = i+1; 85 | case 'type' 86 | type = varargin{i+1}; 87 | i = i+1; 88 | case 'permtest' 89 | nperm = varargin{i+1}; 90 | i = i+1; 91 | case 'conditions' 92 | event_type = varargin{i+1}; 93 | i = i+1; 94 | case 'resolve time' 95 | resolve_time = varargin{i+1}; 96 | i = i+1; 97 | case 'gpu' 98 | gpuEnable = varargin{i+1}; 99 | i = i+1; 100 | otherwise 101 | dbtargs = [dbtargs,varargin(i:i+1)]; 102 | i=i+1; 103 | end 104 | 105 | i=i+1; 106 | end 107 | 108 | gpuEnable = gpuEnable && gpuDeviceCount >0; 109 | 110 | switch lower(type) 111 | case {'blplv','blpl'} 112 | dbtargs = [dbtargs,{'centerDC',false,'remodphase',false}]; 113 | case {'icplv'} 114 | dbtargs = [dbtargs,{'remodphase',true}]; 115 | 116 | end 117 | 118 | if ~isa(x,'dbt') 119 | 120 | nx = size(x,2); 121 | 122 | else 123 | dbx = x; 124 | nx = size(dbx.blrep,3); 125 | gpuEnable = gpuEnable || dbx.gpuEnable; % Enable if dbt object is already GPU enabled. 126 | 127 | end 128 | 129 | if gpuEnable 130 | dbtargs = [dbtargs,{'gpu',true}]; 131 | gpuarg = {'gpuArray'}; 132 | else 133 | gpuarg = {}; 134 | end 135 | 136 | dby = []; 137 | 138 | if ~isempty(y) && (~isscalar(y) || isa(y,'dbt')) 139 | if ~isa(y,'dbt') 140 | dby = dbt(y,fs,bw,dbtargs{:}); 141 | ny = size(y,2); 142 | else 143 | dby = y; 144 | ny = size(dby.blrep,3); 145 | 146 | end 147 | else 148 | ny = nx; 149 | end 150 | 151 | 152 | if ~isa(x,'dbt') 153 | dbx = dbt(x,fs,bw,dbtargs{:}); 154 | end 155 | 156 | switch lower(type) 157 | case {'blplv','blpl'} 158 | dbx = blphase(dbx); 159 | dby = blphase(dby); 160 | end 161 | 162 | if isempty(keep_time) 163 | % keepT = true(size(dbx.time)); 164 | keepT = dbx.time < dbx.Norig/dbx.fullFS; 165 | 166 | elseif isa(x,'dbt') 167 | keepT = keep_time; 168 | else 169 | keepT = resampi(keep_time,fs,dbx.sampling_rate,'linear')>.5; 170 | end 171 | 172 | w = dbx.frequency; 173 | csp = zeros(nx,ny,length(w),gpuarg{:}); 174 | 175 | if nargout > 4 176 | trf = csp; 177 | end 178 | 179 | if ~isempty(timerange) 180 | [AX,tt] = choptf(timerange,trigger,dbx); 181 | if ~isempty(y) 182 | [AY,tt] = choptf(timerange,trigger,dby); 183 | end 184 | if ~resolve_time 185 | tt = 0; 186 | end 187 | else 188 | unqev =0; 189 | AX = dbx.blrep(keepT,:,:); 190 | AY = dby.blrep(keepT,:,:); 191 | end 192 | 193 | if isempty(event_type) 194 | event_type = zeros(size(tt)); 195 | end 196 | [unqev,~,unqi] = unique(event_type); 197 | 198 | % bias= zeros(nx,ny,length(w),gpuarg{:}); 199 | 200 | coh = zeros([nx,ny,length(w),length(tt),length(unqev),nperm+1],gpuarg{:}); 201 | if nargout > 6 202 | bias = zeros([nx,ny,length(w),length(tt),length(unqev),nperm+1],gpuarg{:}); 203 | end 204 | for permi = 1:nperm+1 205 | if permi>1 206 | nt = size(AX,1); 207 | rp = randperm(nt); 208 | AX = AX(rp,:,:); 209 | end 210 | for i = 1:length(dbx.frequency) 211 | 212 | for t = 1:length(tt) 213 | for k = 1:length(unqev) 214 | if isempty(timerange) 215 | blx = squeeze(AX(:,i,:)); 216 | if isempty(y) 217 | bly = blx; 218 | else 219 | bly = squeeze(AY(:,i,:)); 220 | end 221 | else 222 | if resolve_time 223 | 224 | blx = squeeze(AX(t,i,event_type == unqev(k),:)); 225 | 226 | if isempty(y) 227 | bly = blx; 228 | else 229 | % bly = squeeze(AY(:,i,:)); 230 | bly = squeeze(AY(t,i,event_type == unqev(k),:)); 231 | end 232 | else 233 | ax = AX(:,i,event_type == unqev(k),:); 234 | blx = reshape(ax,[size(ax,1)*size(ax,3), size(ax,4) ]) ; 235 | if isempty(y) 236 | bly = blx; 237 | else 238 | ay = AY(:,i,event_type == unqev(k),:); 239 | bly = reshape(ay,[size(ay,1)*size(ay,3), size(ay,4) 1]) ; 240 | 241 | end 242 | end 243 | 244 | end 245 | 246 | switch lower(type) 247 | case {'blplv','blpl','plv'} 248 | blx = blx./abs(blx); 249 | bly = bly./abs(bly); 250 | case {'icplv'} 251 | blx = itercent(blx./abs(blx)); 252 | bly = itercent(bly./abs(bly)); 253 | 254 | end 255 | csp(:,:,i,t,k) = blx'*bly; 256 | 257 | 258 | 259 | if subtract_mean 260 | csp(:,:,i,t,k) = csp(:,:,i,t,k) - sum(blx)'*mean(bly); 261 | end 262 | 263 | if isempty(y) 264 | coh(:,:,i,t,k,permi) = diag(diag(csp(:,:,i,t,k).^-.5))*csp(:,:,i,t,k)*diag(diag(csp(:,:,i,t,k)).^-.5); 265 | else 266 | switch lower(type) 267 | case {'awplv','aplv'} 268 | coh(:,:,i,t,k,permi) = csp(:,:,i,t,k)./(abs(blx)'*abs(bly)); 269 | % bias(:,:,i,t,k) = sqrt(sum((abs(blx).*abs(bly)).^2)./sum(abs(blx)'.*abs(bly)).^2); 270 | if nargout > 6 271 | bias(:,:,i,t,k,permi) = sqrt((abs(blx))'.^2*(abs(bly)).^2)./(abs(blx)'*abs(bly)).*sqrt(1 + 1/2*dbx.shoulder);%.*sqrt(1 + 1/2*dbx.shoulder); 272 | end 273 | case 'coh' 274 | coh(:,:,i,t,k,permi) = diag(sum(abs(blx).^2).^-.5)*csp(:,:,i,t,k)*diag(sum(abs(bly).^2).^-.5); 275 | if nargout > 6 276 | bias(:,:,i,t,k,permi) = sqrt(abs(blx)'.^2*abs(bly).^2)./(abs(blx)'*abs(bly)).*sqrt(1 + 1/2*dbx.shoulder); 277 | end 278 | case {'plv','blpl','blplv','icplv'} 279 | coh(:,:,i,t,k,permi) = diag(sum(abs(blx).^2).^-.5)*csp(:,:,i,t,k)*diag(sum(abs(bly).^2).^-.5); 280 | otherwise 281 | error('Unrecognized type %s',type) 282 | 283 | end 284 | end 285 | 286 | if nargout > 5 287 | % cblx = blx-repmat(mean(blx),size(blx,1),1); 288 | % cbly = bly-repmat(mean(bly),size(bly,1),1); 289 | % trf(:,:,i,t,k) = diag(sum(abs(blx).^2))\(blx'*bly); 290 | trf(:,:,i,t,k,permi) = (blx'*bly)/diag(sum(abs(bly).^2)); 291 | end 292 | 293 | end 294 | end 295 | end 296 | end 297 | 298 | if nargout > 4 299 | if isequal(x,y) 300 | dbs = dbx; 301 | else 302 | dbs = [dbx,dby]; 303 | end 304 | end 305 | 306 | %%%% 307 | function q = itercent(q) 308 | 309 | N = 10; 310 | 311 | for k = 1:N, 312 | q = q-repmat(mean(q),[size(q,1) 1 1]); 313 | q=q./abs(q); 314 | % Q(:,k) = q; 315 | end 316 | 317 | 318 | -------------------------------------------------------------------------------- /stft.m: -------------------------------------------------------------------------------- 1 | classdef stft 2 | 3 | % STFT signal representation. 4 | % 5 | % Use: 6 | % 7 | % B = stft(X,Fs,TW) 8 | % 9 | % X - Signal as a column vector 10 | % Fs - Sampling frequency 11 | % TW - Time window width 12 | % 13 | % 14 | % B = stft(X,Fs,TW, ['option'], [value]) 15 | % Options: 16 | % 17 | % offset - offset of the first band from 0hz (default = 0) 18 | % padding - 'time': pad signal in the time domain, changing duration (default) 19 | % 'frequency': pad in the frequency domain, changing sampling rate 20 | % shoulder - (0 - 1) degree of overlap between neighboring bands (default = 0) 21 | % 22 | % The overlapping portions of the bands are windowed with a linear taper: 23 | % ____ __________ ________ _________ 24 | % \ / \ / \ / 25 | % . . . X X X . . . 26 | % / \ / \ / \ 27 | % |-----------| |-| 28 | % TW shoulder 29 | % 30 | 31 | 32 | % C Kovach 2013 33 | % 34 | % ----------- SVN REVISION INFO ------------------ 35 | % $URL$ 36 | % $Revision$ 37 | % $Date$ 38 | % $Author$ 39 | % ------------------------------------------------ 40 | 41 | properties 42 | 43 | blrep = []; %%% Band-limited analytic representaiton of the signal (time x frequency band) 44 | timewindow = []; %%% bandwidth parameter 45 | sampling_rate = []; %%% Sampling rate after downsampling 46 | time = []; %%% Time points for the rows in blrep 47 | frequency = []; %%% Center frequencies for the columns in blrep 48 | windows =[]; %%% Time ranges for the columns of blrep 49 | fullN = 0; %%% Length of the signal after padding 50 | fullFS = 0; %%% Sampling frequency of the reconstructed signal 51 | Norig = 0; %%% Original signal length before padding 52 | nyqval = 0; %%% fft value at the nyquist frequency 53 | shoulder = 0; %%% Degree of frequency overlap between neighboring bands (0 - 1) 54 | lowpass = []; %%% Lowpass cutoff 55 | % taperfun =[]; 56 | taper = []; 57 | twtol = 1e-8; % 58 | upsampleTx = 0; 59 | fftpad = 0; 60 | times =[]; 61 | % taper = 'quadratic'; 62 | end 63 | 64 | 65 | methods 66 | 67 | function me = stft(varargin) 68 | 69 | padding = 'time'; 70 | i = 4; 71 | % me.taperfun = @(x)x; 72 | % me.taperfun = @(x)(1-cos(x*pi))/2; % Function that defines tapering in the overlapping regions of the window 73 | me.taper = taper; 74 | 75 | while i < length(varargin) 76 | switch lower(varargin{i}) 77 | 78 | case 'band' 79 | band = varargin{i+1}; 80 | me.offset = band(1); 81 | me.lowpass = band(end); 82 | i = i+1; 83 | case 'padding' 84 | padding = varargin{i+1}; 85 | i = i+1; 86 | case 'shoulder' 87 | me.shoulder = varargin{i+1}; 88 | i = i+1; 89 | case 'lowpass' 90 | me.lowpass = varargin{i+1}; 91 | i = i+1; 92 | case 'taper' %Define a taper function. Default is (1-cos(x))/2 93 | me.taperfun = varargin{i+1}; 94 | i = i+1; 95 | case {'fftpad','upsamplefx'} % Pad by proportion of window size 96 | me.fftpad = varargin{i+1}; 97 | i = i+1; 98 | case 'upsampletx' %Upsample time by proportion + 1 99 | 100 | nx = varargin{i+1}; 101 | 102 | if nx ~= round(nx) 103 | warning('%s will not currently allow signals to be reconstructed with non-integer upsampling. Integer upsampling is recommended.',upper(mfilename)) 104 | end 105 | 106 | me.upsampleFx = nx; 107 | i = i+1; 108 | 109 | 110 | case 'upsample' %Upsample time by proportion with fft padding and frequency by increasing overlap 111 | nx = varargin{i+1}-1; 112 | me.fftpad = nx; 113 | me.upsampleTx = nx; 114 | if nx ~= round(nx) 115 | warning('%s will not currently allow signals to be reconstructed with non-integer upsampling. Integer upsampling is recommended.',upper(mfilename)) 116 | end 117 | i = i+1; 118 | otherwise 119 | error('Unrecognized keyword %s',varargin{i}) 120 | end 121 | i = i+1; 122 | end 123 | 124 | if isempty(varargin) 125 | return 126 | end 127 | 128 | 129 | fullsig = varargin{1}; 130 | fs = varargin{2}; 131 | tw = varargin{3}; 132 | 133 | n = size(fullsig,1); 134 | T = n./fs; % signal duration 135 | % me.decim = decim; 136 | 137 | stepsize = tw/(me.upsampleTx +1); 138 | % [nm,den] = rat(T./stepsize,me.twtol); 139 | 140 | nchan = size(fullsig,2); 141 | 142 | me.Norig = n; 143 | 144 | % timewindows = 0:tw-me.shoulder:T-tw; 145 | 146 | 147 | nstepsize = ceil(stepsize*fs); 148 | stepsize = nstepsize./fs; 149 | newT = ceil(T./stepsize)*stepsize; 150 | newn = newT.*fs; 151 | newtw = stepsize.*(me.upsampleTx+1); 152 | 153 | winN = round(fs*newtw); 154 | 155 | 156 | nsh = round(me.shoulder*tw*fs); 157 | 158 | upratio = me.upsampleTx+1; 159 | stepsize = newtw/upratio; 160 | nstepsize = round(stepsize.*fs); 161 | 162 | 163 | 164 | me.fullN = newn; 165 | %%% Likewise for new sampling frequency 166 | newfs = newn./newT; 167 | me.fullFS = newfs; 168 | 169 | 170 | me.times(:,1) = (0:stepsize:newT - stepsize);%-newfs/newn; 171 | me.times(:,2) = me.times(:,1)+newtw*(1+me.shoulder);%+newfs/newn; 172 | nwin =size(me.times,1); 173 | 174 | %%% Reshaping matrix. This step includes the initial 175 | %%% circular shift. 176 | rsmat = repmat((0:nwin-1)*nstepsize,round(winN*(1+me.shoulder)),1) +... 177 | repmat((1:round(winN*(1+me.shoulder)))',1,nwin);% -nsh; 178 | 179 | fullsig(end+1:max(rsmat(:)),:,:)=0; 180 | %dcindx = find(rsmat==1); 181 | 182 | tp = me.taper.make((0:1:nsh-1)/nsh); 183 | invtaper = me.taper.make(1-(0:1:nsh-1)/nsh); 184 | 185 | 186 | for k = 1:nchan 187 | x = fullsig(:,k); 188 | Xrs(:,:,k) = double(x(rsmat)); 189 | if nsh>0 190 | Xrs(end+(1-nsh:0),1:nwin,k) = diag(sparse(tp))*Xrs(end+(1-nsh:0),1:nwin,k); 191 | 192 | Xrs(1:nsh,1:nwin,k) = diag(sparse(invtaper))*Xrs(1:nsh,1:nwin,k); 193 | end 194 | 195 | end 196 | padN = floor((me.fftpad+1)*winN*(1+me.shoulder)); 197 | me.fftpad = 1/(1+me.shoulder)*padN/winN-1; 198 | 199 | Xrs(end+1:padN,:,:) =0; 200 | Frs = fft(Xrs); 201 | me.blrep = permute(2*Frs(1:end/2,:,:),[2 1 3]); 202 | me.blrep(:,1,:) = Frs(1,:,:); 203 | 204 | me.sampling_rate = nwin/newT; 205 | 206 | 207 | me.timewindow = newtw; 208 | me.windows = [0:newtw:me.lowpass-newtw;(newtw:newtw:newT)]'; 209 | me.time = mean(me.times,2); 210 | w = (0:padN-1)./padN*newfs/2; 211 | me.frequency = w; 212 | 213 | end 214 | 215 | %%%% 216 | 217 | function [data,fs] = signal(me,rowfilter,hilbert) 218 | 219 | %%% Reconstruct the signal from its band-limited analytic representation 220 | if nargin < 2 || isempty(rowfilter) 221 | mult = 1; 222 | elseif islogical(rowfilter) 223 | mult = diag(sparse(rowfilter)); 224 | elseif min(size(rowfilter)) == 1 225 | 226 | mult = diag(sparse( ismember(1:size(me.blrep',1),rowfilter))); 227 | 228 | else 229 | mult = rowfilter; 230 | end 231 | 232 | if nargin < 3 || isempty(hilbert) 233 | hilbert = false; 234 | end 235 | 236 | F = mult*me.blrep'; 237 | F(2*end,:)=0; 238 | n = me.fullN; 239 | Xrs = ifft(F); 240 | Xrs = Xrs(1:end/2,:); 241 | nsh = round(me.shoulder*me.timewindow.*me.fullFS); 242 | 243 | % Taper is normally defined so that h(k).^2 + h(k+bw).^2 = 1 244 | tp = me.taper.make((1:1:nsh)/nsh); 245 | invtaper = me.taper.make(1-(1:1:nsh)/nsh); 246 | 247 | sh = diag(sparse(tp))*Xrs(end-nsh+1:end,1:end-1); 248 | Xrs(1:nsh,2:end) = diag(sparse(invtaper))*Xrs(1:nsh,2:end)+sh; 249 | Xrs(end-nsh+1:end,:) =[]; 250 | 251 | data = Xrs(1:me.Norig); 252 | if ~hilbert 253 | data = real(data); 254 | end 255 | fs = me.fullFS; 256 | 257 | end 258 | function delete(me) 259 | me.blrep = []; 260 | end 261 | 262 | end 263 | end 264 | 265 | -------------------------------------------------------------------------------- /dbtDenoise_devel.m: -------------------------------------------------------------------------------- 1 | function [xdn,F,blsig,spike] = dbtDenoise(x,fs,bandwidth,varargin) 2 | 3 | % Denoise using the demodulated band representation of a signal (see 4 | % DBT). A threshold is computed on the coefficients using a threshold on 5 | % kurtosis. 6 | % 7 | % Usage: 8 | % 9 | % [xdn,filt,dbt] = dbtDenoise(x,Fs,bandwidth,[keyword],[value]) 10 | % 11 | % Inputs: 12 | % 13 | % x - signal 14 | % Fs - sampling frequency 15 | % bandwidth to use in the dbt (default = 0.05) 16 | % 17 | % 18 | % Outputs: 19 | % 20 | % xdn - denoised signal 21 | % filt - filter used on DBT coefficients in denoising 22 | % blsig - DBT of xdn 23 | % 24 | % Keyword options: 25 | % 26 | % 'remove spikes': true - remove spikes at the default threshold (default) 27 | % before applying dbt-based denoising. 28 | % false - no spike removal 29 | % n (scalar) - remove spikes at specified threshold 30 | % Thresholding is applled iteratively to z-scores 31 | % across all remaining time points none remain above the threshold. 32 | % 33 | % 'kthresh' : kurtosis threshold. Coefficients in frequency bands for which 34 | % kurtosis over time falls above this value are 35 | % thresholded at 'zlothreshold' and otherwise at 36 | % 'zhireshold'. This feature was added as 37 | % kurtosis is useful for detecting frequency 38 | % modulated line noise. Default = 10. 39 | % 40 | % 'zhithresh' : Threshold for coefficient removal. (default=6) 41 | % 42 | % 'zlothresh' : Threshold for coefficient removal in frequency 43 | % banbds that fall above 'kthresh'. (default = 3). 44 | % 45 | % See also DBT, SPIKEFILTER 46 | 47 | % C Kovach 2013 48 | % ----------- SVN REVISION INFO ------------------ 49 | % $URL$ 50 | % $Revision$ 51 | % $Date$ 52 | % $Author$ 53 | % ------------------------------------------------ 54 | 55 | if nargin < 3 || isempty(bandwidth) 56 | bandwidth = .05; % This seems to work well for constant line noise. For FM line noise try .25 or so. 57 | end 58 | 59 | kurtosis_threshold = 10; % Apply a lower Z threshold to frequency points that have kurtosis above this value 60 | spike.remove_spikes = true; % Zero out time points that exceed some threshold 61 | spike.threshold = 10; % This is the threshold used in detecting spikes. 62 | % Z-score is computed iteratively until 63 | % no points exceed this value. The threshold is set 64 | % high by default because the main purpose here is to avoid 65 | % distortion of kurtosis used in the 66 | % kurtosis-threshold. 67 | spike.smoothwindow = .2;% Apply hanning window of given duration to smooth the spike filter. 68 | spike.interpolate = false;% 69 | filter_above = 40; 70 | use_stft = false; 71 | zhithresh = 8; 72 | zlothresh = 4; 73 | makeplots = false; 74 | smoothing_method = 'polynomial'; 75 | adjust_threshold = true; % If true this performs an initial denoising run at a higher threshold if an excessive number of frequency bands are rejected (>15 %) 76 | prefilter_threshold = 15; % Percent rejected bands needed to trigger prefiltering at a higher threshold 77 | baseline_polyord = 10; 78 | smbw = 10; 79 | rm_edge_samples = 2; %Remove this many edge samples 80 | i = 1; 81 | while i <= length(varargin) 82 | switch lower(varargin{i}) 83 | 84 | case {'stft'} % use stft instead of bld 85 | use_stft = true; 86 | varargin(i) = []; 87 | %i = i-1; 88 | case {'dbt'} % use stft instead of dbt 89 | use_stft = false; 90 | varargin(i) = []; 91 | %i = i-1; 92 | case {'kurtosis','kthresh'} % Apply a lower filter threshold for frequency values that have kurtosis exceeding this value 93 | kurtosis_threshold = varargin{i+1}; 94 | varargin(i:i+1) = []; 95 | i = i-1; 96 | case {'filter above'} % ONly apply filter above this frequency 97 | 98 | filter_above = varargin{i+1}; 99 | varargin(i:i+1) = []; 100 | i = i-1; 101 | case {'remove spikes'} 102 | 103 | spike.remove_spikes = varargin{i+1}; 104 | if ~islogical(spike.remove_spikes) && spike.remove_spikes~=1 && spike.remove_spikes~=0 105 | spike.threshold = spike.remove_spikes; 106 | end 107 | varargin(i:i+1) = []; 108 | i = i-1; 109 | case {'spike window'} % spike window widht 110 | spike.smoothwindow = varargin{i+1}; 111 | varargin(i:i+1) = []; 112 | i = i-1; 113 | case {'spike opts'} % option structure for spike exclusion (see SPIKEFILTER) 114 | spike = varargin{i+1}; 115 | varargin(i:i+1) = []; 116 | spike.remove_spikes = true; 117 | i = i-1; 118 | case {'zhithresh','high threshold'} % coefficient threshold 119 | zhithresh = varargin{i+1}; 120 | varargin(i:i+1) = []; 121 | i = i-1; 122 | case {'zlothresh','low threshold'} % Apply a lower threshold to frequencies above the kurtosis threshold 123 | zlothresh = varargin{i+1}; 124 | varargin(i:i+1) = []; 125 | i = i-1; 126 | case {'makeplots'} 127 | makeplots = varargin{i+1}; 128 | varargin(i:i+1) = []; 129 | i = i-1; 130 | case {'smoothing method'} 131 | smoothing_method = varargin{i+1}; 132 | varargin(i:i+1) = []; 133 | i = i-1; 134 | 135 | case {'smoothing bandwidth'} 136 | %Bandwidth of moving average for 'moving average' method 137 | smbw = varargin{i+1}; 138 | varargin(i:i+1) = []; 139 | i = i-1; 140 | case {'smoothing polyord'} 141 | baseline_polyord = varargin{i+1}; 142 | varargin(i:i+1) = []; 143 | i = i-1; 144 | case {'adjust threshold'} 145 | adjust_threshold = varargin{i+1}; 146 | if ~islogical(adjust_threshold) 147 | prefilter_threshold = adjust_threshold; 148 | adjust_threshold = true; 149 | end 150 | 151 | varargin(i:i+1) = []; 152 | i = i-1; 153 | case {'remove edge','rm edge'} 154 | %Number of edge samples to remove 155 | rm_edge_samples = double(varargin{i+1}); 156 | varargin(i:i+1) = []; 157 | i = i-1; 158 | otherwise 159 | % error('Unrecognized keyword %s',varargin{i}) 160 | end 161 | i = i+1; 162 | end 163 | 164 | 165 | shoulder = 1; %This is overridden if passed as an argument in varargin 166 | 167 | if spike.remove_spikes 168 | [x,spike] = spikefilter(x,fs,spike); 169 | % spks = false(size(x)); 170 | % newspks = true; 171 | % while any(newspks) 172 | % z = (x-mean(x(~spks)))/std(x(~spks)); 173 | % newspks = abs(z)>spike.threshold &~spks; 174 | % spks = newspks | spks; 175 | % end 176 | % win = hanning(ceil(spike.smoothwindow.*fs)); 177 | % spike.filter = exp(convn(log(1-spks+eps),win,'same')); 178 | % spike.filter(spike.filter<0)=0; 179 | % x = x.*spike.filter; 180 | end 181 | 182 | if ~use_stft 183 | blsig = dbt(x,fs,bandwidth,'padding','time','shoulder',shoulder,'upsample',1,varargin{:}); % Band limited representation of the signal (see dbt) 184 | nsmbw = ceil(smbw./blsig.bandwidth); 185 | else 186 | blsig = stft(x,fs,bandwidth,'shoulder',shoulder,varargin{:}); % Band limited representation of the signal (see dbt) 187 | nsmbw = ceil(smbw.*blsig.timewindow); 188 | end 189 | w = blsig.frequency; 190 | 191 | include_times = blsig.time >= rm_edge_samples./blsig.sampling_rate ... 192 | & blsig.time <= blsig.Norig./blsig.fullFS - rm_edge_samples./blsig.sampling_rate; 193 | 194 | kt = kurtosis(abs(blsig.blrep(include_times,:,:))); 195 | 196 | pcntrej = mean(kt>kurtosis_threshold); 197 | F0=1; 198 | if adjust_threshold && pcntrej > prefilter_threshold/100; 199 | fprintf('\n%0.0f%% bands flagged. Prefiltering with higher rejection threshold.\n ',pcntrej*100) 200 | [x,F0,blsig] = dbtDenoise(x,fs,bandwidth,varargin{:},'low threshold',2*zlothresh,'adjust threshold',true,'kthresh',2*kurtosis_threshold); 201 | kt = kurtosis(abs(blsig.blrep(include_times,:,:))); 202 | 203 | end 204 | 205 | 206 | switch smoothing_method 207 | case 'polynomial' 208 | nsig = rmbaseline(blsig,w>=filter_above & kt=filter_above & ktkurtosis_threshold) = nan; 223 | 224 | while any(abs(z)>zlothresh) 225 | 226 | z(abs(z)>zlothresh) = nan; 227 | 228 | z = nzsc(z); 229 | 230 | end 231 | 232 | z = (mn-mean(mn(~isnan(z))))./std(mn(~isnan(z))); 233 | ln = z>zlothresh; %%% Threshold the adjusted z 234 | 235 | P = abs(nsig); 236 | P(:,wzhithresh) 242 | P = P + 0./(Z<=zhithresh); 243 | Z = (abs(nsig)-mean(abs(nsig(~isnan(P)))))./std(abs(nsig(~isnan(P)))); 244 | end 245 | % Exclude frequencies below 40 246 | Z(:,w zlothresh | Z >zhithresh ; 249 | 250 | 251 | % % Smooth edges a little to reduce time-domain artifacts 252 | % g = gausswin(ceil(.5./blsig.sampling_rate)); 253 | % g = g./max(g); 254 | % LN = convn(LN,g./sum(g),'same'); 255 | 256 | F = (1-LN).*F0; 257 | 258 | % if makeplots 259 | % [orig,bl] = rmbaseline(blsig,w>=filter_above & kt15 %) 130 | prefilter_threshold = 10; % Percent rejected bands needed to trigger prefiltering at a higher threshold 131 | baseline_polyord = 10; 132 | smbw = 10; %%% bandwidth for the baseline smoothing 133 | rm_edge_samples = 2; %Remove this many edge samples 134 | i = 1; 135 | 136 | argin = varargin; 137 | 138 | while i <= length(varargin) 139 | switch lower(varargin{i}) 140 | % 141 | % case {'dbt'} % use stft instead of dbt 142 | % use_stft = false; 143 | % varargin(i) = []; 144 | % %i = i-1; 145 | case {'kurtosis','kthresh'} % Apply a lower filter threshold for frequency values that have kurtosis exceeding this value 146 | kurtosis_threshold = varargin{i+1}; 147 | varargin(i:i+1) = []; 148 | i = i-1; 149 | case {'filter above'} % ONly apply filter above this frequency 150 | 151 | filter_above = varargin{i+1}; 152 | varargin(i:i+1) = []; 153 | i = i-1; 154 | case {'remove spikes'} 155 | 156 | spike.remove_spikes = varargin{i+1}; 157 | if ~islogical(spike.remove_spikes) && spike.remove_spikes~=1 && spike.remove_spikes~=0 158 | spike.threshold = spike.remove_spikes; 159 | end 160 | varargin(i:i+1) = []; 161 | i = i-1; 162 | 163 | case {'keep spikes'} % If the 'remove spikes' and 'keep spikes' options are both true, then 164 | % spikes are removed for the purpose of 165 | % estimating noise, but retained in the 166 | % denoised signal. 167 | spike.keep_spikes = varargin{i+1}; 168 | varargin(i:i+1) = []; 169 | i = i-1; 170 | 171 | case {'spike window'} % spike window widht 172 | spike.smoothwindow = varargin{i+1}; 173 | varargin(i:i+1) = []; 174 | i = i-1; 175 | case {'spike opts'} % option structure for spike exclusion (see SPIKEFILTER) 176 | spike = varargin{i+1}; 177 | varargin(i:i+1) = []; 178 | if ~isfield(spike,'remove_spikes') 179 | spike.remove_spikes = true; 180 | end 181 | i = i-1; 182 | case {'zhithresh','high threshold'} % coefficient threshold 183 | zhithresh = varargin{i+1}; 184 | varargin(i:i+1) = []; 185 | i = i-1; 186 | case {'zlothresh','low threshold'} % Apply a lower threshold to frequencies above the kurtosis threshold 187 | zlothresh = varargin{i+1}; 188 | varargin(i:i+1) = []; 189 | i = i-1; 190 | case {'flagthresh','flag threshold'} % Threshold baseline-corrected z score at which to identify bands as contaminated. 191 | flagthresh = varargin{i+1}; 192 | varargin(i:i+1) = []; 193 | i = i-1; 194 | case {'makeplots','plot','make plot'} 195 | makeplots = varargin{i+1}; 196 | varargin(i:i+1) = []; 197 | i = i-1; 198 | case {'smoothing method'} 199 | smoothing_method = varargin{i+1}; 200 | varargin(i:i+1) = []; 201 | i = i-1; 202 | 203 | case {'smoothing bandwidth'} 204 | %Bandwidth of moving average for 'moving average' method 205 | smbw = varargin{i+1}; 206 | varargin(i:i+1) = []; 207 | i = i-1; 208 | case {'smoothing polyord'} 209 | baseline_polyord = varargin{i+1}; 210 | varargin(i:i+1) = []; 211 | i = i-1; 212 | case {'adjust threshold'} 213 | adjust_threshold = varargin{i+1}; 214 | if ~islogical(adjust_threshold) 215 | prefilter_threshold = adjust_threshold; 216 | adjust_threshold = true; 217 | end 218 | 219 | varargin(i:i+1) = []; 220 | i = i-1; 221 | case {'remove edge','rm edge'} 222 | %Number of edge samples to remove 223 | rm_edge_samples = double(varargin{i+1}); 224 | varargin(i:i+1) = []; 225 | i = i-1; 226 | otherwise 227 | % error('Unrecognized keyword %s',varargin{i}) 228 | end 229 | i = i+1; 230 | end 231 | 232 | 233 | shoulder = 1; %This is overridden if passed as an argument in varargin 234 | 235 | if spike.remove_spikes 236 | if spike.keep_spikes 237 | xorig = x; 238 | end 239 | [x,spike] = spikefilter(x,fs,spike); 240 | end 241 | 242 | if ~use_stft 243 | blsig = dbt(x,fs,bandwidth,'padding','time','shoulder',shoulder,'upsample',1,varargin{:}); % Band limited representation of the signal (see dbt) 244 | nsmbw = ceil(smbw./blsig.bandwidth); 245 | else 246 | blsig = stft(x,fs,bandwidth,'shoulder',shoulder,varargin{:}); % Band limited representation of the signal (see dbt) 247 | nsmbw = ceil(smbw.*blsig.timewindow); 248 | end 249 | w = blsig.frequency; 250 | 251 | include_times = blsig.time >= rm_edge_samples./blsig.sampling_rate ... 252 | & blsig.time <= blsig.Norig./blsig.fullFS - rm_edge_samples./blsig.sampling_rate; 253 | 254 | kt = kurtosis(abs(blsig.blrep(include_times,:,:))); 255 | 256 | pcntrej = mean(kt>kurtosis_threshold); 257 | F0=1; 258 | if adjust_threshold && pcntrej > prefilter_threshold/100; 259 | fprintf('\n%0.0f%% bands flagged. Prefiltering with higher rejection threshold.\n ',pcntrej*100) 260 | [x,F0,blsig] = dbtDenoise(x,fs,bandwidth,argin{:},'low threshold',2*zlothresh,'flag threshold',2*flagthresh,'adjust threshold',true,'kthresh',2*kurtosis_threshold,'spike opts',spike); 261 | kt = kurtosis(abs(blsig.blrep(include_times,:,:))); 262 | end 263 | 264 | 265 | switch smoothing_method 266 | case 'polynomial' 267 | nsig = rmbaseline(blsig,w>=filter_above & ktkurtosis_threshold) = nan; 284 | 285 | while any(abs(z)>flagthresh) 286 | 287 | z(abs(z)>flagthresh) = nan; 288 | 289 | z = nzsc(z); 290 | 291 | end 292 | 293 | z = (mn-mean(mn(~isnan(z))))./std(mn(~isnan(z))); 294 | ln = z>flagthresh; %%% Threshold the adjusted z 295 | 296 | P = abs(nsig); 297 | P(:,w zlothresh | Z >zhithresh ; 307 | 308 | 309 | 310 | F = (1-LN).*F0; 311 | 312 | 313 | %%% Apply the filter 314 | blsig.blrep = blsig.blrep.*F; 315 | 316 | xdn = blsig.signal; 317 | 318 | xdn = xdn(1:length(x)); 319 | if spike.remove_spikes && spike.keep_spikes 320 | noise = x-xdn; 321 | xdn = xorig-noise; 322 | end 323 | 324 | if makeplots 325 | % dnnsig = blsig.blrep; 326 | pl = plot(blsig.frequency,100*(1-mean(F))'); 327 | if ~isequal(F0,1) 328 | hold on 329 | pl(2) = plot(blsig.frequency,100*(1-mean(F0))'); 330 | set(pl(2),'color','b'); 331 | hold off 332 | 333 | legend({'Final','First pass'}) 334 | end 335 | 336 | set(pl(1),'color','r'); 337 | xlabel('Freq (hz)') 338 | ylabel('% Discarded'); 339 | grid on 340 | xlim([blsig.frequency(1) blsig.frequency(end)]) 341 | end 342 | 343 | 344 | -------------------------------------------------------------------------------- /dbtbicoh.m: -------------------------------------------------------------------------------- 1 | function [out,dbx,indices] = dbtbicoh(x1,x2,varargin) 2 | 3 | % 4 | % [out,dbx] = dbtbicoh(x1,x2,fs,bw,varargin) 5 | % 6 | % Computes bicoherence with DBT. 7 | % 8 | % This script is under development and is not stable. Do not use it unless 9 | % you know what you're doing. 10 | 11 | % ----------- SVN REVISION INFO ------------------ 12 | % $URL$ 13 | % $Revision$ 14 | % $Date$ 15 | % $Author$ 16 | % ------------------------------------------------ 17 | 18 | % C. Kovach 2016 19 | 20 | warning('This script is under development and is not stable. Do not use it unless you know what you''re doing') 21 | 22 | 23 | dbx1 = []; 24 | dbx2 = []; 25 | if isa(x1,'dbt') 26 | if isscalar(x1) 27 | dbx1 = x1; 28 | bw1 = dbx1.bandwidth; 29 | fs = dbx1.fullFS; 30 | else 31 | dbx1=x1(1); 32 | dbx2 =x1(2); 33 | end 34 | end 35 | 36 | if isscalar(x2) 37 | if isa(x2,'dbt') 38 | dbx2 = x2; 39 | bw2 = dbx2.bandwidth; 40 | elseif isnumeric(x2) 41 | fs = x2; 42 | bw1=varargin{1}; 43 | varargin(1)=[]; 44 | x2 = x1; 45 | end 46 | elseif isnumeric(x2) 47 | fs = varargin{1}; 48 | bw1 = varargin{2}; 49 | varargin(1:2)=[]; 50 | end 51 | 52 | 53 | 54 | opts = struct(... 55 | 'bw1',bw1,... 56 | 'bw2x',1,... 57 | 'symmetric',false,... 58 | 'upsampfx',0,... 59 | 'rotation','ww',... 60 | 'maxfreq',Inf,... 61 | 'type','bbb',... 62 | 'do_decomp',false,... 63 | 'decomp','svd',... 64 | 'tdopts',{{}},... 65 | 'tdrank',Inf,... 66 | 'w1lim',Inf,... 67 | 'w2lim',Inf,... 68 | 'loopchan',true,... 69 | 'normalization','awplvbc',... 70 | 'wrap',false); 71 | 72 | if length(varargin)> 0 && isstruct(varargin{1}) 73 | newopts = varargin{1}; 74 | fn = fieldnames(newopts); 75 | for k = 1:length(fn) 76 | opts.(fn{k}) = newopts.(fn{k}); 77 | end 78 | varargin(1) = []; 79 | end 80 | 81 | 82 | i = 1; 83 | while i < length(varargin) 84 | switch lower(varargin{i}) 85 | 86 | case {'type'} 87 | opts.type = varargin{i+1}; 88 | if strcmpi(opts.type,'bbb')||strcmpi(opts.type,'nnn') 89 | opts.bw2x = 1; 90 | end 91 | i = i+1; 92 | case {'symmetric'} 93 | opts.symmetric = varargin{i+1}; 94 | i = i+1; 95 | case {'w1lim'} % Upper limit on frequency 1 96 | opts.w1lim= varargin{i+1}; 97 | i = i+1; 98 | case {'w2lim'} 99 | opts.w2lim= varargin{i+1}; 100 | i = i+1; 101 | case {'maxfreq','lowpass'} 102 | opts.maxfreq = varargin{i+1}; 103 | i = i+1; 104 | case {'upsampfx'} 105 | opts.upsampfx = varargin{i+1}; 106 | i = i+1; 107 | case {'bw2x'} % bw2 relative to bw when bandwidths differ 108 | opts.bw2x = varargin{i+1}; 109 | opts.bw2=opts.bw1*opts.bw2x; 110 | i = i+1; 111 | case {'bw2'} 112 | opts.bw2=varargin{i+1}; 113 | opts.bw2x = opts.bw2/opts.bw1; 114 | i = i+1; 115 | case {'bw1'} 116 | opts.bw1 = varargin{i+1}; 117 | opts.bw2x=opts.bw2/opts.bw1; 118 | i = i+1; 119 | 120 | case {'svd','cp_als','cp_fastals','tucker_als'} % tensor or svd decomposition 121 | opts.do_decomp = varargin{i+1}; 122 | opts.decomp = varargin{i}; 123 | i = i+1; 124 | 125 | case {'decomp'} % tensor decomposition specified 126 | opts.decomp = varargin{i+1}; 127 | opts.do_decomp = ischar(opts.decomp) && ~isempty(opts.decomp) && ~strcmpi(opts.decomp,'none'); 128 | i = i+1; 129 | case {'tdrank','npc','ncomp','pckeep'} % rank of decomposition (number of components to keep for svd) 130 | opts.tdrank = varargin{i+1}; 131 | i = i+1; 132 | case {'loopchan'} % copmute cross-bicoherece in loop rather than vectorized (for the sake of memory) 133 | opts.loopchan = varargin{i+1}; 134 | i = i+1; 135 | case {'wrap'} % wrap the spectrum so that values are sampled modulo nyquist. 136 | opts.wrap = varargin{i+1}; 137 | i = i+1; 138 | case {'normalization'} % copmute cross-bicoherece in loop rather than vectorized (for the sake of memory) 139 | opts.normalization = varargin{i+1}; 140 | i = i+1; 141 | 142 | otherwise 143 | error('Unrecognized keyword %s',varargin{i}) 144 | end 145 | i=i+1; 146 | end 147 | 148 | opts.bw2 = opts.bw2x*opts.bw1; 149 | lpf = min(fs/2,opts.maxfreq*2); 150 | nch1 = size(x1,2); 151 | nch2 = size(x2,2); 152 | if opts.loopchan 153 | 154 | nloop = nch2; 155 | nch2 = 1; 156 | else 157 | nloop = 1; 158 | % nch2 = nch; 159 | end 160 | 161 | if max(nch1,nch2) > 1 && ~strcmpi(opts.decomp,'svd') && ~strcmpi(opts.decomp,'none'); 162 | q = which(opts.decomp); 163 | 164 | while isempty(q) 165 | warning('Unable to find the decomposition method %s',opts.decomp) 166 | switch opts.decomp 167 | case {'cp_als','tucker_als'} 168 | fprintf('\nThis method requires the tensor toolbox from here:\n') 169 | fprintf('http://www.sandia.gov/~tgkolda/TensorToolbox/index-2.6.html\n') 170 | case {'cp_fastals'} 171 | fprintf('\nThis method requires the tensor toolbox from here:\n') 172 | fprintf('\thttp://www.sandia.gov/~tgkolda/TensorToolbox/index-2.6.html\n') 173 | fprintf('and also the extension here: \n') 174 | fprintf('\thttp://www.bsp.brain.riken.jp/~phan/#tensorbox\n') 175 | end 176 | fprintf('Do any necessary installation and add the folder to the path'); 177 | fn = uigetdir(sprintf('Find the directory containting %s.m',opts.decomp)); 178 | if isnumeric(fn) 179 | fprintf('Aborting.') 180 | return 181 | end 182 | addpath(fn) 183 | q = which(opts.decomp); 184 | end 185 | end 186 | 187 | switch lower(opts.type) 188 | case {'single','bbb','nnn','nnb','nbb','bnb','mi'} 189 | %% The standard approach 190 | 191 | 192 | 193 | switch lower(opts.type) 194 | case {'nnb','nbb','bnb','mi'} 195 | upsamptx = opts.bw2/opts.bw1-1; 196 | % upsamptx = 0; 197 | upsampf2 = (opts.upsampfx+1)*(upsamptx+1)-1; 198 | 199 | if isscalar(x2) 200 | x2 = x1; 201 | end 202 | dbx2 = dbt(x2,fs,opts.bw2,'remodphase',false,'upsampleFx',upsampf2,'lowpass',lpf,'gpu',false); 203 | % dbx2 = dbt(x,fs,bw2,'remodphase',false,'upsampleFx',opts.upsampfx); 204 | dbx1 = dbt(x1,fs,opts.bw1,'upsampleFx',opts.upsampfx,'remodphase',false,'upsampleTx',upsamptx,'lowpass',lpf,'gpu',false); 205 | 206 | otherwise 207 | 208 | upsamptx = 1; 209 | if isa(x1,'dbt') 210 | dbx1 = x1; 211 | else 212 | dbx1 = dbt(x1,fs,opts.bw1,'upsampleFx',opts.upsampfx,'remodphase',false,'upsampleTx',upsamptx*opts.bw2x-1,'lowpass',lpf,'gpu',false); 213 | end 214 | if isscalar(x2) 215 | dbx2 = dbx1; 216 | else 217 | dbx2 = dbt(x2,fs,opts.bw2,'upsampleFx',opts.upsampfx,'remodphase',false,'upsampleTx',upsamptx-1,'lowpass',lpf,'gpu',false); 218 | end 219 | end 220 | %tol = dbx1.fullFS./dbx1.fullN/2; 221 | 222 | % fstep1 = diff(dbx1.frequency(1:2)); 223 | fstep2 = diff(dbx2.frequency(1:2)); 224 | fstep3 = fstep2; 225 | keept1 = dbx1.time <=dbx2.time(end) ; 226 | keept2 = dbx2.time <=dbx1.time(end);%& abs(mod(dbx2.time,1./dbx1.sampling_rate))=-1e-9 & dbx1.frequency<=opts.w1lim*2; %& abs(mod(dbx1.frequency,fstep2))=-1e-9 & dbx2.frequency<=opts.w2lim; 234 | % Frequency bands needed in computing the bispecturm 235 | keepf1=dbx1.frequency>=-1e-9; 236 | keepf2=dbx2.frequency>=-1e-9; 237 | 238 | w1 = dbx1.frequency(getf); 239 | w2 = dbx2.frequency(getf2); 240 | if ~opts.symmetric 241 | w1keep = (1:sum(getf)/2); 242 | w1 = w1(w1keep); 243 | 244 | else 245 | w1keep = (1:sum(getf)); 246 | end 247 | switch opts.rotation 248 | case 'ww' 249 | 250 | %[W1,W2] = ndgrid(1:sum(getf),1:sum(getf2)); 251 | if max(nch1,nch2) > 1 252 | [W1,W2,WCh1,WCh2] = ndgrid(w1keep,1:opts.bw2x:sum(getf2),1:nch1,1:nch2); 253 | else 254 | [W1,W2] = ndgrid(w1keep,1:opts.bw2x:sum(getf2)); 255 | WCh1 = 1; 256 | WCh2 = 1; 257 | end 258 | F3 = w1(W1)+w2(W2)-w2(1); 259 | 260 | w2 = w2(1:opts.bw2x:sum(getf2)); 261 | % case 'vv' 262 | % [V1,V2] = ndgrid(1:sum(getf),1:sum(getf2)); 263 | % W1=(V1-V2)+1; 264 | % W2=(V1+V2)-1; 265 | % W3 = 2*V1-1; 266 | 267 | end 268 | % if opts.wrap 269 | % F3 = mod(F3+dbx1.fullFS/2,dbx1.fullFS)-dbx1.fullFS/2; 270 | % end 271 | % W3 = (round(abs(F3)./fstep3)+1).*sign(F3); 272 | W3 = round(F3./fstep3)+1; 273 | % inds = W3<=length(dbx.frequency); 274 | if ~opts.wrap 275 | inds = W1>0 & W2 <=sum(getf2)& abs(W3)<=sum(keepf2); 276 | else 277 | inds= W1>0 & W2 <=sum(getf2); 278 | end 279 | if ~opts.symmetric 280 | inds = inds & W1 <=W2; 281 | end 282 | blrep = dbx1.blrep(keept1,keepf1,:); 283 | if nch1 > 1 284 | blrep = reshape(blrep,size(blrep,1),size(blrep,2)*nch1); 285 | end 286 | % bspect = nan([size(W1,1) size(W1,2) nch^2]); 287 | bspect = nan(size(W1)); 288 | 289 | Ich1 =( WCh1 - 1)*sum(keepf1); 290 | Ich2 =( WCh2 - 1)*sum(keepf2); 291 | I1=W1 + Ich1; 292 | I2=W2 + Ich2; 293 | I3=W3 + Ich2; 294 | if strcmpi(opts.type,'bnb') 295 | I1x = I2; 296 | I2 = I1; 297 | I1 = I1x; 298 | blrep2 = blrep; 299 | elseif strcmpi(opts.type,'ppc') 300 | I3 = I2; 301 | W3 = W2; 302 | end 303 | 304 | % I1=V1;I2=V2;I3=V3; 305 | % if strcmpi(opts.type,'nbb') ||strcmpi(opts.type,'bbb') || strcmpi(opts.type,'nnn') 306 | for chi = 1:nloop 307 | if strcmpi(opts.type,'bnb') 308 | blrep = dbx2.blrep(keept2,keepf1,chi+(1:nch2)-1); 309 | cblrep = conj(blrep); 310 | else 311 | blrep2 = dbx2.blrep(keept2,keepf2,chi+(1:nch2)-1); 312 | cblrep = conj(blrep2); 313 | 314 | end 315 | if nch2 >1 316 | % blrep = reshape(blrep,size(blrep,1),size(blrep,2)*nch); 317 | blrep2 = reshape(blrep2,size(blrep2,1),size(blrep2,2)*nch2); 318 | cblrep = reshape(cblrep,size(cblrep,1),size(cblrep,2)*nch2); 319 | end 320 | if opts.wrap 321 | nyqf = min(dbx2.frequency(end),dbx2.fullFS/2); 322 | wrapind = F3 >nyqf; 323 | wrI = I3(wrapind); 324 | [unq,unqi] = unique(F3(wrapind)); 325 | wrneg = (2*nyqf-unq)/fstep3+1; 326 | cblrep(:,wrI(unqi))=conj(cblrep(:,wrneg)); 327 | end 328 | % elseif strcmpi(opts.type,'bnb') 329 | % cblrep = conj(blrep); 330 | % blrep2 = dbx2.blrep(keept2,getf2,:); 331 | % elseif strcmpi(opts.type,'bbb') || strcmpi(opts.type,'nnn') 332 | % cblrep = conj(blrep); 333 | % blrep2 = blrep; 334 | % end 335 | 336 | bspect(inds) = sum(blrep(:,I1(inds)).*blrep2(:,I2(inds)).* cblrep(:,I3(inds))); 337 | 338 | NRM = nan(size(bspect)); 339 | 340 | bias =nan; 341 | switch opts.normalization 342 | case {'bicoh','standard'} 343 | NRM(inds) = sqrt(sum(abs(blrep(:,I1(inds)).*blrep2(:,I2(inds))).^2).* sum(abs(cblrep(:,I3(inds))).^2)); 344 | case 'awplv' 345 | NRM(inds) = sum(abs(blrep(:,I1(inds)).*blrep2(:,I2(inds)).*cblrep(:,I3(inds)))); 346 | case 'awplvbc' 347 | NRM(inds) = sum(abs(blrep(:,I1(inds)).*blrep2(:,I2(inds)).*cblrep(:,I3(inds)))); 348 | bias = zeros(size(NRM)); 349 | bias(inds) = sqrt(sum((abs(blrep(:,I1(inds)).*blrep2(:,I2(inds)).*cblrep(:,I3(inds)))).^2))./sum(abs(blrep(:,I1(inds)).*blrep2(:,I2(inds)).*cblrep(:,I3(inds)))); 350 | case 'pac' 351 | NRM(inds) = sqrt(sum(abs(blrep(:,I1(inds))).^2).*sum(abs(blrep2(:,I2(inds))).^2.*abs(cblrep(:,I3(inds))).^2)); 352 | otherwise 353 | error('Unrecognized normalization, %s',opts.normalization) 354 | end 355 | 356 | if opts.loopchan 357 | bspects(:,:,:,chi) = bspect; 358 | NRMs(:,:,:,chi) = NRM; 359 | if ~isscalar(bias) || isnan(bias) 360 | biases(:,:,:,chi) = bias; 361 | end 362 | end 363 | end 364 | % NRM(inds) = sqrt(sum(abs(blrep(:,I1(inds),:)).^2).* sum(abs(cblrep(:,I3(inds),:).*blrep2(:,I2(inds),:)).^2)); 365 | 366 | end 367 | if opts.loopchan 368 | bspect = bspects; 369 | NRM = NRMs; 370 | bias = biases; 371 | end 372 | 373 | out.BICOH = bspect./NRM; 374 | out.bspect = bspect; 375 | out.NRM = NRM; 376 | out.bw1=opts.bw1; 377 | out.bw2=opts.bw2; 378 | out.bias = bias; 379 | out.w1 = w1; 380 | out.w2 = w2; 381 | out.opts =opts; 382 | out.BICOH(isnan(out.BICOH))=0; 383 | dbx = [dbx1,dbx2]; 384 | 385 | if opts.do_decomp %%% Apply tensor or svd decomposition to the inputs 386 | td.Act = 0; 387 | for chi = 1:nloop 388 | if strcmpi(opts.type,'bnb') 389 | blrep = dbx2.blrep(keept2,keepf1,chi+(1:nch2)-1); 390 | cblrep = conj(blrep); 391 | else 392 | blrep2 = dbx2.blrep(keept2,keepf2,chi+(1:nch2)-1); 393 | cblrep = conj(blrep2); 394 | 395 | end 396 | 397 | if max(nch1,nch2) ==1 || strcmpi(opts.decomp,'svd') 398 | if max(nch1,nch2) >1 399 | BICOH = reshape(out.BICOH,size(out.BICOH(:,:,1)).*[1 nch1*nch2]); 400 | else 401 | BICOH = out.BICOH; 402 | end 403 | [u,l,v] = svd(BICOH); 404 | getn = min(sum(diag(l)>0),opts.tdrank); 405 | [uind,vind] = ndgrid(1:size(BICOH,1),1:size(BICOH,2),1:nch1,1:nch2); 406 | U = u(uind(inds),1:getn); 407 | V = v(vind(inds),1:getn); 408 | 409 | % switch opts.type 410 | % case 'nbb' 411 | 412 | % A = (blrep(:,I1(inds)).*blrep2(:,I2(inds)).* cblrep(:,I3(inds)))*(repmat(NRM(inds).^-1,1,getn).*(V.*conj(U))*diag(diag(l).^-.5)); 413 | % otherwise 414 | % warning('SVD not yet implemented for case %s',opts.type) 415 | % A = nan; 416 | % end 417 | % tdcomp.Act = A; 418 | tdcomp.time = dbx1.time; 419 | tdcomp.u = u(:,1:getn); 420 | tdcomp.l = diag(l); 421 | if max(nch1,nch2) ==1 422 | tdcomp.v = v(:,1:getn); 423 | else 424 | tdcomp.v = reshape(v(:,1:getn), [size(v,1)/(nch1*nch2) nch1 nch2 getn]); 425 | end 426 | UMAT= (V.*conj(U))*diag(diag(l(1:getn,1:getn)).^-.5); 427 | else 428 | %%% Apply tensor decomposition 429 | getn = opts.tdrank; 430 | decomp = str2func(opts.decomp); 431 | tdcomp = decomp(tensor(out.BICOH),opts.tdrank,opts.tdopts{:}); 432 | [u1ind,u2ind,ch1ind,ch2ind] = ndgrid(1:size(out.BICOH,1),1:size(out.BICOH,2),1:nch1,chi+(1:nch2)-1); 433 | UMAT = conj((tdcomp.U{1}(u1ind(inds),:).*tdcomp.U{2}(u2ind(inds),:).*tdcomp.U{3}(ch1ind(inds),:).*tdcomp.U{4}(ch2ind(inds),:))*diag(sqrt(tdcomp.lambda).^-1)); 434 | tdcomp = struct('tensor',tdcomp); 435 | end 436 | % Time activation 437 | tdcomp.Act = td.Act + (blrep(:,I1(inds)).*blrep2(:,I2(inds)).* cblrep(:,I3(inds)))*(repmat(NRM(inds).^-1,1,getn).*UMAT); 438 | tdcomp.time = dbx1.time; 439 | 440 | end 441 | else 442 | tdcomp = []; 443 | end 444 | 445 | out.nyqf = dbx1.fullFS/2; 446 | out.tdcomp = tdcomp; 447 | 448 | if nargout > 2 449 | indices = [I1(inds),I2(inds),I3(inds)]; 450 | end -------------------------------------------------------------------------------- /dbt.m: -------------------------------------------------------------------------------- 1 | classdef dbt 2 | 3 | % This class implements the demodulated band transform (DBT). 4 | % 5 | % Use: 6 | % 7 | % B = dbt(X,Fs,BW) 8 | % 9 | % X - Signal matrix with channels in columns. 10 | % Fs - Sampling frequency 11 | % BW - Bandwidth of decomposition 12 | % 13 | % 14 | % B = dbt(X,Fs,Bw, ['option'], [value]) 15 | % 16 | % B is an object with the following properties: 17 | % 18 | % B.blrep: time-frequency coefficients with rows corresponding to time 19 | % and columns to frequency. If X is a matrix, channels correspond to the 20 | % 3rd dimension. 21 | % .bandwidth: bandwidth of the DBT. 22 | % .sampling_rate: sampling rate after the transform. 23 | % .time: sampled time points. 24 | % .frequency: sampled center frequencies. 25 | % .bands: band limits for each frequency band. 26 | % .taper: taper used to window frequency bands. 27 | % .fftpad: degree of oversampling achieved through fft zero-padding. 1 28 | % corresponds to 2x oversampling. 29 | % .upsampleFx: Amount by which to oversample the frequency dimension. 30 | % .centerDC: If false, the fft of the DBT bands contains 31 | % positive frequencies only, implying 2x oversampling, 32 | % otherwise each band is demodulated to be centered on DC. 33 | % .cospower: Power applied to the window (default = 1). Setting cospower=2 34 | % gives a Hann window for the default window (with 35 | % shoulder = 1). Frequency upsampling is adjusted by default 36 | % to maintain a tight frame. 37 | % 38 | % Options: 39 | % 40 | % offset - offset of the first band from 0hz (default = 0) 41 | % padding - 'time' (default) or 'none': pad signal in the time domain to 42 | % adjust bandwidth. 43 | % shoulder - (0 - 1) degree of overlap between neighboring bands (default = 1). 44 | % Note that, at present, 1 is the maximum allowable value. 45 | % 46 | % The overlapping portions of the bands are windowed with a taper: 47 | % ____ __________ ________ _________ 48 | % \ / \ / \ / 49 | % . . . X X X . . . 50 | % / \ / \ / \ 51 | % |-----------| |-| 52 | % BW shoulder 53 | % 54 | % By default, the taper is defined so that squares sum to 1 55 | % 56 | % upsampleFx - upsample frequency by a factor of (1+x) (default = 0, no upsampling ). 57 | % upsampleTx - upsample time by a factor of (1+x) (default = 0, no upsampling ). 58 | % upsample - upsample both time and frequency by a factor of (1+x). 59 | % bwtol - tolerance for error in setting the bandwidth. Higher values set the 60 | % bandwidth more precisely but may require more padding. Default=1e-8. 61 | % remodphase - If true, remodulate each band back to its original center 62 | % frequency. Default is false. 63 | % 64 | % Methods: 65 | % 66 | % B.signal: Returns the signal reconstructed from DBT coefficients via the 67 | % inverse DBT transform. B.signal(bands) returns the reconstructed 68 | % signal including only the specified bands. bands can be a 69 | % logical or numerical index into frequency bands. 70 | % Example: B.signal(B.frequency < 200) returns the signal including 71 | % only bands with center frequency below 200. 72 | % B.signal(bands,true) returns the complex-valued analytic signal, 73 | % containing only positive frequencies. 74 | % 75 | % B.specgram: plots a spectrogram based on the DBT. B.specgram(true) normalizes 76 | % the spectrogram with smoothing. 77 | % 78 | % See also TAPER, DBTCOH, DBTDENOISE, DBTPAC, STFT 79 | 80 | % C Kovach 2013 - 2017 81 | % 82 | % ----------- SVN REVISION INFO ------------------ 83 | % $URL$ 84 | % $Revision$ 85 | % $Date$ 86 | % $Author$ 87 | % ------------------------------------------------ 88 | 89 | 90 | properties 91 | 92 | blrep = []; % Band-limited analytic representaiton of the signal (time x frequency band) 93 | bandwidth = []; %The bandwidth parameter. This is the bandwidth not including the shoulders. Full bandwidth is BW*(1+shoulder) 94 | sampling_rate = []; % Sampling rate after downsampling 95 | offset = 0; % Offset if the first band doesn't begin at 0 (a highpass cutoff) 96 | time = []; % Time points for the rows in blrep 97 | frequency = []; % Center frequencies for the columns in blrep 98 | bands =[]; % Bands for the columns of blrep 99 | fullN = 0; % Length of the signal after padding 100 | fullFS = 0; % Sampling frequency of the original signal 101 | Norig = 0; % Original signal length before padding 102 | shoulder = 1; % Degree of frequency overlap between neighboring bands (0 - 1) 103 | lowpass = []; % Lowpass cutoff 104 | taper = []; 105 | fftpad = 0; % Upsample each band by padding its fft by this proportion of the unpadded length. 106 | upsampleFx = 0; %Amount by which to oversample the frequency dimension. 107 | userdata=[]; 108 | centerDC = true; % If true, bands are demodulated to [-bandwidth bandwidth], otherwise to [0 bandwidth] with zero padding to 2*bandwidth. 109 | % If true, the pass band is centered at 0, so 110 | % that the signal contains negative 111 | % frequencies, which halves the number of 112 | % samples. 113 | 114 | cospower=1; % If greater than 1 applies a power-of-cosine window, cos(f)^cospower, in the frequency domain 115 | % cospower=2 is the same as a Hann window. Default is the simple cosine window (cospower=1). 116 | 117 | gpuEnable = false; % Use GPU processor if available; 118 | bwtol = 1e-8; % Tolerance for the bandwidth. Higher values set the bandwidth more precisely but require more padding. 119 | direction = 'acausal'; % acausal (default),'causal', or 'anticausal'. Note these are only approximate as strictly causal or anticausal filters can have no zeros on the unit circle. 120 | remodphase = false; % If true, applies a phase correction equivalent to remodulating the subsampled data to the original band. This is necessary for example to get a 121 | % correct average when averaging the raw spectrum over time. 122 | padding = 'time'; % Options are 'time' or 'none'; 'frequency' is obsolete. 123 | inputargs = {}; 124 | end 125 | 126 | methods 127 | 128 | function me = dbt(varargin) 129 | 130 | me.taper = taper; %#ok 131 | 132 | 133 | using_gpu_default_setting = true; 134 | 135 | i = 4; 136 | while i < length(varargin) 137 | switch lower(varargin{i}) 138 | 139 | case {'offset','highpass'} 140 | me.offset = varargin{i+1}; 141 | i = i+1; 142 | case 'band' 143 | band = varargin{i+1}; 144 | me.offset = band(1); 145 | me.lowpass = band(end); 146 | i = i+1; 147 | case 'padding' 148 | me.padding = varargin{i+1}; 149 | i = i+1; 150 | case 'shoulder' 151 | me.shoulder = varargin{i+1}; 152 | % if me.shoulder ~=1 153 | % me.shoulder = 1; 154 | % warning(sprintf('\n!!!!!!!!!!\nMPORTANT MESSAGE: Due to a bug, shoulder is forced to be 1. This will be fixed in a future revision.\n!!!!!!!!!!')) 155 | % end 156 | i = i+1; 157 | case 'lowpass' 158 | me.lowpass = varargin{i+1}; 159 | i = i+1; 160 | case 'taper' 161 | me.taper = varargin{i+1}; 162 | i = i+1; 163 | case 'centerdc' 164 | me.centerDC = varargin{i+1}; 165 | i = i+1; 166 | case {'fftpad','upsampletx'} % Pad fft by proportion of window size 167 | me.fftpad = varargin{i+1}; 168 | i = i+1; 169 | case 'upsample' %Upsample time by proportion with fft padding and frequency by increasing overlap 170 | nx = varargin{i+1}-1; 171 | me.fftpad = nx; 172 | me.upsampleFx = nx; 173 | if nx ~= round(nx) 174 | warning('%s will not currently allow signals to be reconstructed with non-integer upsampling. Integer upsampling is recommended.',upper(mfilename)) 175 | end 176 | i = i+1; 177 | case 'upsamplefx' %Upsample frequency by proportion + 1 178 | 179 | nx = varargin{i+1}; 180 | 181 | if nx ~= round(nx) 182 | warning('%s will not currently allow signals to be reconstructed with non-integer upsampling. Integer upsampling is recommended.',upper(mfilename)) 183 | end 184 | 185 | me.upsampleFx = nx; 186 | i = i+1; 187 | 188 | case 'direction' 189 | me.direction = varargin{i+1}; 190 | i=i+1; 191 | case 'remodphase' 192 | me.remodphase = varargin{i+1}; 193 | i=i+1; 194 | case 'bwtol' 195 | me.bwtol = varargin{i+1}; 196 | i=i+1; 197 | case 'gpu' 198 | me.gpuEnable = varargin{i+1}; 199 | i=i+1; 200 | using_gpu_default_setting = false; 201 | case 'cospower' 202 | me.cospower = varargin{i+1}; 203 | i=i+1; 204 | otherwise 205 | error('Unrecognized keyword %s',varargin{i}) 206 | end 207 | i = i+1; 208 | end 209 | 210 | if isempty(varargin) 211 | return 212 | end 213 | if using_gpu_default_setting && me.gpuEnable 214 | try % Use gpu by default when possible 215 | me.gpuEnable = gpuDeviceCount>0; 216 | catch 217 | me.gpuEnable = false; 218 | end 219 | elseif me.gpuEnable && gpuDeviceCount==0 220 | warning('No GPU device detected. Switching to non-GPU mode') 221 | me.gpuEnable = false; 222 | end 223 | 224 | 225 | me.upsampleFx = me.upsampleFx + me.cospower - 1; 226 | 227 | fs = varargin{2}; 228 | bw = varargin{3}; 229 | fullsig = varargin{1}; 230 | me.inputargs = varargin(4:end); 231 | 232 | if me.gpuEnable 233 | gpuarg = {'gpuArray'}; 234 | elseif ~isa(class(fullsig),'double') 235 | gpuarg = {class(fullsig)}; 236 | end 237 | 238 | n = size(fullsig,1); 239 | ncol = size(fullsig,2); 240 | 241 | me.Norig = n; 242 | %Resample signal so that everything factors 243 | %Keeping signal duration fixed and allowing bandwidth and sampling 244 | %rate to vary. 245 | 246 | % T = n./fs; % old signal duration 247 | %%% K and M need to be integers 248 | 249 | 250 | switch lower(me.padding) 251 | case {true,'time'} 252 | me.padding = 'time'; 253 | % do nothing. 254 | case 'frequency' 255 | warning('Frequency padding option is obsolete. Using default time instead') 256 | me.padding = 'time'; 257 | case {'none',false} 258 | me.padding = 'none'; 259 | me.bwtol = Inf; 260 | end 261 | 262 | %%% Pad signal in time so that bandwidth sapproximately 263 | %%% divides padded duration duration 264 | 265 | % [~,den] = rat(bw/fs/2,me.bwtol); 266 | stepsize = bw/(me.upsampleFx +1); 267 | [~,den] = rat(stepsize/fs/2,me.bwtol); 268 | 269 | 270 | 271 | newn = ceil(n/den)*den; 272 | newT = newn/fs; 273 | 274 | winN = round(bw*newT/2)*2; 275 | newbw = winN/newT; 276 | 277 | 278 | me.fullN = newn; 279 | me.fullFS = fs; 280 | 281 | nnyq = ceil((newn+1)/2); 282 | if isempty(me.lowpass) 283 | me.lowpass = nnyq/newn*fs ; 284 | end 285 | 286 | 287 | %%% Pad the signal 288 | fullsig(end+1:newn,:) = 0; 289 | 290 | F = fft(fullsig./sqrt(newn)); 291 | 292 | me.bandwidth = newbw; 293 | 294 | 295 | nsh = min(ceil(me.shoulder*newbw./fs*newn),winN); 296 | me.shoulder = nsh*fs./newn./newbw; 297 | 298 | %%% Adjust for upsampling 299 | % This will add extra bands at negative frequencies so that the 300 | % number of bands is an integer multiple of the frequency upsampling 301 | % ratio. This is a bookkeeping measure to ensure that all frequencies 302 | % within the desired range are oversampled to the same degree. 303 | % 304 | % 305 | upratio = me.upsampleFx+1; 306 | stepsize = newbw/upratio; 307 | nstepsize = round(stepsize*newT); 308 | % me.offset = me.offset - floor(nsh./(nstepsize+1))*stepsize; % Include any bands that overlap with lower edge so that the lowest band oversampled to the same degree as other bands 309 | % foffset = me.offset-newbw*(1+me.shoulder)/2 - me.upsampleFx*stepsize; 310 | % This adjusts the high-pass offset so it is an integer multiple of 311 | % frequency sampling 312 | % foffset = ceil((me.offset-newbw*(1+me.shoulder)/2 - me.upsampleFx*stepsize)*newT)/newT; 313 | foffset = round((me.offset-newbw*(1+me.shoulder)/2 - me.upsampleFx*stepsize)*newT)/newT; 314 | noffset = floor(foffset*newT ); 315 | foffset=noffset/newT; 316 | me.offset = foffset + newbw*(1+me.shoulder)/2; 317 | 318 | lowp = foffset + ceil((me.lowpass -foffset + upratio*stepsize )/(stepsize*upratio))*upratio*stepsize ;% same for highest band 319 | 320 | 321 | 322 | me.bands(:,1) = (foffset:stepsize:lowp - stepsize);%-newfs/newn; 323 | me.bands(:,2) = me.bands(:,1)+newbw*(1+me.shoulder);%+newfs/newn; 324 | nwin =size(me.bands,1); 325 | 326 | %%% Reshaping matrix. This step includes the initial 327 | %%% circular shift. 328 | rsmat = noffset +... 329 | repmat((0:nwin-1)*nstepsize,round(winN*(1+me.shoulder)),1) +... 330 | repmat((1:round(winN*(1+me.shoulder)))',1,nwin);% -nsh; 331 | 332 | rsmat = mod(rsmat-1,newn)+1; 333 | %dcindx = find(rsmat==1); 334 | 335 | tp = me.taper.make((0:1:nsh-1)/nsh).^me.cospower; 336 | invtaper = me.taper.make(1-(0:1:nsh-1)/nsh).^me.cospower; 337 | 338 | switch me.direction 339 | %%% Approximate causal or anti-causal filters while 340 | %%% preserving summation properties of the tapers. 341 | %%% Note that a truly causal filter cannot have a zero frequency response anywhere. 342 | case {'causal','anticausal'} 343 | htptp = hilbert([tp,invtaper]); 344 | htptp = htptp./(abs(htptp)+eps); 345 | if strcmp(me.direction,'causal') 346 | htptp = conj(htptp); 347 | end 348 | tp = htptp(1:length(tp)).*tp; 349 | invtaper = htptp(length(tp)+(1:length(invtaper))).*invtaper; 350 | case 'acausal' 351 | % do nothing 352 | otherwise 353 | error('Unrecognized filter direction, %s.',me.direction) 354 | end 355 | 356 | Frs = zeros([size(rsmat),ncol],gpuarg{:}); 357 | for k = 1:ncol 358 | f = F(:,k); 359 | 360 | if me.gpuEnable 361 | 362 | Frs(:,:,k) = f(rsmat); 363 | 364 | if nsh>0 365 | % Frs(end+(1-nsh:0),1:nwin,k) = diag(tp)*Frs(end+(1-nsh:0),1:nwin,k); 366 | % Frs(1:nsh,1:nwin,k) = diag(invtaper)*Frs(1:nsh,1:nwin,k); 367 | Frs(end+(1-nsh:0),1:nwin,k) = repmat(tp(:),1,nwin).*Frs(end+(1-nsh:0),1:nwin,k); 368 | Frs(1:nsh,1:nwin,k) = repmat(invtaper(:),1,nwin).*Frs(1:nsh,1:nwin,k); 369 | end 370 | 371 | else 372 | 373 | Frs(:,:,k) = f(rsmat); 374 | 375 | if nsh>0 376 | % Frs(end+(1-nsh:0),1:nwin,k) = diag(sparse(tp))*Frs(end+(1-nsh:0),1:nwin,k); 377 | % Frs(1:nsh,1:nwin,k) = diag(sparse(invtaper))*Frs(1:nsh,1:nwin,k); 378 | Frs(end+(1-nsh:0),1:nwin,k) = repmat(tp(:),1,nwin).*Frs(end+(1-nsh:0),1:nwin,k); 379 | Frs(1:nsh,1:nwin,k) = repmat(invtaper(:),1,nwin).*Frs(1:nsh,1:nwin,k); 380 | end 381 | end 382 | 383 | end 384 | 385 | padN = floor((me.fftpad+1)*winN*(1+~me.centerDC)*(1+me.shoulder)); 386 | me.fftpad = 1/(1+me.shoulder)*padN/winN/(1+~me.centerDC)-1; 387 | 388 | if padN > round(winN*(1+me.shoulder)) 389 | Frs(padN,:,:) = 0; 390 | elseif padN < round((1+me.shoulder)*winN) 391 | padN =round( (1+me.shoulder)*winN); 392 | me.fftpad = 0; 393 | end 394 | 395 | if me.centerDC 396 | Frs = circshift(Frs,-ceil(winN/2*(1+me.shoulder))); 397 | end 398 | 399 | me.blrep = ifft(Frs)*sqrt(padN)*sqrt(2)/sqrt(upratio-me.cospower+1); 400 | 401 | me.sampling_rate = padN/newT; 402 | 403 | 404 | me.time = (((1:size(me.blrep,1))-1)*newT./size(me.blrep,1))'; 405 | w = mean(me.bands,2)'; 406 | me.blrep(:,w > lowp + me.shoulder*bw,:) = 0; 407 | % w(w > me.lowpass + me.shoulder*bw) = []; 408 | me.frequency = w; 409 | 410 | if me.remodphase 411 | %%% This remodulates the signal to obtain the correct phase 412 | %%% at a given frequency for the original band. It is necessary 413 | %%% when averaging the complex-valued spectrum over time so 414 | %%% that phase aligns correctly. 415 | remodulator = exp(1i*2*pi*me.time*(me.frequency - (~me.centerDC)*me.bandwidth*(1+me.shoulder)/2)); 416 | me.blrep = me.blrep.*repmat(remodulator,[1 1 size(me.blrep,3)]); 417 | 418 | end 419 | 420 | end 421 | 422 | %%%% 423 | 424 | function [data,fs] = signal(me,columnfilter,doHilbert,return_padded) 425 | 426 | % db.signal 427 | % Reconstruct the signal from its band-limited analytic 428 | % representation by applying the inverse DBT. 429 | % 430 | % db.signal(bands) 431 | % 432 | % Reconstruct the signal using only the specified bands. 'bands 433 | % may be a numeric or logical index. 434 | % Example: 435 | % db.signal(db.frequency > 100) 436 | % returns the signalusing only bands with center frequency above 100 Hz. 437 | % 438 | % db.signal(bands,true) 439 | % 440 | % Returns a complex-valued analytic signal with negative frequencies zero'd. 441 | % 442 | 443 | 444 | upratio = me.upsampleFx+1; 445 | 446 | if me.gpuEnable 447 | gpuarg = {'gpuArray'}; 448 | else 449 | gpuarg = {class(me.blrep)}; 450 | end 451 | if round(me.upsampleFx) ~= me.upsampleFx 452 | error('\n%s does not currently allow signals to be reconstructed from a transform upsampled by a non-integer.',upper(mfilename)) 453 | end 454 | if nargin < 2 || isempty(columnfilter) 455 | mult = diag(ones(1,upratio)); 456 | elseif islogical(columnfilter) 457 | mult = diag(columnfilter); 458 | elseif min(size(columnfilter)) == 1 && min(columnfilter)>=1 459 | 460 | mult = diag( ismember(1:size(me.blrep,2),columnfilter)); 461 | 462 | elseif min(size(columnfilter)) == 1 463 | mult = diag(columnfilter); 464 | else 465 | mult = columnfilter; 466 | end 467 | 468 | if nargin < 3 || isempty(doHilbert) 469 | doHilbert = false; 470 | end 471 | if nargin < 4 || isempty(return_padded) 472 | return_padded = false; 473 | end 474 | % n = me.fullN; 475 | % noffset = round((me.offset-me.bandwidth*me.shoulder)./me.fullFS*n); 476 | % noffset = round((me.offset - me.bandwidth*(1+me.shoulder)/2)*me.fullN/me.fullFS ); 477 | ncol = size(me.blrep,3); 478 | if me.remodphase 479 | %%% If phase remodulation was applied we need to reverse it. 480 | demodulator = exp(-1i*2*pi*me.time*(me.frequency - (~me.centerDC)*me.bandwidth*(1+me.shoulder)/2)); 481 | me.blrep = me.blrep.*repmat(demodulator,[1 1 size(me.blrep,3)]); 482 | 483 | end 484 | 485 | F = zeros(size(me.blrep,1),size(me.blrep,2)/upratio,ncol,upratio,gpuarg{:}); 486 | for k = 1:ncol 487 | for upsi = 1:upratio 488 | F(:,:,k,upsi) = fft(me.blrep(:,upsi:upratio:end,k) )*mult(upsi:upratio:end,upsi:upratio:end)/sqrt(size(me.blrep,1)/2); 489 | end 490 | end 491 | nsh = round(me.shoulder*me.bandwidth./me.fullFS*me.fullN); 492 | padN = length(me.time); 493 | winN = round(padN./(1+me.fftpad)/(1+~me.centerDC)/(1+me.shoulder)); 494 | if me.centerDC 495 | 496 | F = circshift(F,ceil(winN/2*(1+me.shoulder)));%sqrt(2); 497 | 498 | end 499 | F = F(1:round(winN*(1+me.shoulder)),:,:,:)*sqrt(2); 500 | 501 | 502 | nnyq = size(F,1); 503 | if nsh >0 504 | % Taper is normally defined so that h(k).^2 + h(k+bw).^2 = 1 505 | tp = me.taper.make((0:1:nsh-1)/nsh).^me.cospower; 506 | invtaper = me.taper.make(1-(0:1:nsh-1)/nsh).^me.cospower; 507 | switch me.direction 508 | %%% Approximate causal or anti-causal filters while 509 | %%% preserving summation properties of the tapers. 510 | 511 | case {'causal','anticausal'} 512 | htptp = hilbert([tp,invtaper]); 513 | htptp = htptp./(abs(htptp)+eps); 514 | if strcmp(me.direction,'anticausal') 515 | htptp = conj(htptp); 516 | end 517 | tp = htptp(1:length(tp)).*tp; 518 | invtaper = htptp(length(tp)+(1:length(invtaper))).*invtaper; 519 | case 'acausal' 520 | % Do nothing. 521 | otherwise 522 | error('Unrecognized filter direction, %s.',me.direction) 523 | end 524 | 525 | for k = 1:ncol 526 | for upsi = 1:upratio 527 | % sh = diag(sparse(invtaper))*F(1:nsh,2:end,k,upsi); 528 | % F(nnyq-nsh+1:nnyq,1:end-1,k,upsi) = diag(sparse(tp))*F(nnyq-nsh+1:nnyq,1:end-1,k,upsi)+sh; 529 | % sh = diag(invtaper)*F(1:nsh,2:end,k,upsi); 530 | sh = repmat(invtaper(:),1,size(F,2)-1).*F(1:nsh,2:end,k,upsi); 531 | % F(nnyq-nsh+1:nnyq,1:end-1,k,upsi) = diag(tp)*F(nnyq-nsh+1:nnyq,1:end-1,k,upsi)+sh; 532 | F(nnyq-nsh+1:nnyq,1:end-1,k,upsi) = repmat(tp(:),1,size(F,2)-1).*F(nnyq-nsh+1:nnyq,1:end-1,k,upsi)+sh; 533 | end 534 | end 535 | 536 | % else 537 | % invtaper = 1; 538 | end 539 | 540 | % if nsh > 0 541 | % %%% Retain the leading edge if there is an offset 542 | % F0 = permute(F(1:nsh,1,:),[1 3 2]).*repmat(invtaper',1,ncol); 543 | % else 544 | % F0=zeros(0,ncol); 545 | % end 546 | 547 | F(1:nsh,:,:,:)=[]; 548 | Ffull = zeros(me.fullN,ncol,gpuarg{:}); 549 | switch me.padding 550 | case {'frequency','fft'} 551 | warning('Frequency padding is an obsolete option.') 552 | end 553 | T = me.fullN./me.fullFS; %Signal duration after padding 554 | for k = 1:ncol 555 | 556 | for upsi = 1:upratio 557 | f = F(:,:,k,upsi); 558 | % Q( 1 + mod(nofs + (1:numel(f))-1,me.fullN),upsi)=f(:); 559 | nofs = round((me.bands(upsi,1) + me.bandwidth*me.shoulder)*T); 560 | %%% Reconstruct signal, averaging over the oversampled 561 | %%% bands. 562 | fillindx = nofs + (1:numel(f))'-1; 563 | fillindx = 1+mod(fillindx(fillindx-fillindx(1) 124 | 125 | while i < length(varargin) 126 | switch lower(varargin{i}) 127 | 128 | case {'offset','highpass'} 129 | me.offset = varargin{i+1}; 130 | i = i+1; 131 | case 'band' 132 | band = varargin{i+1}; 133 | me.offset = band(1); 134 | me.lowpass = band(end); 135 | i = i+1; 136 | case 'padding' 137 | me.padding = varargin{i+1}; 138 | i = i+1; 139 | case {'cfreq','center frequency','center_frequency'} 140 | me.cfreq = varargin{i+1}; 141 | i = i+1; 142 | case 'shoulder' 143 | me.shoulder = varargin{i+1}; 144 | % if me.shoulder ~=1 145 | % me.shoulder = 1; 146 | % warning(sprintf('\n!!!!!!!!!!\nMPORTANT MESSAGE: Due to a bug, shoulder is forced to be 1. This will be fixed in a future revision.\n!!!!!!!!!!')) 147 | % end 148 | i = i+1; 149 | case 'lowpass' 150 | me.lowpass = varargin{i+1}; 151 | i = i+1; 152 | case 'taper' 153 | me.taper = varargin{i+1}; 154 | i = i+1; 155 | case 'centerdc' 156 | me.centerDC = varargin{i+1}; 157 | i = i+1; 158 | case {'fftpad','upsampletx'} % Pad fft by proportion of window size 159 | me.fftpad = varargin{i+1}; 160 | i = i+1; 161 | case 'upsample' %Upsample time by proportion with fft padding and frequency by increasing overlap 162 | nx = varargin{i+1}-1; 163 | me.fftpad = nx; 164 | me.upsampleFx = nx; 165 | if nx ~= round(nx) 166 | warning('%s will not currently allow signals to be reconstructed with non-integer upsampling. Integer upsampling is recommended.',upper(mfilename)) 167 | end 168 | i = i+1; 169 | case 'upsamplefx' %Upsample frequency by proportion + 1 170 | 171 | nx = varargin{i+1}; 172 | 173 | if nx ~= round(nx) 174 | warning('%s will not currently allow signals to be reconstructed with non-integer upsampling. Integer upsampling is recommended.',upper(mfilename)) 175 | end 176 | 177 | me.upsampleFx = nx; 178 | i = i+1; 179 | 180 | case 'direction' 181 | me.direction = varargin{i+1}; 182 | i=i+1; 183 | case 'remodphase' 184 | me.remodphase = varargin{i+1}; 185 | i=i+1; 186 | case 'bwtol' 187 | me.bwtol = varargin{i+1}; 188 | i=i+1; 189 | case 'logspace' 190 | me.logspace = varargin{i+1}; 191 | i=i+1; 192 | otherwise 193 | error('Unrecognized keyword %s',varargin{i}) 194 | end 195 | i = i+1; 196 | end 197 | 198 | if isempty(varargin) 199 | return 200 | end 201 | 202 | fs = varargin{2}; 203 | % bw = varargin{3}; 204 | 205 | fullsig = varargin{1}; 206 | 207 | n = size(fullsig,1); 208 | ncol = size(fullsig,2); 209 | 210 | me.inputargs = varargin(3:end); 211 | 212 | me.Norig = n; 213 | 214 | nnyq = ceil((n+1)/2); 215 | if isempty(me.lowpass) 216 | me.lowpass = nnyq/n*fs ; 217 | end 218 | 219 | %%% A vector giving the "proportion" of the distance from one center frequency to the next 220 | me.cfreq(me.cfreqme.lowpass)=[]; 221 | 222 | cfreq0 = unique([me.offset-(2-1./(me.upsampleFx+1)).*(min(me.cfreq)-me.offset); me.offset;me.cfreq(:);me.lowpass; me.lowpass+(me.lowpass-max(me.cfreq)) ]*2/fs); 223 | 224 | 225 | w = mod(2*(0:n-1)./n + 1,2)-1; 226 | % Create a margin at nyquist for the sake of consistency 227 | w(w0) = exp(log(cfreq0(cfreq0(1:end-1)>0)) + diff(log(cfreq0(cfreq0>0)))*(k-1)./(me.upsampleFx+1)); 240 | frqscale(wcs>0,k) = mod(interp1(log(cfrq(cfrq>0)),1:sum(cfrq>0),log(wcs(wcs>0)),'linear'),1); 241 | 242 | end 243 | cfreqs(k,:) = cfrq; 244 | end 245 | intval = [zeros(1,size(frqscale,2));cumsum(diff(frqscale)<0)]; 246 | tp = me.taper; 247 | 248 | Wgt = tp.make(frqscale); 249 | 250 | F = circshift(fft(fullsig),[-ncs,0]); 251 | 252 | 253 | %Apply weighting 254 | srate = []; 255 | for k = 1:size(cfreq0)-2 256 | for kk = 1:size(Wgt,2) 257 | 258 | indx1 = intval(:,kk)==k-1 & ~isnan(Wgt(:,kk)); 259 | indx2 = intval(:,kk)==k & ~isnan(Wgt(:,kk)); 260 | 261 | win1 = sqrt(1-Wgt(indx1,kk).^2); 262 | win2 = Wgt(indx2,kk); 263 | n1 = length(win1); 264 | n2 = length(win2); 265 | mxn = ceil(max(n1,n2)*(1+me.fftpad)); 266 | 267 | f1 = zeros(mxn,ncol); 268 | f2=f1; 269 | f1(end-n1+1:end,:) = F(indx1,:).*repmat(win1,1,ncol); 270 | f2(1:n2,:) = F(indx2 ,:).*repmat(win2,1,ncol); 271 | f = cat(1,f2,f1); 272 | 273 | blsigs{kk,k} = ifft(f); 274 | times{kk,k} = (0:length(f)-1)./length(f)*n./fs; 275 | ws = wcs(indx1|indx2); 276 | frange{kk,k} = [min(ws) max(ws)]*fs/2; 277 | srate(kk,k) = length(f)./n*fs; 278 | end 279 | 280 | end 281 | 282 | me.blrep = blsigs; 283 | me.time = times; 284 | me.frequency = cfreqs(:,2:end)*fs/2; 285 | me.sampling_rate = srate; 286 | me.bands = frange; 287 | me.fullFS = fs; 288 | 289 | end 290 | function sig = signal(me,varargin) 291 | 292 | 293 | 294 | fs = me.fullFS; 295 | % bw = varargin{3}; 296 | 297 | 298 | n = me.Norig; 299 | ncol = size(me.blrep{1},2); 300 | 301 | 302 | 303 | cfreq0 = unique([me.offset-(min(me.cfreq)-me.offset); me.offset;me.cfreq(:);me.lowpass; me.lowpass+(me.lowpass-max(me.cfreq)) ]*2/fs); 304 | 305 | 306 | w = mod(2*(0:n-1)./n + 1,2)-1; 307 | % Create a margin at nyquist for the sake of consistency 308 | w(w 317 | if me.logspace 318 | cfrq(cfreq0(1:end-1)>0) = exp(log(cfreq0(cfreq0(1:end-1)>0)) + diff(log(cfreq0(cfreq0>0)))*(k-1)./(me.upsampleFx+1)); 319 | frqscale(wcs>0,k) = mod(interp1(log(cfrq(cfrq>0)),1:sum(cfrq>0),log(wcs(wcs>0)),'linear'),1); %#ok 320 | 321 | end 322 | end 323 | 324 | intval = [zeros(1,size(frqscale,2));cumsum(diff(frqscale)<0)]; 325 | tp = me.taper; 326 | 327 | Wgt = tp.make(frqscale); 328 | 329 | %Reverse 330 | F = zeros(me.Norig,ncol); 331 | for k = 1:size(cfreq0)-2 332 | for kk = 1:size(Wgt,2) 333 | 334 | indx1 = intval(:,kk)==k-1 & ~isnan(Wgt(:,kk)); 335 | indx2 = intval(:,kk)==k & ~isnan(Wgt(:,kk)); 336 | 337 | win1 = sqrt(1-Wgt(indx1,kk).^2); 338 | win2 = Wgt(indx2,kk); 339 | n1 = length(win1); 340 | n2 = length(win2); 341 | 342 | f = fft(me.blrep{kk,k}); 343 | f1 = f(end-n1+1:end,:); 344 | f2 = f(1:n2); 345 | F(indx1,:) = f1.*repmat(win1,1,ncol)+F(indx1,:); 346 | F(indx2,:) = f2.*repmat(win2,1,ncol)+F(indx2,:); 347 | 348 | end 349 | 350 | end 351 | F(wcs>1 | wcs< 0,:) = 0; 352 | sig = real(ifft(circshift(F,[ncs,0]))); 353 | end 354 | 355 | function specgram(me) 356 | 357 | hold on 358 | mnf = cellfun(@min,me.bands); 359 | mxf = cellfun(@max,me.bands); 360 | plotmn = cellfun(@(x) x + [1 -1]*diff(x)/3,me.bands); 361 | 362 | end 363 | 364 | % %Resample signal so that everything factors 365 | % %Keeping signal duration fixed and allowing bandwidth and sampling 366 | % %rate to vary. 367 | % 368 | % % T = n./fs; % old signal duration 369 | % %%% K and M need to be integers 370 | % 371 | % 372 | % switch lower(me.padding) 373 | % case {true,'time'} 374 | % me.padding = 'time'; 375 | % % do nothing. 376 | % case 'frequency' 377 | % warning('Frequency padding option is obsolete. Using default time instead') 378 | % me.padding = 'time'; 379 | % case {'none',false} 380 | % me.padding = 'none'; 381 | % me.bwtol = Inf; 382 | % end 383 | % 384 | % %%% Pad signal in time so that bandwidth is approximately 385 | % %%% divides padded duration duration 386 | % 387 | % % [~,den] = rat(bw/fs/2,me.bwtol); 388 | % stepsize = bw/(me.upsampleFx +1); 389 | % [~,den] = rat(stepsize/fs/2,me.bwtol); 390 | % 391 | % 392 | % 393 | % newn = ceil(n/den)*den; 394 | % newT = newn/fs; 395 | % 396 | % winN = round(bw*newT); 397 | % newbw = winN/newT; 398 | % 399 | % 400 | % me.fullN = newn; 401 | % me.fullFS = fs; 402 | % 403 | % 404 | % 405 | % 406 | % %%% Pad the signal 407 | % fullsig(end+1:newn,:) = 0; 408 | % 409 | % F = fft(fullsig./sqrt(newn)); 410 | % 411 | % me.bandwidth = newbw; 412 | % 413 | % 414 | % nsh = min(ceil(me.shoulder*newbw./fs*newn),winN); 415 | % me.shoulder = nsh*fs./newn./newbw; 416 | % 417 | % %%% Adjust for upsampling 418 | % % This will add extra bands at negative frequencies so that the 419 | % % number of bands is an integer multiple of the frequency upsampling 420 | % % ratio. This is a bookkeeping measure to ensure that all frequencies 421 | % % within the desired range are oversampled to the same degree. 422 | % % 423 | % % 424 | % upratio = me.upsampleFx+1; 425 | % stepsize = newbw/upratio; 426 | % nstepsize = round(stepsize*newT); 427 | % % me.offset = me.offset - floor(nsh./(nstepsize+1))*stepsize; % Include any bands that overlap with lower edge so that the lowest band oversampled to the same degree as other bands 428 | % % foffset = me.offset-newbw*(1+me.shoulder)/2 - me.upsampleFx*stepsize; 429 | % % This adjusts the high-pass offset so it is an integer multiple of 430 | % % frequency sampling 431 | % foffset = floor((me.offset-newbw*(1+me.shoulder)/2 - me.upsampleFx*stepsize)*newT)/newT; 432 | % noffset = round(foffset*newT ); 433 | % foffset=noffset/newT; 434 | % me.offset = foffset + newbw*(1+me.shoulder)/2; 435 | % 436 | % lowp = foffset + ceil((me.lowpass -foffset + upratio*stepsize )/(stepsize*upratio))*upratio*stepsize ;% same for highest band 437 | % 438 | % 439 | % 440 | % me.bands(:,1) = (foffset:stepsize:lowp - stepsize);%-newfs/newn; 441 | % me.bands(:,2) = me.bands(:,1)+newbw*(1+me.shoulder);%+newfs/newn; 442 | % nwin =size(me.bands,1); 443 | % 444 | % %%% Reshaping matrix. This step includes the initial 445 | % %%% circular shift. 446 | % rsmat = noffset +... 447 | % repmat((0:nwin-1)*nstepsize,round(winN*(1+me.shoulder)),1) +... 448 | % repmat((1:round(winN*(1+me.shoulder)))',1,nwin);% -nsh; 449 | % 450 | % rsmat = mod(rsmat-1,newn)+1; 451 | % %dcindx = find(rsmat==1); 452 | % 453 | % tp = me.taper.make((0:1:nsh-1)/nsh); 454 | % invtaper = me.taper.make(1-(0:1:nsh-1)/nsh); 455 | % 456 | % switch me.direction 457 | % %%% Approximate causal or anti-causal filters while 458 | % %%% preserving summation properties of the tapers. 459 | % %%% Note that a truly causal filter cannot have a zero frequency response anywhere. 460 | % case {'causal','anticausal'} 461 | % htptp = hilbert([tp,invtaper]); 462 | % htptp = htptp./(abs(htptp)+eps); 463 | % if strcmp(me.direction,'causal') 464 | % htptp = conj(htptp); 465 | % end 466 | % tp = htptp(1:length(tp)).*tp; 467 | % invtaper = htptp(length(tp)+(1:length(invtaper))).*invtaper; 468 | % case 'acausal' 469 | % % do nothing 470 | % otherwise 471 | % error('Unrecognized filter direction, %s.',me.direction) 472 | % end 473 | % 474 | % Frs = zeros([size(rsmat),ncol]); 475 | % for k = 1:ncol 476 | % f = F(:,k); 477 | % Frs(:,:,k) = double(f(rsmat)); 478 | % if nsh>0 479 | % Frs(end+(1-nsh:0),1:nwin,k) = diag(sparse(tp))*Frs(end+(1-nsh:0),1:nwin,k); 480 | % 481 | % Frs(1:nsh,1:nwin,k) = diag(sparse(invtaper))*Frs(1:nsh,1:nwin,k); 482 | % end 483 | % 484 | % %%% Set all negative frequencies to zero 485 | % % Frs(rsmat(:, end-1:end)>ceil((newn+1)/2),end,k)=0; 486 | % % Frs(rsmat(:, 1:2)>ceil((newn+1)/2),1,k)=0; 487 | % 488 | % %%% Corrections for DC and Nyquist to preserve power 489 | % %%% after zeroing negative frequencies 490 | % % Frs(rsmat(:,1)==1,1,k)=Frs(rsmat(:,1)==1,1,k)/sqrt(2); 491 | % % Frs(dcindx + (k-1)*numel(rsmat))=Frs(dcindx + (k-1)*numel(rsmat))/sqrt(2); 492 | % 493 | % end 494 | % 495 | % padN = floor((me.fftpad+1)*winN*(1+~me.centerDC)*(1+me.shoulder)); 496 | % me.fftpad = 1/(1+me.shoulder)*padN/winN/(1+~me.centerDC)-1; 497 | % 498 | % if padN > round(winN*(1+me.shoulder)) 499 | % Frs(padN,:,:) = 0; 500 | % elseif padN < round((1+me.shoulder)*winN) 501 | % padN =round( (1+me.shoulder)*winN); 502 | % me.fftpad = 0; 503 | % end 504 | % 505 | % if me.centerDC 506 | % Frs = circshift(Frs,-ceil(winN/2*(1+me.shoulder))); 507 | % end 508 | % 509 | % me.blrep = ifft(Frs)*sqrt(padN)*sqrt(2)/sqrt(upratio); 510 | % 511 | % me.sampling_rate = padN/newT; 512 | % 513 | % 514 | % me.time = (((1:size(me.blrep,1))-1)*newT./size(me.blrep,1))'; 515 | % w = mean(me.bands,2)'; 516 | % me.blrep(:,w > lowp + me.shoulder*bw,:) = 0; 517 | % % w(w > me.lowpass + me.shoulder*bw) = []; 518 | % me.frequency = w; 519 | % 520 | % if me.remodphase 521 | % %%% This remodulates the signal to obtain the correct phase 522 | % %%% at a given frequency for the original band. It is necessary 523 | % %%% when averaging the complex-valued spectrum over time so 524 | % %%% that phase aligns correctly. 525 | % remodulator = exp(1i*2*pi*me.time*(me.frequency - (~me.centerDC)*me.bandwidth*(1+me.shoulder)/2)); 526 | % me.blrep = me.blrep.*repmat(remodulator,[1 1 size(me.blrep,3)]); 527 | % 528 | % end 529 | % 530 | % end 531 | 532 | %%%% 533 | 534 | % function [data,fs] = signal(me,columnfilter,doHilbert,return_padded) 535 | % 536 | % % db.signal 537 | % % Reconstruct the signal from its band-limited analytic 538 | % % representation by applying the inverse DBT. 539 | % % 540 | % % db.signal(bands) 541 | % % 542 | % % Reconstruct the signal using only the specified bands. 'bands 543 | % % may be a numeric or logical index. 544 | % % Example: 545 | % % db.signal(db.frequency > 100) 546 | % % returns the signalusing only bands with center frequency above 100 Hz. 547 | % % 548 | % % db.signal(bands,true) 549 | % % 550 | % % Returns a complex-valued analytic signal with negative frequencies zero'd. 551 | % % 552 | % 553 | % 554 | % upratio = me.upsampleFx+1; 555 | % 556 | % if round(me.upsampleFx) ~= me.upsampleFx 557 | % error('\n%s does not currently allow signals to be reconstructed from a transform upsampled by a non-integer.',upper(mfilename)) 558 | % end 559 | % if nargin < 2 || isempty(columnfilter) 560 | % mult = diag(sparse(ones(1,upratio))); 561 | % elseif islogical(columnfilter) 562 | % mult = diag(sparse(columnfilter)); 563 | % elseif min(size(columnfilter)) == 1 && min(columnfilter)>=1 564 | % 565 | % mult = diag(sparse( ismember(1:size(me.blrep,2),columnfilter))); 566 | % 567 | % elseif min(size(columnfilter)) == 1 568 | % mult = diag(sparse(columnfilter)); 569 | % else 570 | % mult = columnfilter; 571 | % end 572 | % 573 | % if nargin < 3 || isempty(doHilbert) 574 | % doHilbert = false; 575 | % end 576 | % if nargin < 4 || isempty(return_padded) 577 | % return_padded = false; 578 | % end 579 | % % n = me.fullN; 580 | % % noffset = round((me.offset-me.bandwidth*me.shoulder)./me.fullFS*n); 581 | % % noffset = round((me.offset - me.bandwidth*(1+me.shoulder)/2)*me.fullN/me.fullFS ); 582 | % ncol = size(me.blrep,3); 583 | % if me.remodphase 584 | % %%% If phase remodulation was applied we need to reverse it. 585 | % demodulator = exp(-1i*2*pi*me.time*(me.frequency - (~me.centerDC)*me.bandwidth*(1+me.shoulder)/2)); 586 | % me.blrep = me.blrep.*repmat(demodulator,[1 1 size(me.blrep,3)]); 587 | % 588 | % end 589 | % 590 | % F = zeros(size(me.blrep,1),size(me.blrep,2)/upratio,ncol,upratio); 591 | % for k = 1:ncol 592 | % for upsi = 1:upratio 593 | % F(:,:,k,upsi) = fft(me.blrep(:,upsi:upratio:end,k) )*mult(upsi:upratio:end,upsi:upratio:end)/sqrt(size(me.blrep,1)/2); 594 | % end 595 | % end 596 | % nsh = round(me.shoulder*me.bandwidth./me.fullFS*me.fullN); 597 | % padN = length(me.time); 598 | % winN = round(padN./(1+me.fftpad)/(1+~me.centerDC)/(1+me.shoulder)); 599 | % if me.centerDC 600 | % 601 | % F = circshift(F,ceil(winN/2*(1+me.shoulder)));%sqrt(2); 602 | % 603 | % end 604 | % F = F(1:round(winN*(1+me.shoulder)),:,:,:)*sqrt(2); 605 | % 606 | % 607 | % nnyq = size(F,1); 608 | % if nsh >0 609 | % % Taper is normally defined so that h(k).^2 + h(k+bw).^2 = 1 610 | % tp = me.taper.make((0:1:nsh-1)/nsh); 611 | % invtaper = me.taper.make(1-(0:1:nsh-1)/nsh); 612 | % switch me.direction 613 | % %%% Approximate causal or anti-causal filters while 614 | % %%% preserving summation properties of the tapers. 615 | % 616 | % case {'causal','anticausal'} 617 | % htptp = hilbert([tp,invtaper]); 618 | % htptp = htptp./(abs(htptp)+eps); 619 | % if strcmp(me.direction,'anticausal') 620 | % htptp = conj(htptp); 621 | % end 622 | % tp = htptp(1:length(tp)).*tp; 623 | % invtaper = htptp(length(tp)+(1:length(invtaper))).*invtaper; 624 | % case 'acausal' 625 | % % Do nothing. 626 | % otherwise 627 | % error('Unrecognized filter direction, %s.',me.direction) 628 | % end 629 | % 630 | % for k = 1:ncol 631 | % for upsi = 1:upratio 632 | % sh = diag(sparse(invtaper))*F(1:nsh,2:end,k,upsi); 633 | % F(nnyq-nsh+1:nnyq,1:end-1,k,upsi) = diag(sparse(tp))*F(nnyq-nsh+1:nnyq,1:end-1,k,upsi)+sh; 634 | % end 635 | % end 636 | % 637 | % % else 638 | % % invtaper = 1; 639 | % end 640 | % 641 | % % if nsh > 0 642 | % % %%% Retain the leading edge if there is an offset 643 | % % F0 = permute(F(1:nsh,1,:),[1 3 2]).*repmat(invtaper',1,ncol); 644 | % % else 645 | % % F0=zeros(0,ncol); 646 | % % end 647 | % 648 | % F(1:nsh,:,:,:)=[]; 649 | % Ffull = zeros(me.fullN,ncol); 650 | % switch me.padding 651 | % case {'frequency','fft'} 652 | % warning('Frequency padding is an obsolete option.') 653 | % end 654 | % T = me.fullN./me.fullFS; %Signal duration after padding 655 | % for k = 1:ncol 656 | % 657 | % for upsi = 1:upratio 658 | % f = F(:,:,k,upsi); 659 | % % Q( 1 + mod(nofs + (1:numel(f))-1,me.fullN),upsi)=f(:); 660 | % nofs = round((me.bands(upsi,1) + me.bandwidth*me.shoulder)*T); 661 | % %%% Reconstruct signal, averaging over the oversampled 662 | % %%% bands. 663 | % Ffull( 1 + mod(nofs + (1:numel(f))-1,me.fullN),k) =... 664 | % Ffull( 1 + mod(nofs + (1:numel(f))-1,me.fullN),k)... 665 | % + f(:) * sqrt(me.fullN)/sqrt(upratio); 666 | % end 667 | % if ~mod(me.fullN,2) 668 | % Ffull(ceil(me.fullN/2+1),k) = Ffull(ceil(me.fullN/2+1),k)/2; 669 | % end 670 | % Ffull(1,k) = real(Ffull(1,k))/2; 671 | % end 672 | % Ffull(ceil((me.fullN+1)/2)+1:end,:,:) = 0; 673 | % 674 | % Ffull = Ffull./sqrt(2); 675 | % if doHilbert %%% If second argument is true return analytic signal 676 | % data = ifft(full(Ffull)); 677 | % else 678 | % data = real(ifft(full(Ffull))); 679 | % end 680 | % if ~return_padded 681 | % data = data(1:me.Norig,:); 682 | % end 683 | % fs = me.fullFS; 684 | % 685 | % end 686 | %%%% 687 | % function varargout = specgram(me,normalize) 688 | % if nargin < 2 689 | % normalize = 0; 690 | % end 691 | % if normalize 692 | % fun = @rmbaseline; 693 | % else 694 | % fun = @(x)x.blrep; 695 | % end 696 | % S = 20*log10(abs(fun(me)))'; 697 | % t = me.time; 698 | % w = me.frequency; 699 | % if nargout == 0 700 | % imagesc(t,w,S) 701 | % axis xy 702 | % else 703 | % varargout(1:3) = {S,t,w}; 704 | % end 705 | % end 706 | end 707 | end 708 | 709 | --------------------------------------------------------------------------------