Permalink
Cannot retrieve contributors at this time
595 lines (532 sloc)
23.3 KB
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function [cfg] = ft_rejectartifact(cfg, data) | |
% FT_REJECTARTIFACT removes data segments containing artifacts. It returns a | |
% configuration structure with a modified trial definition which can be used for | |
% preprocessing of only the clean data. | |
% | |
% You should start by detecting the artifacts in the data using the function | |
% FT_ARTIFACT_xxx where xxx is the type of artifact. Subsequently FT_REJECTARTIFACT | |
% looks at the detected artifacts and removes them from the trial definition or from | |
% the data. In case you wish to replace bad parts by NaNs, you have to specify data | |
% as an input parameter. | |
% | |
% Use as | |
% cfg = ft_rejectartifact(cfg) | |
% with the cfg as obtained from FT_DEFINETRIAL, or as | |
% data = ft_rejectartifact(cfg, data) | |
% with the data as obtained from FT_PREPROCESSING | |
% | |
% The following configuration options are supported | |
% cfg.artfctdef.reject = 'none', 'partial', 'complete', 'nan', 'zero', or 'value' (default = 'complete') | |
% cfg.artfctdef.minaccepttim = when using partial rejection, minimum length | |
% in seconds of remaining trial (default = 0.1) | |
% cfg.artfctdef.crittoilim = when using complete rejection, reject trial only when artifacts occur within | |
% this time window (default = whole trial). This only works with in-memory data, | |
% since trial time axes are unknown for data on disk. | |
% cfg.artfctdef.feedback = 'yes' or 'no' (default = 'no') | |
% cfg.artfctdef.invert = 'yes' or 'no' (default = 'no') | |
% cfg.artfctdef.value = scalar value to replace the data in the artifact segments (default = nan) | |
% cfg.artfctdef.eog.artifact = Nx2 matrix with artifact segments, this is added to the cfg by using FT_ARTIFACT_EOG | |
% cfg.artfctdef.jump.artifact = Nx2 matrix with artifact segments, this is added to the cfg by using FT_ARTIFACT_JUMP | |
% cfg.artfctdef.muscle.artifact = Nx2 matrix with artifact segments, this is added to the cfg by using FT_ARTIFACT_MUSCLE | |
% cfg.artfctdef.zvalue.artifact = Nx2 matrix with artifact segments, this is added to the cfg by using FT_ARTIFACT_ZVALUE | |
% cfg.artfctdef.visual.artifact = Nx2 matrix with artifact segments, this is added to the cfg by using FT_DATABROWSER | |
% cfg.artfctdef.xxx.artifact = Nx2 matrix with artifact segments, this could be added by your own artifact detection function | |
% | |
% A trial that contains an artifact can be rejected completely or partially. In case | |
% of partial rejection, a minimum length of the resulting sub-trials can be specified | |
% using minaccepttim. | |
% | |
% Output: | |
% If cfg is the only input parameter, the output is a cfg structure with an updated trl. | |
% If cfg and data are both input parameters, the output is an updated raw data structure with only the clean data segments. | |
% | |
% To facilitate data-handling and distributed computing you can use | |
% cfg.inputfile = ... | |
% If you specify this option the input data will be read from a *.mat | |
% file on disk. This mat files should contain only a single variable named 'data', | |
% corresponding to the input structure. | |
% | |
% See also FT_ARTIFACT_ZVALUE, FT_ARTIFACT_EOG, FT_ARTIFACT_MUSCLE, FT_ARTIFACT_JUMP, | |
% FT_ARTIFACT_THRESHOLD, FT_ARTIFACT_CLIP, FT_ARTIFACT_ECG, FT_DATABROWSER, | |
% FT_REJECTVISUAL | |
% Undocumented local options: | |
% cfg.headerfile | |
% cfg.reject | |
% cfg.trl | |
% cfg.trlold | |
% cfg.version | |
% | |
% These old configuration options are still supported | |
% cfg.rejectmuscle = 'no' or 'yes' | |
% cfg.rejecteog = 'no' or 'yes' | |
% cfg.rejectjump = 'no' or 'yes' | |
% cfg.rejectfile = string with filename | |
% cfg.artfctdef.writerej = filename of rejection file | |
% cfg.artfctdef.type = cell-array with strings, e.g. {'eog', 'muscle' 'jump'} | |
% Copyright (C) 2003-2020, Robert Oostenveld | |
% | |
% This file is part of FieldTrip, see http://www.fieldtriptoolbox.org | |
% 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 <http://www.gnu.org/licenses/>. | |
% | |
% $Id$ | |
% FIXME this function contains a lot of lines of code that pertain to backward | |
% compatibility support that dates back to 2004/2005. It would be good to strip | |
% that code and only keep the relevant parts | |
% these are used by the ft_preamble/ft_postamble function and scripts | |
ft_revision = '$Id$'; | |
ft_nargin = nargin; | |
ft_nargout = nargout; | |
% do the general setup of the function | |
ft_defaults | |
ft_preamble init | |
ft_preamble debug | |
ft_preamble loadvar data | |
ft_preamble provenance data | |
ft_preamble trackconfig | |
% the ft_abort variable is set to true or false in ft_preamble_init | |
if ft_abort | |
return | |
end | |
% check if the input cfg is valid for this function | |
cfg = ft_checkconfig(cfg, 'dataset2files', 'yes'); | |
% set the defaults | |
cfg.artfctdef = ft_getopt(cfg, 'artfctdef'); | |
cfg.artfctdef.type = ft_getopt(cfg.artfctdef, 'type', {}); | |
cfg.artfctdef.reject = ft_getopt(cfg.artfctdef, 'reject', 'complete'); | |
cfg.artfctdef.minaccepttim = ft_getopt(cfg.artfctdef, 'minaccepttim', 0.1); | |
cfg.artfctdef.crittoilim = ft_getopt(cfg.artfctdef, 'crittoilim', []); | |
cfg.artfctdef.feedback = ft_getopt(cfg.artfctdef, 'feedback', 'no'); | |
cfg.artfctdef.invert = ft_getopt(cfg.artfctdef, 'invert', 'no'); | |
cfg.artfctdef.value = ft_getopt(cfg.artfctdef, 'value', nan); | |
% convert from old-style to new-style configuration | |
if isfield(cfg,'reject') | |
ft_warning('converting from old-style artifact configuration to new-style'); | |
cfg.artfctdef.reject = cfg.reject; | |
cfg = rmfield(cfg, 'reject'); | |
end | |
% convert from old-style to new-style configuration | |
if isfield(cfg.artfctdef,'common') | |
ft_warning('converting from old-style artifact configuration to new-style'); | |
if isfield(cfg.artfctdef.common,'minaccepttim') | |
cfg.artfctdef.minaccepttim = cfg.artfctdef.common.minaccepttim; | |
cfg.artfctdef = rmfield(cfg.artfctdef, 'common'); | |
end | |
end | |
% ensure that it is a cell-array | |
if ischar(cfg.artfctdef.type) | |
cfg.artfctdef.type = {cfg.artfctdef.type}; | |
end | |
% support the rejectXXX cfg settings for backward compatibility | |
if isfield(cfg, 'rejectmuscle') | |
fn = find(strcmp('muscle', cfg.artfctdef.type)); | |
if strcmp(cfg.rejectmuscle,'yes') && isempty(fn) | |
% this overrules the other setting, add it to the type-list | |
cfg.artfctdef.type = cat(1, {'muscle'}, cfg.artfctdef.type(:)); | |
elseif strcmp(cfg.rejectmuscle,'no') && ~isempty(fn) | |
% this overrules the other setting, remove it from the type-list | |
cfg.artfctdef.type(fn) = []; | |
end | |
cfg = rmfield(cfg, 'rejectmuscle'); | |
end | |
% support the rejectXXX cfg settings for backward compatibility | |
if isfield(cfg, 'rejecteog') | |
fn = find(strcmp('eog', cfg.artfctdef.type)); | |
if strcmp(cfg.rejecteog,'yes') && isempty(fn) | |
% this overrules the other setting, add it to the type-list | |
cfg.artfctdef.type = cat(1, {'eog'}, cfg.artfctdef.type(:)); | |
elseif strcmp(cfg.rejecteog,'no') && ~isempty(fn) | |
% this overrules the other setting, remove it from the type-list | |
cfg.artfctdef.type(fn) = []; | |
end | |
cfg = rmfield(cfg, 'rejecteog'); | |
end | |
% support the rejectXXX cfg settings for backward compatibility | |
if isfield(cfg, 'rejectjump') | |
fn = find(strcmp('jump', cfg.artfctdef.type)); | |
if strcmp(cfg.rejectjump,'yes') && isempty(fn) | |
% this overrules the other setting, add it to the type-list | |
cfg.artfctdef.type = cat(1, {'jump'}, cfg.artfctdef.type(:)); | |
elseif strcmp(cfg.rejectjump,'no') && ~isempty(fn) | |
% this overrules the other setting, remove it from the type-list | |
cfg.artfctdef.type(fn) = []; | |
end | |
cfg = rmfield(cfg, 'rejectjump'); | |
end | |
% support the rejectXXX cfg settings for backward compatibility | |
if isfield(cfg, 'rejectfile') | |
% this is slightly different to the ones above, since rejectfile is either 'no' or contains the filename | |
fn = find(strcmp('file', cfg.artfctdef.type)); | |
if ~strcmp(cfg.rejectfile,'no') && isempty(fn) | |
% this overrules the other setting, add it to the type-list | |
cfg.artfctdef.type = cat(1, {'file'}, cfg.artfctdef.type(:)); | |
elseif strcmp(cfg.rejectfile,'no') && ~isempty(fn) | |
% this overrules the other setting, remove it from the type-list | |
cfg.artfctdef.type(fn) = []; | |
end | |
end | |
% the data can be specified as input variable or through cfg.inputfile | |
hasdata = exist('data', 'var'); | |
if hasdata | |
% ensure that the data is valid for this function | |
data = ft_checkdata(data, 'datatype', 'raw', 'hassampleinfo', 'yes'); | |
end | |
if hasdata && isfield(data, 'sampleinfo') | |
% construct the trial definition from the sampleinfo and the trialinfo | |
trl = sampleinfo2trl(data); | |
elseif isfield(cfg, 'trl') && ischar(cfg.trl) | |
% load the trial information from file | |
trl = loadvar(cfg.trl, 'trl'); | |
elseif isfield(cfg, 'trl') | |
% use the trial information that was specified | |
trl = cfg.trl; | |
else | |
% there must be trials that can be scanned for artifacts and/or rejected | |
ft_error('no trials were selected, cannot perform artifact detection/rejection'); | |
end | |
% remember the original trial definition, this may include additional columns | |
trlold = trl; | |
% the code below expects an Nx3 matrix with begsample, endsample and offset | |
if istable(trl) | |
trl = table2array(trl(:,1:3)); | |
else | |
trl = trl(:,1:3); | |
end | |
% ensure the crittoilim input argument is valid | |
if ~isempty(cfg.artfctdef.crittoilim) | |
if (size(cfg.artfctdef.crittoilim,2) ~= 2 ... | |
|| (size(cfg.artfctdef.crittoilim,1) ~= size(trl,1) ... | |
&& size(cfg.artfctdef.crittoilim,1) ~= 1)) | |
ft_error('if specified, cfg.artfctdef.crittoilim should be a 1x2 or Nx2 vector'); | |
end | |
% if specified as 1x2 vector, expand into Nx2 | |
if (size(cfg.artfctdef.crittoilim,1) == 1) | |
cfg.artfctdef.crittoilim = repmat(cfg.artfctdef.crittoilim,size(trl,1),1); | |
end | |
checkCritToi = 1; % flag for convenience | |
else | |
checkCritToi = 0; | |
end | |
% prevent double occurences of artifact types, ensure that the order remains the same | |
[fn, i] = unique(cfg.artfctdef.type); | |
cfg.artfctdef.type = cfg.artfctdef.type(sort(i)); | |
% ensure that it is a row vector | |
cfg.artfctdef.type = cfg.artfctdef.type(:)'; | |
% If bad parts are to be filled with NaNs, make sure data is available | |
if ~hasdata && any(strcmp({'nan', 'zero', 'value'}, cfg.artfctdef.reject)) | |
ft_error('If bad parts are to be filled with NaNs or another value, the input data has to be specified'); | |
end | |
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
% call the appropriate function for each of the artifact types | |
% this will produce a Nx2 matrix with the begin and end sample of artifacts | |
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
for type=1:length(cfg.artfctdef.type) | |
funhandle = ft_getuserfun(cfg.artfctdef.type{type}, 'artifact'); | |
ft_info('evaluating %s\n', func2str(funhandle)); | |
% each call to artifact_xxx adds cfg.artfctdef.xxx.artifact | |
if hasdata | |
cfg = feval(funhandle, cfg, data); | |
else | |
cfg = feval(funhandle, cfg); | |
end | |
end | |
% collect the artifacts that have been detected from cfg.artfctdef.xxx.artifact | |
fn = fieldnames(cfg.artfctdef); | |
sel = false(size(fn)); | |
artifact = {}; | |
for i=1:length(fn) | |
sel(i) = issubfield(cfg.artfctdef, fn{i}) && issubfield(cfg.artfctdef, [fn{i} '.artifact']); | |
if sel(i) | |
artifact{end+1} = getsubfield(cfg.artfctdef, [fn{i} '.artifact']); | |
num = size(artifact{end}, 1); | |
if isempty(num) | |
num = 0; | |
end | |
ft_info('detected %3d %s artifacts\n', num, fn{i}); | |
end | |
end | |
% update the configuration to reflect the artifacts types that were scanned | |
cfg.artfctdef.type = fn(sel); | |
% combine all trials into a single boolean vector | |
trialall = trl2boolvec(trl); | |
% combine all artifacts into a single vector | |
rejectall = zeros(1, max(trl(:,2))); | |
for i=1:length(cfg.artfctdef.type) | |
begsample = []; | |
endsample = []; | |
if istable(artifact{i}) && ~isempty(artifact{i}) | |
begsample = artifact{i}.begsample; | |
endsample = artifact{i}.endsample; | |
elseif ~istable(artifact{i}) && ~isempty(artifact{i}) | |
begsample = artifact{i}(:,1); | |
endsample = artifact{i}(:,2); | |
end | |
for j=1:length(begsample) | |
rejectall(begsample(j):endsample(j)) = i; % the artifact type is coded here | |
end | |
end | |
% ensure that both vectors are the same length | |
if length(trialall)>length(rejectall) | |
rejectall(length(trialall)) = 0; | |
elseif length(trialall)<length(rejectall) | |
trialall(length(rejectall)) = 0; | |
end | |
% make header, needed only for sampling frequency | |
if hasdata | |
hdr = ft_fetch_header(data); | |
else | |
if isfield(cfg, 'headerformat') | |
hdr = ft_read_header(cfg.headerfile, 'headerformat', cfg.headerformat); | |
else | |
hdr = ft_read_header(cfg.headerfile); | |
end | |
end | |
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
% give visual feedback on the trial definition and the artifacts | |
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
if strcmp(cfg.artfctdef.feedback, 'yes') | |
COLOR = 'grcmykgrcmykgrcmykgrcmyk'; | |
% compute the time axis that corresponds with each trial | |
time = cell(size(trl,1), 1); | |
timebeg = nan(size(trl,1), 1); | |
timeend = nan(size(trl,1), 1); | |
for i=1:size(trl,1) | |
time{i} = offset2time(trl(i,3), hdr.Fs, trl(i,2)-trl(i,1)+1); | |
timebeg(i) = time{i}(1); | |
timeend(i) = time{i}(end); | |
end | |
figure | |
title('linear display of the continuous data') | |
xlabel('sample number'); | |
hold on | |
x = 1:length(trialall); | |
y = ones(size(trialall)) * 0.53; | |
y(~trialall) = nan; | |
plot(x, y, 'b'); | |
for i=1:length(cfg.artfctdef.type) | |
x = 1:length(rejectall); | |
y = ones(size(rejectall)) * (0.5 - i*0.03); | |
y(~(rejectall==i)) = nan; | |
plot(x, y, COLOR(i)); | |
end | |
set(gca, 'ytick', []); | |
fn = (trialall~=0) | (rejectall~=0); | |
[dum2, fn] = find(fn); | |
smpbeg = fn(1) - hdr.Fs; | |
smpend = fn(end) + hdr.Fs; | |
axis([smpbeg smpend 0 1]); | |
legend([{'defined trials'}, cfg.artfctdef.type(:)']); | |
figure | |
title('individual trials aligned to time-zero') | |
xlabel('time (s)'); | |
ylabel('trial number'); | |
hold on | |
for i=1:size(trl,1) | |
x = [time{i}(1) time{i}(end)]; | |
y = [i i]; | |
plot(x, y, 'b') | |
for j=1:length(cfg.artfctdef.type) | |
x = time{i}; | |
y = rejectall(trl(i,1):trl(i,2)); | |
y(y~=j) = nan; | |
y(y==j) = i + j*0.03; | |
plot(x, y, COLOR(j)); | |
end | |
end | |
axis([min(timebeg)-0.1 max(timeend)+0.1 0.5 size(trl,1)+0.5]); | |
axis ij | |
legend([{'defined trials'}, cfg.artfctdef.type(:)']); | |
end % feedback | |
% convert to logical, this is required for the subsequent code | |
rejectall = (rejectall~=0); | |
% invert the artifact selection | |
if istrue(cfg.artfctdef.invert) | |
ft_info('inverting selection of clean/artifactual data\n'); | |
rejectall = ~rejectall; | |
end | |
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
% write the rejection to an EEP format file | |
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
if isfield(cfg.artfctdef, 'writerej') && ~isempty(cfg.artfctdef.writerej) | |
fid = fopen(cfg.artfctdef.writerej, 'w'); | |
if fid<0 | |
ft_error('could not open rejection file for writing'); | |
else | |
% determine the begin and end of each rejected period (in samples) | |
rejectonset = find(diff([0 rejectall])== 1); | |
rejectofset = find(diff([rejectall 0])==-1); | |
% determine the begin and end of each rejected period (in seconds) | |
rejectonset = (rejectonset-1)/hdr.Fs; | |
rejectofset = (rejectofset-1)/hdr.Fs; | |
for rejlop=1:length(rejectonset) | |
ft_info(fid, '%f-%f\n', rejectonset(rejlop), rejectofset(rejlop)); | |
end | |
fclose(fid); | |
end | |
end | |
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
% remove the trials that (partially) coincide with a rejection mark | |
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
if any(strcmp(cfg.artfctdef.reject, {'partial', 'complete', 'nan', 'zero', 'value'})) | |
count_complete_reject = 0; | |
count_partial_reject = 0; | |
count_nan = 0; | |
count_zero = 0; | |
count_value = 0; | |
count_outsidecrit = 0; | |
trlCompletelyRemovedInd = []; | |
trlPartiallyRemovedInd = []; | |
% add a 4th column with the original trial number | |
trl(:,4) = 1:size(trl,1); | |
trlnew = []; | |
for trial=1:size(trl,1) | |
% cut out the part of the rejection-axis corresponding with this trial | |
rejecttrial = rejectall(trl(trial,1):trl(trial,2)); | |
if all(not(rejecttrial)) | |
% the whole trial is good | |
trlnew = [trlnew; trl(trial,:)]; | |
elseif all(rejecttrial) && strcmp(cfg.artfctdef.reject, 'nan') | |
% the whole trial is bad, but it is requested to be replaced with NaNs | |
data.trial{trial}(:,rejecttrial) = nan; | |
trlnew = [trlnew; trl(trial,:)]; % Mark the trial as good as nothing will be removed | |
count_nan = count_nan + 1; | |
elseif all(rejecttrial) && strcmp(cfg.artfctdef.reject, 'zero') | |
% the whole trial is bad, but it is requested to be replaced with zeros | |
data.trial{trial}(:,rejecttrial) = 0; | |
trlnew = [trlnew; trl(trial,:)]; % Mark the trial as good as nothing will be removed | |
count_zero = count_zero + 1; | |
elseif all(rejecttrial) && strcmp(cfg.artfctdef.reject, 'value') | |
% the whole trial is bad, but it is requested to be replaced with a specific value | |
data.trial{trial}(:,rejecttrial) = cfg.artfctdef.value; | |
trlnew = [trlnew; trl(trial,:)]; % Mark the trial as good as nothing will be removed | |
count_value = count_value + 1; | |
elseif all(rejecttrial) | |
% the whole trial is bad | |
count_complete_reject = count_complete_reject + 1; | |
trlCompletelyRemovedInd = [trlCompletelyRemovedInd trial]; | |
elseif any(rejecttrial) && strcmp(cfg.artfctdef.reject, 'complete') | |
% some part of the trial is bad, check if within crittoilim? | |
if (checkCritToi) | |
critInd = (data.time{trial} >= cfg.artfctdef.crittoilim(trial,1) & data.time{trial} <= cfg.artfctdef.crittoilim(trial,2)); | |
if (any(critInd & rejecttrial)) | |
count_complete_reject = count_complete_reject + 1; | |
trlCompletelyRemovedInd = [trlCompletelyRemovedInd trial]; | |
continue; | |
else | |
trlnew = [trlnew; trl(trial,:)]; | |
count_outsidecrit = count_outsidecrit + 1; | |
end | |
else % no crittoilim checking required | |
count_complete_reject = count_complete_reject + 1; | |
trlCompletelyRemovedInd = [trlCompletelyRemovedInd trial]; | |
continue; | |
end | |
elseif any(rejecttrial) && strcmp(cfg.artfctdef.reject, 'partial') | |
% some part of the trial is bad, reject only the bad part | |
partial = []; | |
rejecttrial = [0 not(rejecttrial) 0]; | |
% the first column is the begin sample, the second the end sample and the third is the offset | |
partial(:,1) = find(diff(rejecttrial(:))== 1) + trl(trial,1) - 1; | |
partial(:,2) = find(diff(rejecttrial(:))==-1) + trl(trial,1) - 2; | |
partial(:,3) = find(diff(rejecttrial(:))== 1) - 1 + trl(trial,3); | |
% some people use additional columns in the trl matrix to store trigger values and/or reaction times | |
% these should remain linked to the original trial, i.e. they should be copied for each new fragment | |
for i=4:size(trl,2) | |
partial(:,i) = trl(trial,i); | |
end | |
minacceptnumsmp = round(cfg.artfctdef.minaccepttim .* hdr.Fs); | |
partial((partial(:,2)-partial(:,1))<minacceptnumsmp,:) = []; | |
trlnew = [trlnew; partial]; | |
count_partial_reject = count_partial_reject + 1; | |
trlPartiallyRemovedInd = [trlPartiallyRemovedInd trial]; | |
elseif any(rejecttrial) && strcmp(cfg.artfctdef.reject, 'nan') | |
% Some part of the trial is bad, replace bad part with NaNs | |
data.trial{trial}(:,rejecttrial) = nan; | |
trlnew = [trlnew; trl(trial,:)]; % Mark the trial as good as nothing will be removed | |
count_nan = count_nan + 1; | |
elseif any(rejecttrial) && strcmp(cfg.artfctdef.reject, 'zero') | |
% Some part of the trial is bad, replace bad part with zeros | |
data.trial{trial}(:,rejecttrial) = 0; | |
trlnew = [trlnew; trl(trial,:)]; % Mark the trial as good as nothing will be removed | |
count_zero = count_zero + 1; | |
elseif any(rejecttrial) && strcmp(cfg.artfctdef.reject, 'value') | |
% Some part of the trial is bad, replace bad part with specified value | |
data.trial{trial}(:,rejecttrial) = cfg.artfctdef.value; | |
trlnew = [trlnew; trl(trial,:)]; % Mark the trial as good as nothing will be removed | |
count_value = count_value + 1; | |
end | |
end % for each trial | |
ft_info('rejected %3d trials completely\n', count_complete_reject); | |
ft_info('rejected %3d trials partially\n', count_partial_reject); | |
ft_info('filled parts of %3d trials with NaNs\n', count_nan); | |
ft_info('filled parts of %3d trials with zeros\n', count_zero); | |
ft_info('filled parts of %3d trials with a specified value\n', count_value); | |
if (checkCritToi) | |
ft_info('retained %3d trials with artifacts outside critical window\n', count_outsidecrit); | |
end | |
ft_info('resulting %3d trials\n', size(trlnew,1)); | |
if strcmp(cfg.artfctdef.feedback, 'yes') | |
ft_info('the following trials were completely removed: '); | |
for k = trlCompletelyRemovedInd | |
ft_info('%d ', k); | |
end | |
ft_info('\nthe following trials were partially removed: '); | |
for k = trlPartiallyRemovedInd | |
ft_info('%d ', k); | |
end | |
ft_info('\n'); | |
end | |
begsample = trlnew(:,1); | |
endsample = trlnew(:,2); | |
offset = trlnew(:,3); | |
number = trlnew(:,4); % numbers corresponding to the original trials | |
if istable(trlold) | |
trlnew = table(begsample, endsample, offset); | |
% concatenate the additional columns from the original trial definition | |
trlnew = cat(2, trlnew, trlold(number, 4:end)); | |
else | |
% concatenate the additional columns from the original trial definition | |
trlnew = [begsample endsample offset trlold(number, 4:end)]; | |
end | |
cfg.trlold = trlold; % return the original trial definition in the configuration | |
cfg.trl = trlnew; % return the cleaned trial definition in the configuration | |
else | |
cfg.trlold = trlold; % return the original trial definition in the configuration | |
cfg.trl = trlold; % return the original trial definition in the configuration | |
ft_info('not rejecting any data, only marking the artifacts\n'); | |
end | |
if isempty(cfg.trl) | |
ft_error('No trials left after artifact rejection.') | |
else | |
if hasdata && ~any(strcmp(cfg.artfctdef.reject, {'nan', 'zero', 'value'})) % Skip this step to avoid removing parts that were filled with NaNs or zeros | |
% apply the updated trial definition on the data | |
tmpcfg = keepfields(cfg, {'trl', 'showcallinfo', 'trackcallinfo', 'trackconfig', 'trackusage', 'trackdatainfo', 'trackmeminfo', 'tracktimeinfo'}); | |
data = removefields(data, {'trialinfo'}); | |
data = ft_redefinetrial(tmpcfg, data); | |
% restore the provenance information | |
[cfg, data] = rollback_provenance(cfg, data); | |
end | |
end | |
% do the general cleanup and bookkeeping at the end of the function | |
ft_postamble debug | |
ft_postamble trackconfig | |
ft_postamble provenance | |
if hasdata | |
ft_postamble previous data | |
ft_postamble history data | |
ft_postamble savevar data | |
% return the data, the output variable is called cfg instead of data | |
cfg = data; | |
end |