function [cfg, artifact] = ft_artifact_threshold(cfg, data) % FT_ARTIFACT_THRESHOLD scans data segments of interest for channels in which the % signal exceeds a specified minimum or maximum value, or in which the peak-to-peak % range within the trial exceeds a specified threshold. % % Use as % [cfg, artifact] = ft_artifact_threshold(cfg) % with the configuration options % cfg.dataset = string with the filename % or % cfg.headerfile = string with the filename % cfg.datafile = string with the filename % and optionally % cfg.headerformat % cfg.dataformat % % Alternatively you can use it as % [cfg, artifact] = ft_artifact_threshold(cfg, data) % where the input data is a structure as obtained from FT_PREPROCESSING. % % In both cases the configuration should also contain % cfg.trl = structure that defines the data segments of interest, see FT_DEFINETRIAL % cfg.continuous = 'yes' or 'no' whether the file contains continuous data % and % cfg.artfctdef.threshold.channel = cell-array with channel labels % cfg.artfctdef.threshold.bpfilter = 'no' or 'yes' (default = 'yes') % cfg.artfctdef.threshold.bpfreq = [0.3 30] % cfg.artfctdef.threshold.bpfiltord = 4 % % In the same way as specifying the options for band-pass filtering, it is also % possible to specify lpfilter, hpfilter, bsfilter, dftfilter or medianfilter, see % FT_PREPROCESSING. % % The detection of artifacts is done according to the following settings, % you should specify at least one of these thresholds % cfg.artfctdef.threshold.min = value in uV or T, default -inf % cfg.artfctdef.threshold.max = value in uV or T, default inf % cfg.artfctdef.threshold.onset = value in uV or T, default inf % cfg.artfctdef.threshold.offset = value in uV or T, default inf % % When cfg.artfctdef.threshold.onset and offset are used, the rising and falling % flank are thresholded with different values. In case onset and offset are both % positive, the data will be thresholded above their values. In case both onset and % offset are negative, the data will be thresholded below their values. % % Note that this function does not support artifactpadding or filterpadding. % % The output argument "artifact" is a Nx2 matrix comparable to the "trl" matrix of % FT_DEFINETRIAL. The first column of which specifying the beginsamples of an % artifact period, the second column contains the endsamples of the artifactperiods. % % To facilitate data-handling and distributed computing, you can use % cfg.inputfile = ... % to read the input data 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_REJECTARTIFACT, FT_ARTIFACT_CLIP, FT_ARTIFACT_ECG, FT_ARTIFACT_EOG, % FT_ARTIFACT_JUMP, FT_ARTIFACT_MUSCLE, FT_ARTIFACT_THRESHOLD, FT_ARTIFACT_ZVALUE % Copyright (C) 2003-2011, Robert Oostenveld, SMI, FCDC % % 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 . % % $Id$ % 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 provenance ft_preamble loadvar data % 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, 'renamed', {'datatype', 'continuous'}); cfg = ft_checkconfig(cfg, 'renamedval', {'continuous', 'continuous', 'yes'}); % set the default options cfg.continuous = ft_getopt(cfg, 'continuous', []); cfg.headerformat = ft_getopt(cfg, 'headerformat', []); cfg.dataformat = ft_getopt(cfg, 'dataformat', []); cfg.feedback = ft_getopt(cfg, 'feedback', 'text'); cfg.representation = ft_getopt(cfg, 'representation', 'numeric'); % numeric or table % set the default artifact detection parameters cfg.artfctdef = ft_getopt(cfg, 'artfctdef'); cfg.artfctdef.threshold = ft_getopt(cfg.artfctdef, 'threshold'); cfg.artfctdef.threshold.channel = ft_getopt(cfg.artfctdef.threshold, 'channel', 'all'); cfg.artfctdef.threshold.bpfilter = ft_getopt(cfg.artfctdef.threshold, 'bpfilter', 'yes'); cfg.artfctdef.threshold.bpfreq = ft_getopt(cfg.artfctdef.threshold, 'bpfreq', [0.3 30]); cfg.artfctdef.threshold.bpfiltord = ft_getopt(cfg.artfctdef.threshold, 'bpfiltord', 4); cfg.artfctdef.threshold.range = ft_getopt(cfg.artfctdef.threshold, 'range', inf); cfg.artfctdef.threshold.min = ft_getopt(cfg.artfctdef.threshold, 'min', -inf); cfg.artfctdef.threshold.max = ft_getopt(cfg.artfctdef.threshold, 'max', inf); cfg.artfctdef.threshold.onset = ft_getopt(cfg.artfctdef.threshold, 'onset', []); cfg.artfctdef.threshold.offset = ft_getopt(cfg.artfctdef.threshold, 'offset', []); % the data is either passed into the function by the user or read from file with cfg.inputfile hasdata = exist('data', 'var'); % read the header, or get it from the input data if ~hasdata cfg = ft_checkconfig(cfg, 'dataset2files', 'yes'); cfg = ft_checkconfig(cfg, 'required', {'headerfile', 'datafile'}); hdr = ft_read_header(cfg.headerfile, 'headerformat', cfg.headerformat); else data = ft_checkdata(data, 'datatype', 'raw', 'hassampleinfo', 'yes'); cfg = ft_checkconfig(cfg, 'forbidden', {'dataset', 'headerfile', 'datafile'}); hdr = ft_fetch_header(data); end % set default cfg.continuous if isempty(cfg.continuous) if hdr.nTrials==1 cfg.continuous = 'yes'; else cfg.continuous = 'no'; end end % get the specification of the data segments that should be scanned for artifacts if ~isfield(cfg, 'trl') && hasdata trl = data.sampleinfo; for k = 1:numel(data.trial) trl(k,3) = time2offset(data.time{k}, data.fsample); end elseif isfield(cfg, 'trl') && ischar(cfg.trl) trl = loadvar(cfg.trl, 'trl'); elseif isfield(cfg, 'trl') && isnumeric(cfg.trl) trl = cfg.trl; else ft_error('cannot determine which segments of data to scan for artifacts'); end if ~isempty(cfg.artfctdef.threshold.onset) || ~isempty(cfg.artfctdef.threshold.offset) if cfg.artfctdef.threshold.onset>0 && cfg.artfctdef.threshold.offset>0 direction = 'up'; elseif cfg.artfctdef.threshold.onset<0 && cfg.artfctdef.threshold.offset<0 direction = 'down'; else error('incorrect specification of onset and offset'); end else direction = 'none'; end % get the remaining settings artfctdef = cfg.artfctdef.threshold; artfctdef.trl = trl; ntrial = size(trl,1); label = ft_channelselection(artfctdef.channel, hdr.label); chanindx = match_str(hdr.label, label); nchan = length(chanindx); artifact = table(); % only print this once ft_notice once FieldTrip:applyingPreprocessing ft_progress('init', cfg.feedback, ['searching for artifacts in ' num2str(nchan) ' channels']); for trlop=1:ntrial ft_progress(trlop/ntrial, 'searching in trial %d from %d\n', trlop, ntrial); if hasdata dat = ft_fetch_data(data, 'header', hdr, 'begsample', trl(trlop,1), 'endsample', trl(trlop,2), 'chanindx', chanindx, 'checkboundary', strcmp(cfg.continuous, 'no')); else dat = ft_read_data(cfg.datafile, 'header', hdr, 'begsample', trl(trlop,1), 'endsample', trl(trlop,2), 'chanindx', chanindx, 'checkboundary', strcmp(cfg.continuous, 'no'), 'dataformat', cfg.dataformat); end % determine the length of the data in this trial nsample = trl(trlop,2)-trl(trlop,1)+1; if size(trl,2)>2 time = offset2time(trl(trlop,3), hdr.Fs, nsample); else time = offset2time(0, hdr.Fs, nsample); end % only do the preprocessing and filtering if there is an option that suggests to have an effect status = struct2cell(artfctdef); status = status(cellfun(@(x) ischar(x), status)); if any(ismember(status, {'yes', 'abs', 'complex', 'real', 'imag', 'absreal', 'absimag', 'angle'})) ft_notice('FieldTrip:applyingPreprocessing', 'applying preprocessing options') dat = preproc(dat, label, time, artfctdef); end for sgnlop=1:nchan % make a vector that indicates for each sample whether it exceeds the threshold artval = false(1, nsample); artval = artval | any(dat(sgnlop,:)<=artfctdef.min,1); artval = artval | any(dat(sgnlop,:)>=artfctdef.max,1); % compute the range as the maximum of the peak-to-peak values for each channel ptpval = max(dat(sgnlop,:)) - min(dat(sgnlop,:)); if any(ptpval>=artfctdef.range) artval(:) = true; % mark the whole segment as bad end % this is when a different onset and offset are specified switch direction case 'up' onset = find(diff([0 dat(sgnlop,:)>=artfctdef.onset])>0); % find all rising flanks offset = nan(size(onset)); for i=1:numel(onset) rem = dat(sgnlop,onset(i)+1:end); % this is the remaining data following the artifact onset rem = (rem<=artfctdef.offset); % threshold for the offset if any(rem) offset(i) = find(rem, 1, 'first'); % find the falling flank else offset(i) = length(rem); % take the last sample end offset(i) = offset(i) + onset(i); % add it to the other artifacts in the boolean vector artval(onset(i):offset(i)) = true; end case 'down' onset = find(diff([0 dat(sgnlop,:)<=artfctdef.onset])>0); % find all rising flanks offset = nan(size(onset)); for i=1:numel(onset) rem = dat(sgnlop,onset(i)+1:end); % this is the remaining data following the artifact onset rem = (rem>=artfctdef.offset); if any(rem) offset(i) = find(rem, 1, 'first'); % find the falling flank else offset(i) = length(rem); % take the last sample end offset(i) = offset(i) + onset(i); % add it to the other artifacts in the boolean vector artval(onset(i):offset(i)) = true; end case 'none' % nothing to do end % to avoid confusion with the offset that is used further down clear onset offset begsample = find(diff([0 artval])>0)'; endsample = find(diff([artval 0])<0)'; offset = nan(size(begsample)); % the offset of the peak relative to the segment, just like in FT_DEFINETRIAL channel = repmat(label(sgnlop), size(begsample)); % determine the sample at which the signal peaks for i=1:numel(begsample) seg = dat(sgnlop,begsample(i):endsample(i)); % get the segment of data if all(seg>=artfctdef.max) || strcmp(direction, 'up') [dum, indx] = max(seg); offset(i) = 1 - indx; % relative to the start of the segment, 0 is the first sample, -1 is the 2nd, etc. elseif all(seg<=artfctdef.min) || strcmp(direction, 'down') [dum, indx] = min(seg); offset(i) = 1 - indx; % relative to the start of the segment, 0 is the first sample, -1 is the 2nd, etc. end % if up or down end % for each artifact in this trial % express them relative to the start of the data, not the start of the trial begsample = begsample + trl(trlop,1) - 1; endsample = endsample + trl(trlop,1) - 1; % remember the parts where this channel exceeds the threshold as artifacts if ~isempty(begsample) artifact = vertcat(artifact, table(begsample, endsample, offset, channel)); end end % for sgnlop end % for trlop ft_progress('close'); % reset it to the default ft_notice on FieldTrip:applyingPreprocessing if strcmp(cfg.representation, 'numeric') && istable(artifact) if isempty(artifact) % an empty table does not have columns artifact = zeros(0,3); else % convert the table to a numeric array with the columns begsample, endsample and offset artifact = table2array(artifact(:,1:3)); end elseif strcmp(cfg.representation, 'table') && isnumeric(artifact) if isempty(artifact) % an empty table does not have columns artifact = table(); else % convert the numeric array to a table with the columns begsample, endsample and offset begsample = artifact(:,1); endsample = artifact(:,2); offset = artifact(:,3); artifact = table(begsample, endsample, offset); end end % remember the details that were used here and store the detected artifacts cfg.artfctdef.threshold = artfctdef; cfg.artfctdef.threshold.artifact = artifact; ft_notice('detected %d artifacts\n', size(artifact,1)); % do the general cleanup and bookkeeping at the end of the function ft_postamble provenance ft_postamble previous data ft_postamble savevar