-
Notifications
You must be signed in to change notification settings - Fork 247
/
pop_resample.m
461 lines (395 loc) · 17.8 KB
/
pop_resample.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
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
% POP_RESAMPLE - resample dataset (pop up window).
%
% Usage:
% >> [OUTEEG] = pop_resample( INEEG ); % pop up interactive window
% >> [OUTEEG] = pop_resample( INEEG, freq);
% >> [OUTEEG] = pop_resample( INEEG, freq, fc, df);
%
% Graphical interface:
% The edit box entitled "New sampling rate" contains the frequency in
% Hz for resampling the data. Entering a value in this box is the same
% as providing it in the 'freq' input from the command line.
%
% Inputs:
% INEEG - input dataset
% freq - frequency to resample (Hz)
%
% Optional inputs:
% fc - anti-aliasing filter cutoff (pi rad / sample)
% {default 0.9}
% df - anti-aliasing filter transition band width (pi rad /
% sample) {default 0.2}
%
% Outputs:
% OUTEEG - output dataset
%
% Author: Arnaud Delorme, CNL/Salk Institute, 2001
%
% Note: uses the RESAMPLE function from the signal processing toolbox
% if present. Otherwise use griddata interpolation method (it should be
% reprogrammed using spline interpolation for speed up).
%
% See also: RESAMPLE, EEGLAB
% Copyright (C) 2001 Arnaud Delorme, Salk Institute, arno@salk.edu
%
% This file is part of EEGLAB, see http://www.eeglab.org
% for the documentation and details.
%
% Redistribution and use in source and binary forms, with or without
% modification, are permitted provided that the following conditions are met:
%
% 1. Redistributions of source code must retain the above copyright notice,
% this list of conditions and the following disclaimer.
%
% 2. Redistributions in binary form must reproduce the above copyright notice,
% this list of conditions and the following disclaimer in the documentation
% and/or other materials provided with the distribution.
%
% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
% THE POSSIBILITY OF SUCH DAMAGE.
% 01-25-02 reformated help & license -ad
% 03-08-02 remove ica activity resampling (now set to []) -ad
% 03-08-02 debug call to function help -ad
% 04-05-02 recompute event latencies -ad
function [EEG, command] = pop_resample( EEG, freq, fc, df)
command = '';
if nargin < 1
help pop_resample;
return;
end
if isempty(EEG(1).data)
disp('Pop_resample error: cannot resample empty dataset'); return;
end
if ~plugin_askinstall('Firfilt', 'pop_firwsord'), return; end
if nargin < 2
% popup window parameters
% -----------------------
promptstr = {['New sampling rate']};
inistr = { num2str(EEG(1).srate) };
result = inputdlg2( promptstr, 'Resample current dataset -- pop_resample()', 1, inistr, 'pop_resample');
if length(result) == 0
return;
end
freq = eval( result{1} );
end
% Default cutoff frequency (pi rad / smp)
if nargin < 3 || isempty(fc)
fc = 0.9;
end
% Default transition band width (pi rad / smp)
if nargin < 4 || isempty(df)
df = 0.2;
end
% fc in range?
if fc < 0 || fc > 1
error('Anti-aliasing filter cutoff frequency out of range.')
end
% process multiple datasets
% -------------------------
if length(EEG) > 1
if nargin < 2
[ EEG, command ] = eeg_eval( 'pop_resample', EEG, 'warning', 'on', 'params', { freq } );
else
[ EEG, command ] = eeg_eval( 'pop_resample', EEG, 'params', { freq } );
end
return;
end
% finding the best ratio
% [p,q] = rat(freq/EEG.srate, 0.0001) % not used right now
[p,q] = rat(freq/EEG.srate, 1e-12); % used! AW
% set variable
% ------------
EEG.data = reshape(EEG.data, EEG.nbchan, EEG.pnts, EEG.trials);
oldpnts = EEG.pnts;
% resample for multiple channels
% -------------------------
if isfield(EEG, 'event') && ~isempty(EEG.event) && isfield(EEG.event, 'type') && ischar(EEG.event(1).type)
tmpevent = EEG.event;
bounds = eeg_findboundaries(tmpevent);
if ~isempty(bounds)
disp('Data break detected and taken into account for resampling');
bounds = [ tmpevent(bounds).latency ];
bounds(bounds <= 0 | bounds > size(EEG.data,2)) = []; % Remove out of range boundaries
bounds(mod(bounds, 1) ~= 0) = round(bounds(mod(bounds, 1) ~= 0) + 0.5); % Round non-integer boundary latencies
end
bounds = [1 bounds size(EEG.data, 2) + 1]; % Add initial and final boundary event
bounds = unique(bounds); % Sort (!) and remove doublets
else
bounds = [1 size(EEG.data,2) + 1]; % [1:size(EEG.data,2):size(EEG.data,2)*size(EEG.data,3)+1];
end
eeglab_options;
if option_donotusetoolboxes
usesigproc = 0;
elseif exist('resample') == 2
if any(findstr(which('resample.m'), 'fieldtrip'))
usesigproc = 0;
else
usesigproc = 1;
end
else
usesigproc = 0;
disp('Signal Processing Toolbox absent: using custom interpolation instead of resample() function.');
disp('This method uses cubic spline interpolation after anti-aliasing (see >> help spline)');
end
fprintf('resampling data %3.4f Hz\n', EEG.srate*p/q);
eeglab_options;
for index1 = 1:size(EEG.data,1)
fprintf('%d ', index1);
sigtmp = reshape(EEG.data(index1,:, :), oldpnts, EEG.trials);
if index1 == 1
tmpres = [];
indices = [1];
for ind = 1:length(bounds)-1
tmpres = [ tmpres; myresample( double( sigtmp(bounds(ind):bounds(ind+1)-1,:)), p, q, usesigproc, fc, df ) ];
indices = [ indices size(tmpres,1)+1 ];
end
if size(tmpres,1) == 1, EEG.pnts = size(tmpres,2);
else EEG.pnts = size(tmpres,1);
end
if option_memmapdata == 1
tmpeeglab = mmo([], [EEG.nbchan, EEG.pnts, EEG.trials]);
else tmpeeglab = zeros(EEG.nbchan, EEG.pnts, EEG.trials);
end
else
for ind = 1:length(bounds)-1
tmpres(indices(ind):indices(ind+1)-1,:) = myresample( double( sigtmp(bounds(ind):bounds(ind+1)-1,:) ), p, q, usesigproc, fc, df);
end
end;
tmpeeglab(index1,:, :) = tmpres;
end
fprintf('\n');
EEG.srate = EEG.srate*p/q;
EEG.data = tmpeeglab;
EEG.pnts = size(EEG.data,2);
EEG.xmax = EEG.xmin + (EEG.pnts-1)/EEG.srate; % cko: recompute xmax, since we may have removed a few of the trailing samples
EEG.times = linspace(EEG.xmin*1000, EEG.xmax*1000, EEG.pnts);
% recompute all event latencies
% -----------------------------
if isfield(EEG.event, 'latency')
fprintf('resampling event latencies...\n');
if EEG.trials > 1 % Epoched data; not recommended
warning( 'Resampling of epoched data is not recommended (due to anti-aliasing filtering)! Note: For epoched datasets recomputing urevent latencies is not supported. The urevent structure will be cleared.' )
for iEvt = 1:length( EEG.event )
EEG.event( iEvt ).latency = ( EEG.event( iEvt ).latency - ( EEG.event(iEvt).epoch - 1 ) * oldpnts - 1 ) * p / q + ( EEG.event(iEvt).epoch - 1 ) * EEG.pnts + 1;
% % Recompute event latencies
if isfield(EEG.event, 'duration') && ~isempty(EEG.event(iEvt).duration)
EEG.event( iEvt ).duration = EEG.event( iEvt ).duration * p/q;
end
end
EEG.urevent = [];
else % Continuous data
eeglab_options;
for iEvt = 1:length(EEG.event)
% From >> help resample: Y is P/Q times the length of X (or the
% ceiling of this if P/Q is not an integer).
% That is, recomputing event latency by pnts / oldpnts will give
% inaccurate results in case of multiple segments and rounded segment
% length. Error is accumulated and can lead to several samples offset.
% Blocker for boundary events.
% Old version EEG.event(index1).latency = EEG.event(index1).latency * EEG.pnts /oldpnts;
% Recompute event latencies relative to segment onset
if eeg_isboundary(EEG.event(iEvt), option_boundary99) && mod(EEG.event(iEvt).latency, 1) == 0.5 % Workaround to keep EEGLAB style boundary events at -0.5 latency relative to DC event; actually incorrect
iBnd = sum(EEG.event(iEvt).latency + 0.5 >= bounds);
EEG.event(iEvt).latency = indices(iBnd) - 0.5;
else
iBnd = sum(EEG.event(iEvt).latency >= bounds);
if iBnd > 0
EEG.event(iEvt).latency = (EEG.event(iEvt).latency - bounds(iBnd)) * p / q + indices(iBnd);
end
end
% Recompute event duration relative to segment onset
if isfield(EEG.event, 'duration') && ~isempty(EEG.event(iEvt).duration)
EEG.event( iEvt ).duration = EEG.event( iEvt ).duration * p/q;
end
end
if isfield(EEG, 'urevent') && isfield(EEG.urevent, 'latency')
try
for iUrevt = 1:length(EEG.urevent)
isBoundaryEvent = false;
if ischar( EEG.urevent(iUrevt).type )
isBoundaryEvent = strcmpi(EEG.urevent(iUrevt).type, 'boundary');
elseif option_boundary99
isBoundaryEvent = EEG.urevent(iUrevt).type == -99;
end
% Recompute urevent latencies relative to segment onset
if isBoundaryEvent && mod(EEG.urevent(iUrevt).latency, 1) == 0.5 % Workaround to keep EEGLAB style boundary events at -0.5 latency relative to DC event; actually incorrect
iBnd = sum(EEG.urevent(iUrevt).latency + 0.5 >= bounds);
EEG.urevent(iUrevt).latency = indices(iBnd) - 0.5;
else
iBnd = sum(EEG.urevent(iUrevt).latency >= bounds);
EEG.urevent(iUrevt).latency = (EEG.urevent(iUrevt).latency - bounds(iBnd)) * p / q + indices(iBnd);
end
end
catch
disp('pop_resample warning: ''urevent'' problem, reinitializing urevents');
EEG = rmfield(EEG, 'urevent');
end
end
end
EEG = eeg_checkset(EEG, 'eventconsistency');
end
% resample for multiple channels ica
EEG.icaact = [];
% store dataset
fprintf('resampling finished\n');
EEG.setname = [EEG.setname ' resampled'];
command = sprintf('EEG = pop_resample( EEG, %d);', freq);
return;
% resample if resample is not present
% -----------------------------------
function tmpeeglab = myresample(data, p, q, usesigproc, fc, df)
if length(data) < 2
tmpeeglab = data;
return;
end
%if size(data,2) == 1, data = data'; end
if usesigproc
% padding to avoid artifacts at the beginning and at the end
% Andreas Widmann May 5, 2011
%The pop_resample command introduces substantial artifacts at beginning and end
%of data when raw data show DC offset (e.g. as in DC recorded continuous files)
%when MATLAB Signal Processing Toolbox is present (and MATLAB resample.m command
%is used).
%Even if this artifact is short, it is a filtered DC offset and will be carried
%into data, e.g. by later highpass filtering to a substantial amount (easily up
%to several seconds).
%The problem can be solved by padding the data at beginning and end by a DC
%constant before resampling.
% N = 10; % Resample default
% nPad = ceil((max(p, q) * N) / q) * q; % # datapoints to pad, round to integer multiple of q for unpadding
% tmpeeglab = resample([data(ones(1, nPad), :); data; data(end * ones(1, nPad), :)], pnts, new_pnts);
% Conservative custom anti-aliasing FIR filter, see bug 1757
nyq = 1 / max([p q]);
fc = fc * nyq; % Anti-aliasing filter cutoff frequency
df = df * nyq; % Anti-aliasing filter transition band width
m = pop_firwsord('kaiser', 2, df, 0.002); % Anti-aliasing filter kernel
b = firws(m, fc, windows('kaiser', m + 1, 5)); % Anti-aliasing filter kernel
b = p * b; % Normalize filter kernel to inserted zeros
% figure; freqz(b, 1, 2^14, q * 1000) % Debugging only! Sampling rate hardcoded as it is unknown in this context. Manually adjust for debugging!
% Padding, see bug 1017
nPad = ceil((m / 2) / q) * q; % Datapoints to pad, round to integer multiple of q for unpadding
startPad = repmat(data(1, :), [nPad 1]);
endPad = repmat(data(end, :), [nPad 1]);
% Resampling
tmpeeglab = resample([startPad; data; endPad], p, q, b(:));
% Remove padding
nPad = nPad * p / q; % # datapoints to unpad
tmpeeglab = tmpeeglab(nPad + 1:end - nPad, :); % Remove padded data
else % No Signal Processing toolbox
% anti-alias filter
% -----------------
% data = eegfiltfft(data', 256, 0, 128*pnts/new_pnts); % Downsample from 256 to 128 times the ratio of freq.
% % Code was verified by Andreas Widdman March 2014
% % No! Only cutoff frequency for downsampling was confirmed.
% % Upsampling doesn't work and FFT filtering introduces artifacts.
% % Also see bug 1757. Replaced May 05, 2015, AW
if p < q, nyq = p / q; else nyq = q / p; end
fc = fc * nyq; % Anti-aliasing filter cutoff frequency
df = df * nyq; % Anti-aliasing filter transition band width
m = pop_firwsord('kaiser', 2, df, 0.002); % Anti-aliasing filter kernel
b = firws(m, fc, windows('kaiser', m + 1, 5)); % Anti-aliasing filter kernel
% figure; freqz(b, 1, 2^14, 1000) % Debugging only! Sampling rate hardcoded as it is unknown in this context. Manually adjust for debugging!
if p < q % Downsampling, anti-aliasing filter
data = firfiltdcpadded(b, data, 0);
end
% spline interpolation
% --------------------
% X = [1:length(data)];
% nbnewpoints = length(data)*p/q;
% nbnewpoints2 = ceil(nbnewpoints);
% lastpointval = length(data)/nbnewpoints*nbnewpoints2;
% XX = linspace( 1, lastpointval, nbnewpoints2);
% New time axis scaling, May 06, 2015, AW
X = 0:size(data,1) - 1;
newpnts = ceil(size(data,1) * p / q);
XX = (0:newpnts - 1) / (p / q);
cs = spline( X, data');
tmpeeglab = ppval(cs, XX)';
if p > q % Upsampling, anti-imaging filter
tmpeeglab = firfiltdcpadded(b, tmpeeglab, 0);
end
end
% FIRFILTDCPADDED - Pad data with DC constant and filter
%
% Usage:
% >> data = firfiltdcpadded(data, b, causal);
%
% Inputs:
% data - raw data
% b - vector of filter coefficients
% causal - boolean perform causal filtering {default 0}
%
% Outputs:
% data - smoothed data
%
% Note:
% firfiltdcpadded always operates (pads, filters) along first dimension.
% Not memory optimized.
%
% Author: Andreas Widmann, University of Leipzig, 2013
%
% See also:
% firfiltsplit
%123456789012345678901234567890123456789012345678901234567890123456789012
% Copyright (C) 2013 Andreas Widmann, University of Leipzig, widmann@uni-leipzig.de
%
% This file is part of EEGLAB, see http://www.eeglab.org
% for the documentation and details.
%
% Redistribution and use in source and binary forms, with or without
% modification, are permitted provided that the following conditions are met:
%
% 1. Redistributions of source code must retain the above copyright notice,
% this list of conditions and the following disclaimer.
%
% 2. Redistributions in binary form must reproduce the above copyright notice,
% this list of conditions and the following disclaimer in the documentation
% and/or other materials provided with the distribution.
%
% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
% THE POSSIBILITY OF SUCH DAMAGE.
function [ data ] = firfiltdcpadded(b, data, causal)
% Defaults
if nargin < 3 || isempty(causal)
causal = 0;
end
% Check arguments
if nargin < 2
error('Not enough input arguments.');
end
% Filter's group delay
if mod(length(b), 2) ~= 1
error('Filter order is not even.');
end
groupDelay = (length(b) - 1) / 2;
b = double(b); % Filter with double precision
% Pad data with DC constant
if causal
startPad = repmat(data(1, :), [2 * groupDelay 1]);
endPad = [];
else
startPad = repmat(data(1, :), [groupDelay 1]);
endPad = repmat(data(end, :), [groupDelay 1]);
end
% Filter data
data = filter(b, 1, double([startPad; data; endPad])); % Pad and filter with double precision
% Remove padded data
data = data(2 * groupDelay + 1:end, :);