-
Notifications
You must be signed in to change notification settings - Fork 162
/
process_ft_freqstatistics.m
353 lines (326 loc) · 16.2 KB
/
process_ft_freqstatistics.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
function varargout = process_ft_freqstatistics( varargin )
% PROCESS_FT_FREQSTATISTICS Call FieldTrip function ft_freqstatistics.
%
% Reference: http://www.fieldtriptoolbox.org/reference/ft_freqstatistics
% @=============================================================================
% This function is part of the Brainstorm software:
% https://neuroimage.usc.edu/brainstorm
%
% Copyright (c) University of Southern California & McGill University
% This software is distributed under the terms of the GNU General Public License
% as published by the Free Software Foundation. Further details on the GPLv3
% license can be found at http://www.gnu.org/copyleft/gpl.html.
%
% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE
% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY
% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY
% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE.
%
% For more information type "brainstorm license" at command prompt.
% =============================================================================@
%
% Authors: Arnaud Gloaguen, Francois Tadel, 2015-2016
eval(macro_method);
end
%% ===== GET DESCRIPTION =====
function sProcess = GetDescription() %#ok<DEFNU>
% Description the process
sProcess.Comment = 'FieldTrip: ft_freqstatistics';
sProcess.Category = 'Stat2';
sProcess.SubGroup = 'Test';
sProcess.Index = 132;
sProcess.Description = 'https://neuroimage.usc.edu/brainstorm/Tutorials/Statistics';
% Definition of the input accepted by this process
sProcess.InputTypes = {'timefreq'};
sProcess.OutputTypes = {'ptimefreq'};
sProcess.nInputs = 2;
sProcess.nMinFiles = 2;
sProcess.isSeparator = 1;
% Definition of the options
sProcess = process_ft_timelockstatistics('DefineStatOptions', sProcess);
end
%% ===== FORMAT COMMENT =====
function Comment = FormatComment(sProcess) %#ok<DEFNU>
Comment = process_ft_timelockstatistics('FormatComment', sProcess);
end
%% ===== RUN =====
function sOutput = Run(sProcess, sInputsA, sInputsB) %#ok<DEFNU>
% Initialize returned variable
sOutput = [];
% Initialize FieldTrip
[isInstalled, errMsg] = bst_plugin('Install', 'fieldtrip');
if ~isInstalled
bst_report('Error', sProcess, [], errMsg);
return;
end
bst_plugin('SetProgressLogo', 'fieldtrip');
% ===== CHECK INPUTS =====
% Make sure that file type is indentical for both sets
if ~isempty(sInputsA) && ~isempty(sInputsB) && ~strcmpi(sInputsA(1).FileType, sInputsB(1).FileType)
bst_report('Error', sProcess, sInputsA, 'Cannot process inputs from different types.');
return;
end
% Check the number of files in input
if (length(sInputsA) < 2) || (length(sInputsB) < 2)
bst_report('Error', sProcess, sInputsA, 'Not enough files in input.');
return;
end
% Check that channel files are available
if any(cellfun(@isempty, {sInputsA.ChannelFile, sInputsB.ChannelFile}))
bst_report('Error', sProcess, sInputsA, 'Channel files are missing for the input files.');
return;
end
% ===== GET OPTIONS =====
OPT = process_ft_timelockstatistics('GetStatOptions', sProcess);
% Number of files
nFilesA = length(sInputsA);
nFilesB = length(sInputsB);
% Check number of files
if (nFilesA ~= nFilesB) && strcmpi(OPT.StatisticType, 'depsamplesT')
bst_report('Error', sProcess, [], 'For a paired t-test, the number of files must be the same in the two groups.');
return;
end
% ===== LOAD INPUT CHANNEL FILES =====
bst_progress('text', 'Reading channel files...');
% Get all the channel files involved
uniqueChannelFiles = unique({sInputsA.ChannelFile, sInputsB.ChannelFile});
% Load all the channel files
AllChannelMats = cell(1, length(uniqueChannelFiles));
for i = 1:length(uniqueChannelFiles)
AllChannelMats{i} = in_bst_channel(uniqueChannelFiles{i});
% Make sure that the list of sensors is the same
if (i > 1) && ~isequal({AllChannelMats{1}.Channel.Name}, {AllChannelMats{i}.Channel.Name})
bst_report('Warning', sProcess, sInputsA, ['The list of channels in all the input files do not match.' 10 ...
'You will not be able to display topography plots of the sensor values.' 10 ...
'If you need this feature, run the process "Standardize > Uniform list of channels" first.']);
AllChannelMats = [];
break;
end
end
% ===== OUTPUT CHANNEL FILE =====
if ~isempty(AllChannelMats)
% Get output channel study
iOutputStudy = sProcess.options.iOutputStudy;
[sChannel, iChanStudy] = bst_get('ChannelForStudy', iOutputStudy);
% If there is one channel already existing: use it
if ~isempty(sChannel)
OutChannelMat = in_bst_channel(sChannel.FileName);
% Make sure that the list of sensors is the same
if ~isequal({OutChannelMat.Channel.Name}, {AllChannelMats{1}.Channel.Name})
bst_report('Warning', sProcess, sInputsA, ['The list of channels in the input files does not match the output channel file:' 10 sChannel.FileName]);
end
% Else: Compute an average of all the channel files
else
% Compute average
OutChannelMat = channel_average(AllChannelMats);
% Save new channel file
db_set_channel(iOutputStudy, OutChannelMat, 0, 0);
end
end
% ===== CREATE FIELDTRIP STRUCTURES =====
% Load all the files in the same structure
sAllInputs = [sInputsA, sInputsB];
ftAllFiles = cell(1, length(sAllInputs));
neighbours = [];
for i = 1:length(sAllInputs)
bst_progress('text', sprintf('Reading input files... [%d/%d]', i, length(sAllInputs)));
% Convert file to a FieldTrip structure
if (i == 1)
[ftAllFiles{i}, TimefreqMat, neighbours] = out_fieldtrip_timefreq(sAllInputs(i).FileName, sAllInputs(i).ChannelFile);
else
[ftAllFiles{i}, TimefreqMat] = out_fieldtrip_timefreq(sAllInputs(i).FileName, sAllInputs(i).ChannelFile);
end
% Time selection
if ~isempty(OPT.TimeWindow) && (size(ftAllFiles{i}.powspctrm,2) > 1)
iTime = panel_time('GetTimeIndices', TimefreqMat.Time, OPT.TimeWindow);
ftAllFiles{i}.powspctrm = ftAllFiles{i}.powspctrm(:,iTime,:);
ftAllFiles{i}.time = ftAllFiles{i}.time(iTime);
if ~isempty(TimefreqMat.TFmask)
TimefreqMat.TFmask = TimefreqMat.TFmask(:,iTime);
end
end
% Save time vector for output
if (i == 1)
if (length(TimefreqMat.Time) == 1)
sfreq = 1000;
else
sfreq = 1/(TimefreqMat.Time(2) - TimefreqMat.Time(1));
end
OutTime = ftAllFiles{i}.time;
if (length(OutTime) == 1)
OutTime = OutTime + [0, 1/sfreq];
end
TFmask = TimefreqMat.TFmask;
% Following files
else
% Combine TFmasks
if ~isempty(TFmask) && isequal(size(TFmask), size(TimefreqMat.TFmask))
TFmask = TFmask & TimefreqMat.TFmask;
end
end
% Absolue value
if OPT.isAbsolute
ftAllFiles{i}.powspctrm = abs(ftAllFiles{i}.powspctrm);
end
% Channel average
if OPT.isAvgChan && (size(ftAllFiles{i}.powspctrm,1) > 1)
ftAllFiles{i}.powspctrm = mean(ftAllFiles{i}.powspctrm, 1);
ftAllFiles{i}.label = {'avgchan'};
end
% Time average
if OPT.isAvgTime && (size(ftAllFiles{i}.powspctrm,2) > 1)
ftAllFiles{i}.powspctrm = mean(ftAllFiles{i}.powspctrm, 2);
ftAllFiles{i}.time = ftAllFiles{i}.time(1);
if (i == 1)
OutTime = OutTime([1,end]);
end
end
% Frequency average
if OPT.isAvgFreq && (size(ftAllFiles{i}.powspctrm,3) > 1)
ftAllFiles{i}.powspctrm = mean(ftAllFiles{i}.powspctrm, 3);
ftAllFiles{i}.freq = ftAllFiles{i}.freq(1);
end
% Check time time vectors
if (i == 1)
% Nothing
elseif ~isequal(size(ftAllFiles{i}.powspctrm,2), size(ftAllFiles{1}.powspctrm,2))
bst_report('Error', sProcess, [], sprintf('All the files must have the same number of time samples.\nFile #%d has %d samples, file #%d has %d samples.', 1, size(ftAllFiles{1}.powspctrm,2), i, size(ftAllFiles{i}.powspctrm,2)));
return;
elseif ~isequal(size(ftAllFiles{i}.powspctrm,3), size(ftAllFiles{1}.powspctrm,3))
bst_report('Error', sProcess, [], sprintf('All the files must have the same number of frequency bins.\nFile #%d has %d samples, file #%d has %d bins.', 1, size(ftAllFiles{1}.powspctrm,3), i, size(ftAllFiles{i}.powspctrm,3)));
return;
% elseif (abs(ftAllFiles{1}.freq(1) - ftAllFiles{i}.freq(1)) > 0)
% bst_report('Error', sProcess, [], 'The frequency definitions of the input files do not match.');
% return;
% elseif (abs(ftAllFiles{1}.time(1) - ftAllFiles{i}.time(1)) > 1e-6)
% bst_report('Error', sProcess, [], 'The time definitions of the input files do not match.');
% return;
% Only one time point: use the time of the first file
elseif (length(ftAllFiles{i}.time) == 1)
ftAllFiles{i}.time = ftAllFiles{1}.time;
end
end
% ===== CALL FIELDTRIP =====
bst_progress('text', 'Calling FieldTrip function: ft_freqstatistics...');
% Input options
statcfg = struct();
statcfg.channel = 'all'; % Channel selection already done so equal to 'all'
statcfg.latency = 'all'; % Time selection already done so equal to 'all'
statcfg.frequency = 'all'; % Frequency selection already done so equal to 'all'
statcfg.avgovertime = 'no'; % Time average already done so equal to 'no'
statcfg.avgchan = 'no'; % Space average already done so equal to 'no'
statcfg.avgoverfreq = 'no'; % Frequency average already done so equal to 'no'
% Different methods for calculating the significance probability and/or critical value
% cfg.method = 'montecarlo' get Monte-Carlo estimates of the significance probabilities and/or critical values from the permutation distribution
% 'analytic' get significance probabilities and/or critical values from the analytic reference distribution (typically, the sampling distribution under the null hypothesis),
% 'stats' use a parametric test from the MATLAB statistics toolbox,
% 'crossvalidate' use crossvalidation to compute predictive performance
statcfg.method = 'montecarlo';
statcfg.numrandomization = OPT.Randomizations;
% Possible statistics to applied for each samples:
% cfg.statistic = 'indepsamplesT' independent samples T-statistic,
% 'indepsamplesF' independent samples F-statistic,
% 'indepsamplesregrT' independent samples regression coefficient T-statistic,
% 'indepsamplesZcoh' independent samples Z-statistic for coherence,
% 'depsamplesT' dependent samples T-statistic,
% 'depsamplesFmultivariate' dependent samples F-statistic MANOVA,
% 'depsamplesregrT' dependent samples regression coefficient T-statistic,
% 'actvsblT' activation versus baseline T-statistic.
statcfg.statistic = OPT.StatisticType;
statcfg.tail = OPT.Tail;
statcfg.correcttail = 'prob';
statcfg.parameter = 'powspctrm'; % parameter in FieldTrip structure on which the stats will be applied
% Define the design of the experiment
switch (OPT.StatisticType)
case 'indepsamplesT'
statcfg.design = zeros(1, nFilesA + nFilesB);
statcfg.design(1,:) = [ones(1,nFilesA), 2*ones(1,nFilesB)];
statcfg.ivar = 1; % the one and only row in cfg.design contains the independent variable
case 'depsamplesT'
statcfg.design = zeros(2, nFilesA + nFilesB);
statcfg.design(1,:) = [ones(1,nFilesA), 2*ones(1,nFilesB)];
statcfg.design(2,:) = [1:nFilesA 1:nFilesB];
statcfg.ivar = 1; % the 1st row in cfg.design contains the independent variable
statcfg.uvar = 2; % the 2nd row in cfg.design contains the subject number (or trial number)
end
% Multiple-comparison correction
statcfg.correctm = OPT.Correction;
% Additional parameters for the method
switch (OPT.Correction)
case 'no'
case {'cluster', 'tfce'}
% Define parameters for cluster statistics
statcfg.clusteralpha = OPT.ClusterAlphaValue;
statcfg.clustertail = statcfg.tail;
statcfg.minnbchan = OPT.MinNbChan;
statcfg.clusterstatistic = OPT.ClusterStatistic;
statcfg.neighbours = neighbours;
case 'bonferroni'
case 'fdr'
case 'max'
case 'holm'
case 'hochberg'
end
% Main function that will compute the statistics
ftStat = ft_freqstatistics(statcfg, ftAllFiles{:});
% Error management
if ~isfield(ftStat, 'prob') || ~isfield(ftStat, 'stat') || isempty(ftStat.prob) || isempty(ftStat.stat)
bst_report('Error', sProcess, [], 'Unknown error: The function ft_freqstatistics did not return anything.');
return;
end
% Apply thresholded mask on the p-values (the prob map is already thresholded for clusters)
if ~ismember(OPT.Correction, {'no', 'cluster', 'tfce'})
ftStat.prob(~ftStat.mask) = .999;
end
% Replace NaN values with zeros
ftStat.stat(isnan(ftStat.stat)) = 0;
% ===== OUTPUT STRUCTURE =====
sOutput = db_template('statmat');
sOutput.tmap = ftStat.stat;
sOutput.pmap = ftStat.prob;
sOutput.RowNames = ftStat.label;
sOutput.df = [];
sOutput.Correction = OPT.Correction;
sOutput.ColormapType = 'stat2';
sOutput.DisplayUnits = 't';
sOutput.TFmask = TFmask;
% Save clusters
if isfield(ftStat, 'posclusters')
sOutput.StatClusters.posclusters = ftStat.posclusters;
sOutput.StatClusters.posdistribution = ftStat.posdistribution;
sOutput.StatClusters.posclusterslabelmat = ftStat.posclusterslabelmat;
end
if isfield(ftStat, 'negclusters')
sOutput.StatClusters.negclusters = ftStat.negclusters;
sOutput.StatClusters.negdistribution = ftStat.negdistribution;
sOutput.StatClusters.negclusterslabelmat = ftStat.negclusterslabelmat;
end
% Time: If there is only one time point, replicate to have two
if (length(ftStat.time) == 1)
sOutput.Time = [OutTime(1), OutTime(end)];
sOutput.tmap(:,2,:) = sOutput.tmap(:,1,:);
sOutput.pmap(:,2,:) = sOutput.pmap(:,1,:);
if isfield(ftStat, 'posclusterslabelmat') && ~isempty(ftStat.posclusterslabelmat)
sOutput.StatClusters.posclusterslabelmat(:,2,:) = sOutput.StatClusters.posclusterslabelmat(:,1,:);
end
if isfield(ftStat, 'negclusterslabelmat') && ~isempty(ftStat.negclusterslabelmat)
sOutput.StatClusters.negclusterslabelmat(:,2,:) = sOutput.StatClusters.negclusterslabelmat(:,1,:);
end
else
sOutput.Time = ftStat.time;
end
% Save FieldTrip configuration structure
sOutput.cfg = statcfg;
if isfield(sOutput.cfg, 'neighbours')
sOutput.cfg = rmfield(sOutput.cfg, 'neighbours');
end
if isfield(ftStat, 'cfg') && isfield(ftStat.cfg, 'version')
sOutput.cfg.version = ftStat.cfg.version;
end
% Save options
sOutput.Options = OPT;
% Last message
bst_progress('text', 'Saving results...');
bst_plugin('SetProgressLogo', []);
end