% 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')
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, :);