% STD_READDATA - load measures for data channels or 
%                  for all components of a specified cluster.
%                  Called by plotting functions
%                  STD_ENVTOPO, STD_ERPPLOT, STD_ERSPPLOT, ...
% Usage:
%         >> [STUDY, datavals, xvals, yvals, events, params] = ...
%                   std_readdata(STUDY, ALLEEG, varargin);
% Inputs:
%       STUDY - studyset structure containing some or all files in ALLEEG
%      ALLEEG - vector of loaded EEG datasets
%
% Optional inputs:
%  'design'    - [integer] read files from a specific STUDY design. Default
%                is empty (use current design in STUDY.currentdesign). Use
%                NaN to create a design with with all the data.
%  'channels'  - [cell] list of channels to import {default: none}
%  'clusters'  - [integer] list of clusters to import {[]|default: all but
%                the parent cluster (1) and any 'NotClust' clusters}
%  'singletrials' - ['on'|'off'] load single trials spectral data (if 
%                available). Default is 'off'.
%  'subject'   - [string] select a specific subject {default:all}
%  'datatype'  - ['erp'|'spec'|'ersp'|'itc'|'erpim'] select measure to load
%                Default is 'erp'.
%  'component' - [integer] select a specific component in a cluster.
%                This is the index of the component in the cluster not the
%                component number {default:all}
%  'ndim'       - [integer] number of dimension of output array
%  'customread' - [func] custom function to read data. See example.
%  'customparams' - [cell] custom parameters for function above. See example.
%
% ERP specific optional inputs:
%  'timerange' - [min max] time range {default: whole measure range}
%  'componentpol' - ['on'|'off'] invert ERP component sign based on
%                   scalp map match with component scalp map centroid.
%                   {default:'on'}
%
% Output:
%  STUDY    - updated studyset structure
%  datavals  - [cell array] erp data (the cell array size is 
%             condition x groups)
%  xvals    - [float array] array of first dim values (for example time)
%  yvals    - [float array] array of second dim values (for example frequencies)
%  events   - [cell array] events (corresponding to the data)
%  params   - [struct] structure containing parameters
%  setinds  - [cell array] index of the dataset for each cell member in
%             datavals. Could be ambiguous if datasets are merged in the 
%             datavals array (the setinds will then contain the index of 
%             one of the datasets).
%  datainfo - [cell array] trial and participant info corresponding to the 
%             datavals array.
%
% Important note: This function does not do baseline correction for ERSP. 
%                 To get the baseline corrected data use the function STD_ERSPPLOT
%
% Example:
%  std_precomp(STUDY, ALLEEG, { ALLEEG(1).chanlocs.labels }, 'erp', 'on');
%  [STUDY,erp,times] = std_readdata(STUDY, ALLEEG, 'channels', { ALLEEG(1).chanlocs(1).labels });
%
%  % To read field 'EEG.etc.eegstats.alpha_asymmetry' for each dataset, enter
%  % (note that you need to use the eegstats plugin to compute this value first)
%  [STUDY,aa] = std_readdata(STUDY, ALLEEG, 'customread', 'std_readeegfield', ...
%               'customparams', {{ 'etc', 'eegstats', 'alpha_asymmetry' }}, 'ndim', 1);
% 
% Author: Arnaud Delorme, CERCO, 2006-

% Copyright (C) Arnaud Delorme, arno@salk.edu
%
% This file is part of EEGLAB, see https://urldefense.com/v3/__http://www.eeglab.org__;!!Mih3wA!Hn3hFRnhxnzS1csHGMH6q95jPsdmpLu36WaSxZZ0BSw4smbm8RghssmOC-feg7O1JHd0ncZ3ACn-r8229A$ 
% 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 [STUDY, datavals, xvals, yvals, events, params, setinds, datainfo] = std_readdata(STUDY, ALLEEG, varargin)

if nargin < 2
    help std_readdata;
    return;
end

STUDY = pop_erpparams(STUDY, 'default');
STUDY = pop_specparams(STUDY, 'default');
STUDY = pop_erspparams(STUDY, 'default');
[opt, moreopts] = finputcheck( varargin, { ...
    'design'        'integer' []             STUDY.currentdesign;
    'channels'      'cell'    []             {};
    'clusters'      'integer' []             [];
    'ndim'          'integer' []             [];
    'timerange'     'real'    []             [];
    'freqrange'     'real'    []             [];
    'datatype'      'string'  { 'erp','spec' 'ersp' 'itc' 'erpim' 'custom' } 'erp';
    'singletrials'  'string'  { 'on','off' } 'off';
    'componentpol'  'string'  { 'on','off' } 'on';
    'component'     'integer' []             [];
    'customread'    ''        []             [];
    'customparams'  'cell'    []             {};
    'subject'       'string'  []             '' }, ...
    'std_readdata', 'ignore');
if ~isempty(moreopts)
    fprintf(2, 'Warning: std_readdata ignored at least one parameters: "%s"\n', moreopts{1})
end
if ischar(opt), error(opt); end

dtype = opt.datatype;

% get the file extension
% ----------------------
tmpDataType = opt.datatype;
if ~isempty(opt.customread)
    opt.datatype = 'custom';
end
if strcmpi(opt.datatype, 'ersp') || strcmpi(opt.datatype, 'itc')
    tmpDataType = 'timef'; 
    if isempty(opt.timerange), opt.timerange = STUDY.etc.erspparams.timerange;  end
    if isempty(opt.freqrange), opt.freqrange = STUDY.etc.erspparams.freqrange; end
elseif strcmpi(opt.datatype, 'erpim')
    if isempty(opt.timerange), opt.timerange = STUDY.etc.erpimparams.timerange;  end
elseif strcmpi(opt.datatype, 'erp')    
    if isempty(opt.timerange), opt.timerange = STUDY.etc.erpparams.timerange;  end
elseif strcmpi(opt.datatype, 'spec')    
    if isempty(opt.freqrange), opt.freqrange = STUDY.etc.specparams.freqrange; end
end
if ~isempty(opt.channels), fileExt = [ '.dat' tmpDataType ];
else                       fileExt = [ '.ica' tmpDataType ];
end

% list of subjects
% ----------------
allSubjects = { STUDY.datasetinfo.subject };
uniqueSubjects = unique(allSubjects);
STUDY.subject = uniqueSubjects;
if ischar(opt.subject) && ~isempty(opt.subject), subjectList = {opt.subject}; else subjectList = opt.subject; end
if isempty(subjectList)
    if isnan(opt.design), subjectList = STUDY.subject;
    else subjectList = STUDY.design(opt.design).cases.value; 
    end
end

% options
% -------
opts = {};
if ~isempty(opt.timerange), opts = { opts{:} 'timelimits', opt.timerange }; end
if ~isempty(opt.freqrange), opts = { opts{:} 'freqlimits', opt.freqrange }; end
opts = { opts{:} 'singletrials' opt.singletrials };
fprintf('Reading subjects'' data or looking up measure values in EEGLAB cache\n');

% determining component polarity if necessary
% -------------------------------------------
if isempty(opt.customread) && isempty(opt.channels) && strcmpi(dtype, 'erp') && strcmpi(opt.componentpol, 'on')
    componentPol = ones(1, length(STUDY.cluster(opt.clusters).comps)); % default is all 1
    disp('Reading component scalp topo polarities - this is done to invert some ERP component polarities');
    STUDY = std_readtopoclust(STUDY, ALLEEG, opt.clusters);
    componentPol = STUDY.cluster(opt.clusters).topopol;
    if isempty(componentPol)
        disp('Cluster topographies absent - cannot adjust single component ERP polarities');
    end
end

% get all sessions (same code as std_readdat)
% -------------------------------------------
allSessions = { STUDY.datasetinfo.session };
allSessions(cellfun(@isempty, allSessions)) = { 1 };
allSessions = cellfun(@num2str, allSessions, 'uniformoutput', false);
uniqueSessions = unique(allSessions);

for iSubj = 1:length(subjectList)
    fprintf('.');
    
    % check cache
    bigstruct = [];
    if ~isempty(opt.channels), bigstruct.channel = opt.channels;
    else                       bigstruct.cluster = opt.clusters; % there can only be one cluster
    end
    bigstruct.datatype     = opt.datatype;
    bigstruct.singletrials = opt.singletrials;
    bigstruct.subject      = subjectList{iSubj};
    bigstruct.component    = opt.component;
    bigstruct.options      = opts;
    bigstruct.moreoptions  = opt;
    if isnan(opt.design)
         bigstruct.design.variable = struct([]);
    else bigstruct.design.variable = STUDY.design(opt.design).variable;
    end

    % find component indices
    % ----------------------
    if ~isempty(opt.clusters)
        datasetInds = strmatch(subjectList{iSubj}, { STUDY.datasetinfo.subject }, 'exact');
        compList    = [];
        polList     = [];
        if isempty(opt.component)
            for iDat = datasetInds(:)'
                indSet   = find(STUDY.cluster(opt.clusters).sets(1,:) == iDat); % each column contain info about the same subject so we many only consider the first row
                if ~isempty(indSet)
                    compList = [ compList STUDY.cluster(opt.clusters).comps(indSet) ];
                    if strcmpi(dtype, 'erp') && strcmpi(opt.componentpol, 'on')
                        polList  = [ polList  componentPol(indSet) ];
                    end
                end
            end
        else
            if ~isempty(intersect(datasetInds, STUDY.cluster(opt.clusters).sets(:,opt.component)))
                compList = [ compList STUDY.cluster(opt.clusters).comps(opt.component) ];
                if strcmpi(dtype, 'erp') && strcmpi(opt.componentpol, 'on')
                    polList  = [ polList  componentPol(opt.component) ];
                end
            end
        end
    end
    
    % read all channels/components at once
    hashcode = gethashcode(std_serialize(bigstruct));
    [STUDY.cache, tmpstruct] = eeg_cache(STUDY.cache, hashcode);
    
    if ~isempty(tmpstruct)
        dataTmp{iSubj}     = tmpstruct{1};
        xvals              = tmpstruct{2};
        yvals              = tmpstruct{3};
        eventsTmp{iSubj}   = tmpstruct{4};
        params             = tmpstruct{5};
        dataTmpSubj{iSubj} = tmpstruct{6};
    else
        datInds = find(strncmp( subjectList{iSubj}, allSubjects, max(cellfun(@length, allSubjects))));

        if isempty(opt.customread)
            fileName = getfilename({STUDY.datasetinfo(datInds).filepath}, STUDY.datasetinfo(datInds(1)).subject, { STUDY.datasetinfo(datInds).session }, fileExt, length(uniqueSessions) == 1);
            if ~isempty(opt.channels)
                 [dataTmp{iSubj}, params, xvals, yvals, eventsTmp{iSubj}, dataTmpSubj{iSubj} ] = std_readfile( fileName, 'designvar', struct(bigstruct.design.variable), opts{:}, 'channels', opt.channels);
            else [dataTmp{iSubj}, params, xvals, yvals, eventsTmp{iSubj}, dataTmpSubj{iSubj} ] = std_readfile( fileName, 'designvar', struct(bigstruct.design.variable), opts{:}, 'components', compList);
            end
        else
            % read custom data
            [dataTmp{iSubj}, params, xvals, yvals, eventsTmp{iSubj}, dataTmpSubj{iSubj}] = feval(opt.customread, STUDY.datasetinfo(datInds), ALLEEG(datInds), struct(bigstruct.design.variable), opt.customparams{:}, opts{:});
        end
        if ~strcmpi(opt.datatype, 'ersp') && ~strcmpi(opt.datatype, 'itc') && ~strcmpi(opt.datatype, 'erpim') % ERP or spectrum or Custom single trials
            % inverting ERP polarity when relevant
            if strcmpi(opt.datatype, 'erp') && ~isempty(opt.clusters) && strcmpi(opt.componentpol, 'on')
                polList = reshape(polList,[1 1 length(polList)]); % components are in the 3rd dim
                dataTmp{iSubj} = cellfun(@(x)bsxfun(@times, x, fastif(isempty(x), [], polList)), dataTmp{iSubj}, 'uniformoutput', false);
            end
            if strcmpi(opt.singletrials, 'off')
                dataTmp{iSubj} = cellfun(@(x)squeeze(mean(x,2)), dataTmp{iSubj}, 'uniformoutput', false); % average
                for iCond = 1:length(dataTmpSubj{iSubj}(:)), if ~isempty(dataTmpSubj{iSubj}{iCond}), dataTmpSubj{iSubj}{iCond} = dataTmpSubj{iSubj}{iCond}(1); end; end
            end
            if strcmpi(opt.datatype, 'spec') && isfield(params, 'logtrials') && strcmpi(params.logtrials, 'off') % if log trial if off it means that single trials are raw power so we need to take the log of the mean
                dataTmp{iSubj} = cellfun(@(x)squeeze(10*log10(x)), dataTmp{iSubj}, 'uniformoutput', false); % average
            end
        elseif strcmpi(opt.datatype, 'erpim')
            %dataTmp{iSubj} = cellfun(@(x)processerpim(x, xvals, params), dataTmp{iSubj}, 'uniformoutput', false);
            for iCond = 1:length(dataTmp{iSubj}(:))
                if all(isnan(eventsTmp{iSubj}{iCond})), eventsTmp{iSubj}{iCond} = []; end
                [dataTmp{iSubj}{iCond}, eventsTmp{iSubj}{iCond}] = processerpim(dataTmp{iSubj}{iCond}, eventsTmp{iSubj}{iCond}, xvals, params);
            end
            nonEmptyCell = find( cellfun(@isempty, dataTmp{iSubj}) == 0);
            if ~isempty(nonEmptyCell)
                yvalsERPim = 1:size(dataTmp{iSubj}{nonEmptyCell(1)},1);
            end
        elseif strcmpi(opt.datatype, 'custom')
            % Nothing to do for custom data, this is done at reading time
        else
            dataTmp{iSubj} = cellfun(@(x)processtf(x, xvals, opt.datatype, opt.singletrials, params), dataTmp{iSubj}, 'uniformoutput', false);
            if strcmpi(opt.singletrials, 'off')
                for iCond = 1:length(dataTmpSubj{iSubj}(:)), if ~isempty(dataTmpSubj{iSubj}{iCond}), dataTmpSubj{iSubj}{iCond} = dataTmpSubj{iSubj}{iCond}(1); end; end
            end
        end
        STUDY.cache = eeg_cache(STUDY.cache, hashcode, { dataTmp{iSubj} xvals yvals eventsTmp{iSubj} params dataTmpSubj{iSubj} });
    end
end
if strcmpi(opt.datatype, 'erpim')
    yvals = yvalsERPim;
end
fprintf('\n');

% if single trials, swap the last 2 dim (put channels before trials)
if strcmpi(opt.singletrials, 'on') && length(opt.channels) > 1
    if ndims(dataTmp{1}{1}) == 3
        for iCase = 1:length(dataTmp)
            for iItem = 1:length(dataTmp{1}(:))
                dataTmp{iCase}{iItem} = permute(dataTmp{iCase}{iItem}, [1 3 2]);
            end
        end
    else
        for iCase = 1:length(dataTmp)
            for iItem = 1:length(dataTmp{1}(:))
                dataTmp{iCase}{iItem} = permute(dataTmp{iCase}{iItem}, [1 2 4 3]);
            end
        end
    end
end

% store data for all subjects
if isempty(opt.ndim)
    if strcmpi(opt.datatype, 'erp') || strcmpi(opt.datatype, 'spec')
        if length(opt.channels) > 1, dim = 3; else dim = 2; end
    else 
        if length(opt.channels) > 1, dim = 4; else dim = 3; end
    end
else
    dim = opt.ndim;
end

% check that all ERPimages have the same number of lines
if strcmpi(opt.datatype, 'erpim')
    [dataTmp,eventsTmp] = checkdataerpimage(dataTmp,eventsTmp);
    events = reorganizedata(eventsTmp, 2);
else
    events = {};
end

if ~isempty(opt.clusters)
    % Split ICA components from the same subjects need to be made 
    % as if coming from different subjects
    dataTmp2 = {};
    dataTmpSubj2 = {};
    correspInd = [];
    realDim  = dim;
    if strcmpi(opt.singletrials, 'on'), realDim = realDim+1; end
    for iDat1 = 1:length(dataTmp)
        for iDat2 = 1:length(dataTmp{iDat1}(:))
            if isempty(dataTmp{iDat1}{iDat2})
                dataTmp{iDat1}{iDat2} = double.empty(0,0,0); % sometimes empty but all dim not 0
            end
        end
        compNumbers = cellfun(@(x)size(x, realDim)*~isempty(x), dataTmp{iDat1});
        uniqComps = unique(compNumbers);
        if length(uniqComps) > 1 
            if ~(uniqComps(1) == 0 && length(uniqComps) == 2)
                error('Cannot handle conditions with different number of components for a given subject');
            end
        end
        
        if any(any(compNumbers))
            for iDat2 = 1:length(dataTmp{iDat1}(:))
                if compNumbers(iDat2)
                    for iComps = 1:compNumbers(iDat2)
                        dataTmp2{end+1}     = cell(size(dataTmp{iDat1}));
                        dataTmpSubj2{end+1} = cell(size(dataTmp{iDat1}));
                        correspInd(end+1) = iDat1;
                        % check dimensions of components
                        if ~isempty(dataTmp{iDat1}{iDat2})
                            if strcmpi(opt.singletrials, 'on') && ...
                                    (strcmpi(tmpDataType, 'timef') || strcmpi(tmpDataType, 'erpim'))
                                dataTmp2{end}{iDat2} = dataTmp{iDat1}{iDat2}(:,:,:,iComps);
                                dataTmpSubj2{end}{iDat2} = dataTmpSubj{iDat1}{iDat2}(:,:,:,1);
                            elseif strcmpi(opt.singletrials, 'on') || ...
                                    (strcmpi(tmpDataType, 'timef') || strcmpi(tmpDataType, 'erpim'))
                                dataTmp2{end}{iDat2} = dataTmp{iDat1}{iDat2}(:,:,iComps);
                                dataTmpSubj2{end}{iDat2} = dataTmpSubj{iDat1}{iDat2}(:,:,1);
                            else                                                                                                       
                                dataTmp2{end}{iDat2} = dataTmp{iDat1}{iDat2}(:,iComps);
                                dataTmpSubj2{end}{iDat2} = dataTmpSubj{iDat1}{iDat2}(:,1);
                            end
                        end
                    end
                end
            end
        end
    end
    dataTmp = dataTmp2;
    dataTmpSubj = dataTmpSubj2;
else
    correspInd = 1:length(dataTmp); % identity for channels 
end
datavals = reorganizedata(dataTmp, dim);

% fix setinds index
if nargout > 6
    datainfo = reorganizedatastruct(dataTmpSubj);
    try
        setinds = cellfun(@(x)[x.index], datainfo, 'UniformOutput', false);
    catch
        error('Error generating dataset indices, either remove the setinds output or recompute your measures')
    end
end

% reorganize data
% ---------------
function datavals = reorganizedata(dataTmp, dim)
    nonEmptyCell = find( cellfun(@isempty, dataTmp) == 0);
    datavals = cell(size(dataTmp{nonEmptyCell(1)}));
        
    % copy data
    for iItem=1:length(dataTmp{nonEmptyCell(1)}(:)')
        if dim > 1
            numItems    = sum(cellfun(@(x)size(x{iItem},dim)*(size(x{iItem},1) > 1), dataTmp)); % the size > 1 allows to detect empty array which have a non-null last dim
        else
            numItems    = sum(cellfun(@(x)size(x{iItem},dim), dataTmp)); % the size > 1 allows to detect empty array which have a non-null last dim
        end
        ind         = find(~cellfun(@(x)isempty(x{iItem}), dataTmp)); 
        if ~isempty(ind)
            ind = ind(1);
            switch dim
                case 1, datavals{iItem} = zeros([ size(dataTmp{ind}{iItem},1) numItems], 'single'); 
                case 2, datavals{iItem} = zeros([ size(dataTmp{ind}{iItem},1) numItems], 'single'); 
                case 3, datavals{iItem} = zeros([ size(dataTmp{ind}{iItem},1) size(dataTmp{ind}{iItem},2) numItems], 'single'); 
                case 4, datavals{iItem} = zeros([ size(dataTmp{ind}{iItem},1) size(dataTmp{ind}{iItem},2) size(dataTmp{ind}{iItem},3) numItems], 'single'); 
            end
        end
    end
    for iItem=1:length(dataTmp{nonEmptyCell(1)}(:)') % conditions * group
        count = 1;
        for iCase = 1:length(dataTmp) % subjects
            if ~isempty(dataTmp{iCase}{iItem})
                if dim > 1
                    numItems = size(dataTmp{iCase}{iItem},dim) * (size(dataTmp{iCase}{iItem},1) > 1); % the size > 1 allows to detect empty array which have a non-null last dim
                else
                    numItems = size(dataTmp{iCase}{iItem},dim);
                end
                switch dim
                    case 1, datavals{iItem}(:,count:count+numItems-1) = dataTmp{iCase}{iItem};
                    case 2, datavals{iItem}(:,count:count+numItems-1) = dataTmp{iCase}{iItem}; 
                    case 3, datavals{iItem}(:,:,count:count+numItems-1) = dataTmp{iCase}{iItem};
                    case 4, datavals{iItem}(:,:,:,count:count+numItems-1) = dataTmp{iCase}{iItem};
                end
                count = count+numItems;
            end
        end
    end

% reorganize data struct
% ---------------
function [datavals,setinds] = reorganizedatastruct(dataTmp)
    nonEmptyCell = find( cellfun(@isempty, dataTmp) == 0);
    datavals = cell(size(dataTmp{nonEmptyCell(1)}));
    setinds  = cell(size(dataTmp{nonEmptyCell(1)}));
        
    % copy data
    for iItem=1:length(dataTmp{nonEmptyCell(1)}(:)') % conditions * group
        count = 1;
        for iCase = 1:length(dataTmp) % subjects
            if ~isempty(dataTmp{iCase}{iItem})
                numItems = length(dataTmp{iCase}{iItem});
                setinds{iItem}(end+1) = iCase;
                if isstruct(dataTmp{iCase}{iItem}) && ~isempty(datavals{iItem})

                    % handle special case of dissimilar structures
                    fields = fieldnames(dataTmp{iCase}{iItem});
                    if isequal(fieldnames(datavals{iItem}), fields)
                        datavals{iItem}(count:count+numItems-1) = dataTmp{iCase}{iItem};
                    else
                        for iField=1:length( fields )
                			datavals{iItem}(count:count+numItems-1).(fields{iField}) = dataTmp{iCase}{iItem}.(fields{iField});
                        end
                    end
                else
                    % for data array
                    datavals{iItem}(count:count+numItems-1) = dataTmp{iCase}{iItem};
                end
                count = count+numItems;
            end
        end
    end    
    
% check data for ERPIMAGE
% -----------------------
function [dataTmp,eventTmp] = checkdataerpimage(dataTmp, eventTmp)
    
    % check second dim for ERPimage
    allsizes = [];
    for iItem=1:length(dataTmp(:))
        allsize2 = cellfun(@(x)size(x,2), dataTmp{iItem});
        allsize2( allsize2 == 0 ) = [];
        allsizes = [ allsizes allsize2 ];
    end
    if length(unique(allsizes(:))) > 1
        disp('********* Discrepancy between the number of lines in ERP-image');
    else
        return;
    end
    commonSize = min(allsizes(:));
    
    % copy data
    for iItem=1:length(dataTmp{1}(:)')
        for iCase = 1:length(dataTmp)
            if ~isempty(dataTmp{iCase}{iItem})
                % special case for ERPimage - one line missing or one line too many
                if size(dataTmp{iCase}{iItem},2)+1 == commonSize
                    dataTmp{iCase}{iItem}(:,end+1) = dataTmp{iCase}{iItem}(:,end); % duplicate last line
                    eventTmp{iCase}{iItem}(end+1) = eventTmp{iCase}{iItem}(end); % duplicate last line
                    disp('******** ERPimage discrepancy between the number of lines detected and corrected')
                elseif size(dataTmp{iCase}{iItem},2)-1 == commonSize
                    dataTmp{iCase}{iItem}(:,end) = [];
                    eventTmp{iCase}{iItem}( end) = [];
                    disp('******** ERPimage discrepancy between the number of lines detected and corrected')
                end
                if size(dataTmp{iCase}{iItem},2) ~= commonSize
                    error('ERPimage discrepancy between the number of lines');
                end
            end
        end
    end    
    
% reorganize data 2
% -----------------
function datavals = reorganizedata2(dataTmp, eventTmp)
    for iCase = 1:length(dataTmp)
        datavals(iCase).data  = dataTmp{iCase};
        datavals(iCase).event = eventTmp{iCase};
    end
    
% call newtimef (duplicate function in std_erspplot)
% --------------
function [dataout,erspbase] = processtf(dataSubject, xvals, datatype, singletrials, g)

    % compute ITC or ERSP
    if strcmpi(datatype, 'ersp')
        P = dataSubject .* conj(dataSubject);
        dataout = newtimeftrialbaseln(P, xvals, g);
        % common baseline is removed in std_erspplot
        if strcmpi(singletrials, 'off')
            dataout = squeeze(mean(dataout, 3));
        end
    else
        dataout = dataSubject;
        if strcmpi(singletrials, 'off')
            if ~isfield(g, 'itctype'), g.itctype = 'phasecoher'; end
            if ndims(dataSubject) == 4
                dataSubject = permute(dataSubject, [4 1 2 3]);
                dataout = newtimefitc(dataSubject, g.itctype);
                dataout = permute(dataout, [2 3 1]);
            else
                dataout = newtimefitc(dataSubject, g.itctype);
            end
            dataout = abs(dataout); % required for plotting scalp topo
        end
    end
  
% call erpimage
% -------------
function [dataout, eventout] = processerpim(dataSubject, events, xvals, g)

    if isempty(dataSubject), dataout = []; eventout = []; return; end
    if ~isfield(g, 'nlines'), finallines = 10; else finallines = g.nlines; end
    if ~isfield(g, 'smoothing'), smoothing = 10; else smoothing = g.smoothing; end
    
    % remove all fields and create new parameter list
    fieldList = { 'nlines' 'smoothing' 'sorttype' 'sortwin' 'sortfield' 'channels' ...
                  'interp' 'trialinfo' 'concatenate' 'savetrials' 'recompute' 'fileout' 'events' 'rmcomps'};
    params = {};
    fieldN = fieldnames(g);
    for iField = 1:length(fieldN)
        if ~ismember(fieldN{iField}, fieldList)
            params{end+1} = fieldN{iField};
            params{end+1} = g.(fieldN{iField});
        end
    end
    
    % reverse engeeneering the number of lines for ERPimage
    if ~isempty(events)
         if all(isnan(events))
             error('Cannot sort trials for one of the dataset');
         end
         lastx  = sum(~isnan(events));
    else lastx  = size(dataSubject,2);
    end
    if lastx < finallines + floor((g.smoothing-1)/2) + 3
        error('The default number of ERPimage lines is too large for one of the dataset');
    end
    firstx = 1;
    xwidth = g.smoothing;
    %xadv   = lastx/finallines;
    nout   = finallines; %floor(((lastx-firstx+xadv+1)-xwidth)/xadv);
    nlines = (lastx-xwidth)/(nout-0.5)*i; % make it imaginary
    %nlines = ceil(lastx/((lastx-firstx+1-xwidth)/(nout-1)));
           
    if ~isempty(params) && ischar(params{1}) && strcmpi(params{1}, 'components')
        params(1:2) = [];
    end
    for iChan = 1:size(dataSubject,3)
        [dataout(:,:,iChan), eventout] = erpimage(dataSubject(:,:,iChan), events, xvals, '', smoothing, nlines, 'noplot', 'on', params{:});
    end
    dataout = permute(dataout, [2 1 3]); % for tftopo
    if ~isempty(events)
        eventout = eventout'; % needs to be a column vector
    else
        eventout = [];
    end

% get file base name: filepath and sess are cell array (in case 2 files per subject)
% ----------------------------------------------------------------------------------
function filebase = getfilename(filepath, subj, sess, fileSuffix, onlyOneSession)
if onlyOneSession
    filebase = fullfile(filepath{1}, [ subj fileSuffix ] );
    if exist(filebase, 'file')
        return;
    end
    clear filebase;
end

if isempty(sess)
    sess = { '1' };
end
for iSess = 1:length(sess)
    if isnumeric(sess{iSess})
        sesStr   = [ '0' num2str(sess{iSess}) ];
    else
        sesStr   = [ '0' sess{iSess} ];
    end
    filebase{iSess} = fullfile(filepath{iSess}, [ subj '_ses-' sesStr(end-1:end) fileSuffix ] );
end
if length(unique(filebase)) < length(filebase)
    filebase = unique(filebase); % order is not important
end
    
