function [freq] = ft_freqanalysis(cfg, data) % FT_FREQANALYSIS performs frequency and time-frequency analysis % on time series data over multiple trials % % Use as % [freq] = ft_freqanalysis(cfg, data) % % The input data should be organised in a structure as obtained from % the FT_PREPROCESSING or the FT_MVARANALYSIS function. The configuration % depends on the type of computation that you want to perform. % % The configuration should contain: % cfg.method = different methods of calculating the spectra % 'mtmfft', analyses an entire spectrum for the entire data % length, implements multitaper frequency transformation % 'mtmconvol', implements multitaper time-frequency % transformation based on multiplication in the frequency % domain. % 'wavelet', implements wavelet time frequency % transformation (using Morlet wavelets) based on % multiplication in the frequency domain. % 'tfr', implements wavelet time frequency transformation % (using Morlet wavelets) based on convolution in the % time domain. % 'mvar', does a fourier transform on the coefficients of % an estimated multivariate autoregressive model, % obtained with FT_MVARANALYSIS. In this case, the % output will contain a spectral transfer matrix, % the cross-spectral density matrix, and the % covariance matrix of the innovatio noise. % cfg.output = 'pow' return the power-spectra % 'powandcsd' return the power and the cross-spectra % 'fourier' return the complex Fourier-spectra % cfg.channel = Nx1 cell-array with selection of channels (default = 'all'), % see FT_CHANNELSELECTION for details % cfg.channelcmb = Mx2 cell-array with selection of channel pairs (default = {'all' 'all'}), % see FT_CHANNELCOMBINATION for details % cfg.trials = 'all' or a selection given as a 1xN vector (default = 'all') % cfg.keeptrials = 'yes' or 'no', return individual trials or average (default = 'no') % cfg.keeptapers = 'yes' or 'no', return individual tapers or average (default = 'no') % cfg.pad = number or 'maxperlen', length in seconds to which the % data can be padded out (default = 'maxperlen') The % padding will determine your spectral resolution. If % you want to compare spectra from data pieces of % different lengths, you should use the same cfg.pad % for both, in order to spectrally interpolate them to % the same spectral resolution. Note that this will % run very slow if you specify cfg.pad as maxperlen % AND the number of samples turns out to have a large % prime factor sum. This is because the FFTs will then % be computed very inefficiently. % cfg.padtype = string, type of padding (default 'zero', see % ft_preproc_padding) % cfg.polyremoval = number (default = 0), specifying the order of the % polynome which is fitted and subtracted from the % time domain data prior to the spectral analysis. For example, a % value of 1 corresponds to a linear trend. The default is a mean % subtraction, thus a value of 0. If no removal is requested, % specify -1. % see FT_PREPROC_POLYREMOVAL for details % % % METHOD SPECIFIC OPTIONS AND DESCRIPTIONS % % MTMFFT % MTMFFT performs frequency analysis on any time series % trial data using the 'multitaper method' (MTM) based on discrete % prolate spheroidal sequences (Slepian sequences) as tapers. Alternatively, % you can use conventional tapers (e.g. Hanning). % cfg.foilim = [begin end], frequency band of interest % OR % cfg.foi = vector 1 x numfoi, frequencies of interest % cfg.tapsmofrq = number, the amount of spectral smoothing through % multi-tapering. Note that 4 Hz smoothing means % plus-minus 4 Hz, i.e. a 8 Hz smoothing box. % cfg.taper = 'dpss', 'hanning' or many others, see WINDOW (default = 'dpss') % For cfg.output='powandcsd', you should specify the channel combinations % between which to compute the cross-spectra as cfg.channelcmb. Otherwise % you should specify only the channels in cfg.channel. % % MTMCONVOL % MTMCONVOL performs time-frequency analysis on any time series trial data % using the 'multitaper method' (MTM) based on Slepian sequences as tapers. Alternatively, % you can use conventional tapers (e.g. Hanning). % cfg.tapsmofrq = vector 1 x numfoi, the amount of spectral smoothing through % multi-tapering. Note that 4 Hz smoothing means % plus-minus 4 Hz, i.e. a 8 Hz smoothing box. % cfg.foi = vector 1 x numfoi, frequencies of interest % cfg.taper = 'dpss', 'hanning' or many others, see WINDOW (default = 'dpss') % For cfg.output='powandcsd', you should specify the channel combinations % between which to compute the cross-spectra as cfg.channelcmb. Otherwise % you should specify only the channels in cfg.channel. % cfg.t_ftimwin = vector 1 x numfoi, length of time window (in seconds) % cfg.toi = vector 1 x numtoi, the times on which the analysis windows % should be centered (in seconds) % % WAVELET % WAVELET performs time-frequency analysis on any time series trial data % using the 'wavelet method' based on Morlet wavelets. Using mulitplication % in the frequency domain instead of convolution in the time domain. % cfg.foi = vector 1 x numfoi, frequencies of interest % OR % cfg.foilim = [begin end], frequency band of interest % cfg.toi = vector 1 x numtoi, the times on which the analysis windows % should be centered (in seconds) % cfg.width = 'width', or number of cycles, of the wavelet (default = 7) % cfg.gwidth = determines the length of the used wavelets in standard deviations % of the implicit Gaussian kernel and should be choosen % >= 3; (default = 3) % % The standard deviation in the frequency domain (sf) at frequency f0 is % defined as: sf = f0/width % The standard deviation in the temporal domain (st) at frequency f0 is % defined as: st = 1/(2*pi*sf) % % % TFR % TFR performs time-frequency analysis on any time series trial data % using the 'wavelet method' based on Morlet wavelets. Using convolution % in the time domain instead of multiplication in the frequency domain. % cfg.foi = vector 1 x numfoi, frequencies of interest % OR % cfg.foilim = [begin end], frequency band of interest % cfg.width = 'width', or number of cycles, of the wavelet (default = 7) % cfg.gwidth = determines the length of the used wavelets in standard deviations % of the implicit Gaussian kernel and should be choosen % >= 3; (default = 3) % % % % To facilitate data-handling and distributed computing you can use % cfg.inputfile = ... % cfg.outputfile = ... % If you specify one of these (or both) the input data will be read from a *.mat % file on disk and/or the output data will be written to a *.mat file. These mat % files should contain only a single variable, corresponding with the % input/output structure. % % See also % Guidelines for use in an analysis pipeline: % after FT_FREQANALYSIS you will have frequency or time-frequency % representations (TFRs) of the data, represented as power-spectra, % power and cross-spectra, or complex fourier-spectra, either for individual % trials or an average over trials. % This usually serves as input for one of the following functions: % * FT_FREQDESCRIPTIVES to compute descriptive univariate statistics % * FT_FREQSTATISTICS to perform parametric or non-parametric statistical tests % * FT_FREQBASELINE to perform baseline normalization of the spectra % * FT_FREQGRANDAVERAGE to compute the average spectra over multiple subjects or datasets % * FT_CONNECTIVITYANALYSIS to compute various measures of connectivity % Furthermore, the data can be visualised using the various plotting % functions, including: % * FT_SINGLEPLOTTFR to plot the TFR of a single channel or the average over multiple channels % * FT_TOPOPLOTTFR to plot the topographic distribution over the head % * FT_MULTIPLOTTFR to plot TFRs in a topographical layout % Undocumented local options: % cfg.method = 'hilbert'. Keeping this as undocumented as it does not make sense to use % in ft_freqanalysis unless the user is doing his own filter-padding % to remove edge-artifacts % cfg.correctt_ftimwin (set to yes to try to determine new t_ftimwins based % on correct cfg.foi) % Copyright (C) 2003-2006, F.C. Donders Centre, Pascal Fries % Copyright (C) 2004-2006, F.C. Donders Centre, Markus Siegel % Copyright (C) 2007-2012, DCCN, The FieldTrip team % % This file is part of FieldTrip, see http://www.ru.nl/neuroimaging/fieldtrip % for the documentation and details. % % FieldTrip is free software: you can redistribute it and/or modify % it under the terms of the GNU General Public License as published by % the Free Software Foundation, either version 3 of the License, or % (at your option) any later version. % % FieldTrip is distributed in the hope that it will be useful, % but WITHOUT ANY WARRANTY; without even the implied warranty of % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the % GNU General Public License for more details. % % You should have received a copy of the GNU General Public License % along with FieldTrip. If not, see . revision = '$Id: ft_freqanalysis.m 10202 2015-02-11 14:15:07Z jansch $'; % do the general setup of the function ft_defaults ft_preamble init ft_preamble provenance ft_preamble trackconfig ft_preamble debug ft_preamble loadvar data % the abort variable is set to true or false in ft_preamble_init if abort return end % ensure that the required options are present cfg.feedback = ft_getopt(cfg, 'feedback', 'text'); cfg.inputlock = ft_getopt(cfg, 'inputlock', []); % this can be used as mutex when doing distributed computation cfg.outputlock = ft_getopt(cfg, 'outputlock', []); % this can be used as mutex when doing distributed computation cfg.trials = ft_getopt(cfg, 'trials', 'all', 1); cfg.channel = ft_getopt(cfg, 'channel', 'all'); % check if the input data is valid for this function data = ft_checkdata(data, 'datatype', {'raw', 'raw+comp', 'mvar'}, 'feedback', cfg.feedback, 'hassampleinfo', 'yes'); % check if the input cfg is valid for this function cfg = ft_checkconfig(cfg, 'renamed', {'label', 'channel'}); cfg = ft_checkconfig(cfg, 'renamed', {'sgn', 'channel'}); cfg = ft_checkconfig(cfg, 'renamed', {'labelcmb', 'channelcmb'}); cfg = ft_checkconfig(cfg, 'renamed', {'sgncmb', 'channelcmb'}); cfg = ft_checkconfig(cfg, 'required', {'method'}); cfg = ft_checkconfig(cfg, 'renamedval', {'method', 'fft', 'mtmfft'}); cfg = ft_checkconfig(cfg, 'renamedval', {'method', 'convol', 'mtmconvol'}); cfg = ft_checkconfig(cfg, 'forbidden', {'latency'}); % see bug 1376 and 1076 cfg = ft_checkconfig(cfg, 'renamedval', {'method', 'wltconvol', 'wavelet'}); % select trials of interest tmpcfg = []; tmpcfg.trials = cfg.trials; tmpcfg.channel = cfg.channel; data = ft_selectdata(tmpcfg, data); % restore the provenance information [cfg, data] = rollback_provenance(cfg, data); % some proper error handling if isfield(data, 'trial') && numel(data.trial)==0 error('no trials were selected'); % this does not apply for MVAR data end if numel(data.label)==0 error('no channels were selected'); end % switch over method and do some of the method specfic checks and defaulting switch cfg.method case 'mtmconvol' cfg.taper = ft_getopt(cfg, 'taper', 'dpss'); if isequal(cfg.taper, 'dpss') && ~isfield(cfg, 'tapsmofrq') error('you must specify a smoothing parameter with taper = dpss'); end % check for foi above Nyquist if isfield(cfg,'foi') if any(cfg.foi > (data.fsample/2)) error('frequencies in cfg.foi are above Nyquist') end if isequal(cfg.taper, 'dpss') && not(isfield(cfg, 'tapsmofrq')) error('you must specify a smoothing parameter with taper = dpss'); end end cfg = ft_checkconfig(cfg, 'required', 'toi'); if ischar(cfg.toi) && strcmp(cfg.toi, 'all') % do an educated guess with respect to the requested time bins begtim = min(cellfun(@min,data.time)); endtim = max(cellfun(@max,data.time)); cfg.toi = linspace(begtim,endtim,round((endtim-begtim)./mean(diff(data.time{1})))+1); elseif ischar(cfg.toi) error('cfg.toi should be either a numeric vector, or can be ''all'''); end case 'mtmfft' cfg.taper = ft_getopt(cfg, 'taper', 'dpss'); if isequal(cfg.taper, 'dpss') && not(isfield(cfg, 'tapsmofrq')) error('you must specify a smoothing parameter with taper = dpss'); end % check for foi above Nyquist if isfield(cfg,'foi') if any(cfg.foi > (data.fsample/2)) error('frequencies in cfg.foi are above Nyquist') end end if isequal(cfg.taper, 'dpss') && not(isfield(cfg, 'tapsmofrq')) error('you must specify a smoothing parameter with taper = dpss'); end case 'wavelet' cfg.width = ft_getopt(cfg, 'width', 7); cfg.gwidth = ft_getopt(cfg, 'gwidth', 3); case 'tfr' cfg = ft_checkconfig(cfg, 'renamed', {'waveletwidth', 'width'}); cfg = ft_checkconfig(cfg, 'unused', {'downsample'}); cfg.width = ft_getopt(cfg, 'width', 7); cfg.gwidth = ft_getopt(cfg, 'gwidth', 3); case 'hilbert' warning('method = hilbert requires user action to deal with filtering-artifacts') if ~isfield(cfg, 'filttype'), cfg.filttype = 'but'; end if ~isfield(cfg, 'filtorder'), cfg.filtorder = 4; end if ~isfield(cfg, 'filtdir'), cfg.filtdir = 'twopass'; end if ~isfield(cfg, 'width'), cfg.width = 1; end case 'mvar' if isfield(cfg, 'inputfile') freq = feval(@ft_freqanalysis_mvar,cfg); else freq = feval(@ft_freqanalysis_mvar,cfg,data); end return otherwise error('specified cfg.method is not supported') end % set all the defaults cfg.pad = ft_getopt(cfg, 'pad', 'maxperlen'); cfg.padtype = ft_getopt(cfg, 'padtype', 'zero'); cfg.output = ft_getopt(cfg, 'output', 'pow'); cfg.calcdof = ft_getopt(cfg, 'calcdof', 'no'); cfg.channel = ft_getopt(cfg, 'channel', 'all'); cfg.precision = ft_getopt(cfg, 'precision', 'double'); cfg.foi = ft_getopt(cfg, 'foi', []); cfg.foilim = ft_getopt(cfg, 'foilim', []); cfg.correctt_ftimwin = ft_getopt(cfg, 'correctt_ftimwin', 'no'); cfg.polyremoval = ft_getopt(cfg, 'polyremoval', 0); % keeptrials and keeptapers should be conditional on cfg.output, % cfg.output = 'fourier' should always output tapers if strcmp(cfg.output, 'fourier') cfg.keeptrials = ft_getopt(cfg, 'keeptrials', 'yes'); cfg.keeptapers = ft_getopt(cfg, 'keeptapers', 'yes'); if strcmp(cfg.keeptrials, 'no') || strcmp(cfg.keeptapers, 'no'), error('cfg.output = ''fourier'' requires cfg.keeptrials = ''yes'' and cfg.keeptapers = ''yes'''); end else cfg.keeptrials = ft_getopt(cfg, 'keeptrials', 'no'); cfg.keeptapers = ft_getopt(cfg, 'keeptapers', 'no'); end % set flags for keeping trials and/or tapers if strcmp(cfg.keeptrials,'no') && strcmp(cfg.keeptapers,'no') keeprpt = 1; elseif strcmp(cfg.keeptrials,'yes') && strcmp(cfg.keeptapers,'no') keeprpt = 2; elseif strcmp(cfg.keeptrials,'no') && strcmp(cfg.keeptapers,'yes') error('There is currently no support for keeping tapers WITHOUT KEEPING TRIALS.'); elseif strcmp(cfg.keeptrials,'yes') && strcmp(cfg.keeptapers,'yes') keeprpt = 4; end if strcmp(cfg.keeptrials,'yes') && strcmp(cfg.keeptapers,'yes') if ~strcmp(cfg.output, 'fourier'), error('Keeping trials AND tapers is only possible with fourier as the output.'); end end % Set flags for output if strcmp(cfg.output,'pow') powflg = 1; csdflg = 0; fftflg = 0; elseif strcmp(cfg.output,'powandcsd') powflg = 1; csdflg = 1; fftflg = 0; elseif strcmp(cfg.output,'fourier') powflg = 0; csdflg = 0; fftflg = 1; else error('Unrecognized output required'); end % prepare channel(cmb) if ~isfield(cfg, 'channelcmb') && csdflg %set the default for the channelcombination cfg.channelcmb = {'all' 'all'}; elseif isfield(cfg, 'channelcmb') && ~csdflg % no cross-spectrum needs to be computed, hence remove the combinations from cfg cfg = rmfield(cfg, 'channelcmb'); end if isfield(cfg, 'channelcmb') % the channels in the data are already the subset according to cfg.channel cfg.channelcmb = ft_channelcombination(cfg.channelcmb, data.label); end % determine the corresponding indices of all channels chanind = match_str(data.label, cfg.channel); nchan = size(chanind,1); if csdflg assert(nchan>1, 'CSD output requires multiple channels'); % determine the corresponding indices of all channel combinations [dummy,chancmbind(:,1)] = match_str(cfg.channelcmb(:,1), data.label); [dummy,chancmbind(:,2)] = match_str(cfg.channelcmb(:,2), data.label); nchancmb = size(chancmbind,1); chanind = unique([chanind(:); chancmbind(:)]); nchan = length(chanind); cutdatindcmb = zeros(size(chancmbind)); for ichan = 1:nchan cutdatindcmb(chancmbind == chanind(ichan)) = ichan; end end % determine trial characteristics ntrials = numel(data.trial); trllength = zeros(1, ntrials); for itrial = 1:ntrials trllength(itrial) = size(data.trial{itrial}, 2); end if strcmp(cfg.pad, 'maxperlen') padding = max(trllength); cfg.pad = padding/data.fsample; else padding = cfg.pad*data.fsample; if padding1 powspctrm(:,:,hasdc_nyq,:) = powspctrm(:,:,hasdc_nyq,:)./2; else powspctrm(:,hasdc_nyq,:) = powspctrm(:,hasdc_nyq,:)./2; end end freq.powspctrm = powspctrm; end if fftflg % correct the 0 Hz or Nyqist bin if present, scaling with a factor of 2 is only appropriate for ~0 Hz if ~isempty(hasdc_nyq) if keeprpt>1 fourierspctrm(:,:,hasdc_nyq,:) = fourierspctrm(:,:,hasdc_nyq,:)./sqrt(2); else fourierspctrm(:,hasdc_nyq,:) = fourierspctrm(:,hasdc_nyq,:)./sqrt(2); end end freq.fourierspctrm = fourierspctrm; end if csdflg % correct the 0 Hz or Nyqist bin if present, scaling with a factor of 2 is only appropriate for ~0 Hz if ~isempty(hasdc_nyq) if keeprpt>1 crsspctrm(:,:,hasdc_nyq,:) = crsspctrm(:,:,hasdc_nyq,:)./2; else crsspctrm(:,hasdc_nyq,:) = crsspctrm(:,hasdc_nyq,:)./2; end end freq.labelcmb = cfg.channelcmb; freq.crsspctrm = crsspctrm; end if strcmp(cfg.calcdof, 'yes'); freq.dof = 2 .* dof; end; if strcmp(cfg.method, 'mtmfft') && (keeprpt == 2 || keeprpt == 4) freq.cumsumcnt = trllength'; end if exist('cumtapcnt', 'var') && (keeprpt == 2 || keeprpt == 4) freq.cumtapcnt = cumtapcnt; end % backwards compatability of foilim if ~isempty(cfg.foilim) cfg = rmfield(cfg, 'foi'); else cfg = rmfield(cfg, 'foilim'); end % some fields from the input should always be copied over in the output freq = copyfields(data, freq, {'grad', 'elec', 'topo', 'topolabel', 'unmixing'}); if isfield(data, 'trialinfo') && strcmp(cfg.keeptrials, 'yes') % copy the trialinfo into the output, but not the sampleinfo freq.trialinfo = data.trialinfo; end % do the general cleanup and bookkeeping at the end of the function ft_postamble debug ft_postamble trackconfig ft_postamble provenance ft_postamble previous data ft_postamble history freq ft_postamble savevar freq