Skip to content
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
Cannot retrieve contributors at this time
function [data] = ft_appenddata(cfg, varargin)
% FT_APPENDDATA concatenates multiple raw data structures that have been preprocessed
% separately into a single raw data structure.
% Use as
% data = ft_appenddata(cfg, data1, data2, data3, ...)
% The following configuration options are supported:
% cfg.keepsampleinfo = 'yes', 'no', 'ifmakessense' (default = 'ifmakessense')
% If the input datasets all have the same channels, the trials will be concatenated.
% This is useful for example if you have different experimental conditions, which,
% besides analyzing them separately, for some reason you also want to analyze
% together. The function will check for consistency in the order of the channels. If
% the order is inconsistent the channel order of the output will be according to the
% channel order of the first data structure in the input.
% If the input datasets have different channels, but the same number of trials, the
% channels will be concatenated within each trial. This is useful for example if the
% data that you want to analyze contains both MEG and EMG channels which require
% different preprocessing options.
% If you concatenate trials and the data originates from the same original datafile,
% the sampleinfo is consistent and you can specify cfg.keepsampleinfo='yes'. If the
% data originates from different datafiles, the sampleinfo is inconsistent and does
% not point to the same recording, hence you should specify cfg.keepsampleinfo='no'.
% Occasionally, the data needs to be concatenated in the trial dimension while
% there's a slight discrepancy in the channels in the input data (e.g. missing
% channels in one of the data structures). The function will then return a data
% structure containing only the channels which are present in all inputs.
% To facilitate data-handling and distributed computing you can use
% cfg.inputfile = ...
% cfg.outputfile = ...
% If you specify one of these (or both) the input data will be read from a *.mat
% file on disk and/or the output data will be written to a *.mat file. These mat
% files should contain only a single variable, corresponding with the
% input/output structure. The data structure in the input file should be a
% cell-array for this particular function.
% Copyright (C) 2005-2008, Robert Oostenveld
% Copyright (C) 2009-2011, Jan-Mathijs Schoffelen
% This file is part of FieldTrip, see
% 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
% 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_preamble init
ft_preamble debug
ft_preamble loadvar varargin
ft_preamble provenance varargin
% the ft_abort variable is set to true or false in ft_preamble_init
if ft_abort
% set the defaults
cfg.keepsampleinfo = ft_getopt(cfg, 'keepsampleinfo', 'ifmakessense');
% although not 100% robust, this could make some users becoming aware of the issue of overlapping trials
for i=1:numel(varargin)
dataset{i} = ft_findcfg(varargin{i}.cfg, 'dataset');
hassampleinfo(i) = isfield(varargin{i}, 'sampleinfo');
if ~all(strcmp(dataset, dataset{1})) && ~strcmp(cfg.keepsampleinfo, 'no')
ft_warning('the data has overlapping segments or originates from different recordings on disk');
ft_warning('please consider specifying cfg.keepsampleinfo=''no''')
end % try
haschantype = false;
haschanunit = false;
for i=1:length(varargin)
% if one of them has chantype or chanunit, we want it for the others as well
haschantype = haschantype || isfield(varargin{i}, 'chantype');
haschanunit = haschanunit || isfield(varargin{i}, 'chanunit');
% ensure that the input data is valid for this function
for i=1:length(varargin)
varargin{i} = ft_checkdata(varargin{i}, 'datatype', {'raw', 'raw+comp'}, 'feedback', 'no', 'haschantype', haschantype, 'haschanunit', haschanunit, 'hassampleinfo', cfg.keepsampleinfo);
% set the defaults
cfg.appendsens = ft_getopt(cfg, 'appendsens', 'no');
cfg.appenddim = ft_getopt(cfg, 'appenddim', []);
cfg.tolerance = ft_getopt(cfg, 'tolerance', 1e-5); % this is passed to append_common, which passes it to ft_selectdata
isequaltime = true;
isequallabel = true;
issamelabel = true;
isequalfsample = true;
for i=2:numel(varargin)
isequaltime = isequaltime && isequal(varargin{i}.time , varargin{1}.time );
isequallabel = isequallabel && isequal(varargin{i}.label, varargin{1}.label);
issamelabel = issamelabel && isempty(setxor(varargin{i}.label, varargin{1}.label));
isequalfsample = isequalfsample && isfield(varargin{i},'fsample') && isfield(varargin{1},'fsample') && isequal(varargin{i}.fsample, varargin{1}.fsample);
if isempty(cfg.appenddim) || strcmp(cfg.appenddim, 'auto')
if isequallabel || issamelabel
cfg.appenddim = 'rpt';
elseif isequaltime && ~isequallabel && ~issamelabel
cfg.appenddim = 'chan';
ft_error('cannot append this data');
ft_info('concatenating over the "%s" dimension\n', cfg.appenddim);
% ft_selectdata cannot create the union of the data contained in cell-arrays
% make a dummy without the actual data, but keep trialinfo/sampleinfo/grad/elec/opto
% also remove the topo/unmixing/topolabel, if present, otherwise it is not
% possible to concatenate raw and comp data. Note that in append_common the
% topo etc. is removed anyhow, when appenddim = 'chan'
dummy = cell(size(varargin));
hastopo = false;
hasunmixing = false;
for i=1:numel(varargin)
dummy{i} = removefields(varargin{i}, {'trial', 'time'});
hastopo = isfield(dummy{i}, 'topo');
hasunmixing = isfield(dummy{i}, 'unmixing');
if strcmp(cfg.appenddim, 'chan')
% this avoids a bunch of confusing warnings in append_common
dummy{i} = removefields(dummy{i}, {'topo', 'unmixing', 'topolabel'});
% add a dummy data field, this causes the datatype to become 'chan'
dummy{i}.dummy = ones(numel(dummy{i}.label),1);
dummy{i}.dummydimord = 'chan';
% don't do any data appending inside the common function
cfg.parameter = {};
% use a low-level function that is shared with the other ft_appendxxx functions
% this will handle the trialinfo/sampleinfo/grad/elec/opto
data = append_common(cfg, dummy{:});
% this is the actual data field that will be appended further down
cfg.parameter = {'trial'};
switch cfg.appenddim
case 'chan'
if isequaltime
ntrl = length(varargin{1}.trial);
dat = varargin{1}.trial;
lab = varargin{1}.label(:);
if hastopo && isfield(varargin{1}, 'topo')
topo = varargin{1}.topo;
topolab = varargin{1}.topolabel(:);
topo = eye(numel(varargin{1}.label));
topolab = varargin{1}.label(:);
if hasunmixing && isfield(varargin{1}, 'unmixing')
unmixing = varargin{1}.unmixing;
unmixing = eye(numel(varargin{1}.label));
for i=2:numel(varargin)
for j=1:ntrl
dat{j} = cat(1, dat{j}, varargin{i}.trial{j});
lab = cat(1, lab, varargin{i}.label(:));
if hastopo && isfield(varargin{i}, 'topo')
topo = blkdiag(topo, varargin{i}.topo);
topolab = cat(1, topolab, varargin{i}.topolabel);
topo = blkdiag(topo, eye(numel(varargin{i}.label)));
topolab = cat(1, topolab, varargin{i}.label(:));
if hasunmixing && isfield(varargin{i}, 'unmixing')
unmixing = blkdiag(unmixing, varargin{i}.unmixing);
unmixing = blkdiag(unmixing, eye(numel(varargin{i}.label)));
data.label = lab; % replace the one from append_common
data.trial = dat;
data.time = varargin{1}.time;
if haschantype
chantype = varargin{1}.chantype(:);
for i=2:numel(varargin)
chantype = cat(1, chantype, varargin{i}.chantype(:));
data.chantype = chantype;
if haschanunit
chanunit = varargin{1}.chanunit(:);
for i=2:numel(varargin)
chanunit = cat(1, chanunit, varargin{i}.chanunit(:));
data.chanunit = chanunit;
if isfield(varargin{1}, 'topodimord'), data.topodimord = varargin{1}.topodimord; end
if isfield(varargin{1}, 'unmixingdimord'), data.unmixingdimord = varargin{1}.unmixingdimord; end
if hastopo, data.topo = topo; end
if hastopo, data.topolabel = topolab; end
if hasunmixing, data.unmixing = unmixing; end
ft_error('data has different time, cannot append over channels');
case 'rpt'
if isequallabel
% the channels are the same and sorted in the same order
dat = cell(1,0);
tim = cell(1,0);
for i=1:numel(varargin)
dat = cat(2, dat, varargin{i}.trial);
tim = cat(2, tim, varargin{i}.time);
data.trial = dat;
data.time = tim;
% the channels are not the same, or do not appear in the same order
% only return the intersection of the channels
dat = cell(1,0);
tim = cell(1,0);
for i=1:numel(varargin)
% find the indices of the channels in each dataset, sorted according to the first input
[dum, indx] = match_str(data.label, varargin{i}.label);
for j=1:numel(varargin{i}.trial)
dat = cat(2, dat, {varargin{i}.trial{j}(indx,:)});
tim = cat(2, tim, {varargin{i}.time{j}});
data.trial = dat;
data.time = tim;
data.label; % keep it as determined by append_common
ft_error('unsupported cfg.appenddim');
end % switch
if isequalfsample
data.fsample = varargin{1}.fsample;
% do the general cleanup and bookkeeping at the end of the function
ft_postamble debug
ft_postamble previous varargin
ft_postamble provenance data
ft_postamble history data
ft_postamble savevar data