% STD_MAKEDESIGN - create a new or edit an existing STUDY.design by
% selecting specific factors to include in subsequent
% 1x2 or 2x2 STUDY measures and statistical computations
% for this design. A STUDY may have many factors
% (task or stimulus conditions, subject groups, session
% numbers, trial types, etc.), but current EEGLAB
% STUDY statistics functions apply only to at most two
% (paired or unpaired) factors. A STUDY.design may
% also be (further) restricted to include only specific
% subjects, datasets, or trial types.
% Usage:
% >> [STUDY] = std_makedesign(STUDY, ALLEEG); % create a default design
% >> [STUDY] = std_makedesign(STUDY, ALLEEG, designind, 'key', 'val' ...);
%
% Inputs:
% STUDY - EEGLAB STUDY set
% ALLEEG - vector of the EEG datasets included in the STUDY structure
% designind - [integer > 0] index (number) of the new STUDY design {default: 1}
%
% Optional inputs:
% 'name' - ['string'] mnemonic design name (ex: 'Targets only')
% {default: 'Design d', where d = designind}
% 'variable1' - ['string'] - first independent variable or contrast. Must
% be a field name in STUDY.datasetinfo or in
% STUDY.datasetinfo.trialinfo. Typical choices include (task
% or other) 'condition', (subject) 'group', (subject) 'session',
% or other condition/group/session factors in STUDY.datasetinfo
% -- for example (subject factor) 'gender' or (condition factor)
% 'timeofday', etc. If trial type variables are defined in
% STUDY.datasetinfo.trialinfo, they may also be used here
% -- for example, 'stimcolor'. However, in this case datasets
% consist of heterogeneous sets of trials of different types,
% so many dataset Plot and Tools menu items may not give
% interpretable results and will thus be made unavailable for
% selection {default: 'condition'}
% 'values1' - {cell array of 'strings'} - 'variable1' instances to include
% in the design. For example, if 'variable1' is 'condition'and
% three values for 'condition' (e.g., 'a' , 'b', and 'c')
% are listed in STUDY.datasetinfo, then 'indval1', { 'a' 'b' }
% will contrast conditions 'a' and 'b', and datasets for
% condition 'c' will be ignored. To combine conditions, use
% nested '{}'s. For example, to combine conditions 'a' and
% 'b' into one condition and contrast it to condition 'c',
% specify 'indval1', { { 'a' 'b' } 'c' } {default: all values
% of 'variable1' in STUDY.datasetinfo}
% 'variable2' - ['string'] - second independent variable name, if any. Typically,
% this might refer to ('unpaired') subject group or (typically
% 'paired') session number, etc.
% 'values2' - {cell array of 'strings'} - variable2 values to include in the
% design {default: all}. Here, 'var[12]' must be field names
% in STUDY.datasetinfo or STUDY.datasetinfo.trialinfo.
% 'subjselect' - {cell array} subject codes of specific subjects to include
% in the STUDY design {default: all subjects in the specified
% conditions, groups, etc.} If 'datselect' is also specified,
% only datasets meeting both criteria are included.
% 'rmfiles' - ['on'|'off'] remove from the STUDY all data measure files
% NOT included in this design. Selecting this option will
% remove all the old measure files associated with the previous
% definition of this design. {default: 'off'}
% 'filepath' - [string] file path for saving precomputed files. Default is
% empty meaning it is in the same folder as the data.
% 'delfiles' - ['on'|'off'|'limited'] delete data files
% associated with the design specified as parameter. 'on'
% delete all data files related to the design. 'limited'
% deletes all data files contained in the design. 'limited'
% will not delete data files from other STUDY using the same
% files. Default is 'off'.
%
% Output:
% STUDY - The input STUDY with a new design added and designated
% as the current design.
%
% Examples:
% STUDY = std_makedesign(STUDY, ALLEEG); % make default design
%
% % create design with 1 independent variable equal to 'condition'
% STUDY = std_makedesign(STUDY, ALLEEG, 2, 'variable1', 'condition');
%
% % create design with 1 independent variable equal to 'condition'
% % but only consider the sub-condition 'stim1' and 'stim2' - of course
% % such conditions must be present in the STUDY
% STUDY = std_makedesign(STUDY, ALLEEG, 2, 'variable1', 'condition', ...
% 'values1', { 'stim1' 'stim2' });
%
% % create design and only include subject 's1' and 's2'
% STUDY = std_makedesign(STUDY, ALLEEG, 2, 'variable1', 'condition', ...
% 'subjselect', { 's1' 's2' });
%
% Author: Arnaud Delorme, Institute for Neural Computation UCSD, 2010-
% Copyright (C) Arnaud Delorme, 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.
function [STUDY, com] = std_makedesign(STUDY, ALLEEG, designind, varargin)
if nargin < 2
help std_makedesign;
return;
end
if nargin < 3
designind = 1;
end
defdes.name = sprintf('STUDY.design %d', designind);
defdes.cases.label = 'subject';
defdes.cases.value = {};
defdes.variable(1).label = 'condition';
defdes.variable(2).label = 'group';
defdes.variable(3).label = '';
defdes.variable(4).label = '';
defdes.variable(1).value = {};
defdes.variable(2).value = {};
defdes.variable(3).value = {};
defdes.variable(4).value = {};
defdes.variable(1).vartype = 'categorical';
defdes.variable(2).vartype = 'categorical';
defdes.filepath = '';
defdes.include = {};
orivarargin = varargin;
if ~isempty(varargin) && isstruct(varargin{1})
defdes = varargin{1};
varargin(1) = [];
end
if isempty(defdes.filepath), defdes.filepath = ''; end
if length(defdes.variable) == 0, defdes.variable(1).label = 'continuous'; defdes.variable(1).vartype = 'categorical'; defdes.variable(1).value = {}; end
if length(defdes.variable) == 1, defdes.variable(2).label = 'continuous'; defdes.variable(2).vartype = 'categorical'; defdes.variable(2).value = {}; end
if length(defdes.variable) == 2, defdes.variable(3).label = 'continuous'; defdes.variable(3).vartype = 'categorical'; defdes.variable(3).value = {}; end
if length(defdes.variable) == 3, defdes.variable(4).label = 'continuous'; defdes.variable(4).vartype = 'categorical'; defdes.variable(4).value = {}; end
for iVar = 1:4
if isempty(defdes.variable(iVar).vartype), defdes.variable(iVar).vartype = 'continuous'; end
if isempty(defdes.variable(iVar).label) , defdes.variable(iVar).label = ''; end
end
opt = finputcheck(varargin, {'variable1' 'string' [] defdes.variable(1).label;
'variable2' 'string' [] defdes.variable(2).label;
'variable3' 'string' [] defdes.variable(3).label;
'variable4' 'string' [] defdes.variable(4).label;
'values1' {'real','cell' } [] defdes.variable(1).value;
'values2' {'real','cell' } [] defdes.variable(2).value;
'values3' {'real','cell' } [] defdes.variable(3).value;
'values4' {'real','cell' } [] defdes.variable(4).value;
'vartype1' 'string' {'categorical' 'continuous'} defdes.variable(1).vartype;
'vartype2' 'string' {'categorical' 'continuous'} defdes.variable(2).vartype;
'vartype3' 'string' {'categorical' 'continuous'} defdes.variable(3).vartype;
'vartype4' 'string' {'categorical' 'continuous'} defdes.variable(4).vartype;
'name' 'string' {} defdes.name;
'filepath' 'string' {} defdes.filepath;
'datselect' 'cell' {} defdes.include;
'dataselect' 'cell' {} {};
'subjselect' 'cell' {} defdes.cases.value;
'delfiles' 'string' { 'on','off', 'limited' } 'off';
'verbose' 'string' { 'on','off' } 'on';
'defaultdesign' 'string' { 'on','off','forceoff'} fastif(nargin < 3, 'on', 'off') }, ...
'std_makedesign', 'ignore');
if ischar(opt), error(opt); end
if ~isempty(opt.dataselect), opt.datselect = opt.dataselect; end
if strcmpi(opt.variable1, 'none'), opt.variable1 = ''; end
if strcmpi(opt.variable2, 'none'), opt.variable2 = ''; end
%if iscell(opt.values1), for i = 1:length(opt.values1), if iscell(opt.values1{i}), opt.values1{i} = cell2str(opt.values1{i}); end; end; end
%if iscell(opt.values2), for i = 1:length(opt.values2), if iscell(opt.values2{i}), opt.values2{i} = cell2str(opt.values2{i}); end; end; end
% check inputs
% ------------
[indvars, indvarvals, ~, paired] = std_getindvar(STUDY);
if isfield(STUDY.datasetinfo, 'trialinfo')
alltrialinfo = { STUDY.datasetinfo.trialinfo };
dattrialselect = cellfun(@(x)([1:length(x)]), alltrialinfo, 'uniformoutput', false);
else alltrialinfo = cell(length(STUDY.datasetinfo));
for i=1:length(ALLEEG), dattrialselect{i} = [1:ALLEEG(i).trials]; end
end
% get values for each independent variable
% ----------------------------------------
m1 = strmatch(opt.variable1, indvars, 'exact'); if isempty(m1), opt.variable1 = ''; end
m2 = strmatch(opt.variable2, indvars, 'exact'); if isempty(m2), opt.variable2 = ''; end
m3 = strmatch(opt.variable3, indvars, 'exact'); if isempty(m3), opt.variable3 = ''; end
m4 = strmatch(opt.variable4, indvars, 'exact'); if isempty(m4), opt.variable4 = ''; end
if isempty(opt.values1) && ~isempty(opt.variable1), opt.values1 = indvarvals{m1}; end
if isempty(opt.values2) && ~isempty(opt.variable2), opt.values2 = indvarvals{m2}; end
if isempty(opt.values3) && ~isempty(opt.variable3), opt.values3 = indvarvals{m3}; end
if isempty(opt.values4) && ~isempty(opt.variable4), opt.values4 = indvarvals{m4}; end
if isempty(opt.variable1), opt.values1 = { '' }; end
if isempty(opt.variable2), opt.values2 = { '' }; end
if isempty(opt.variable3), opt.values3 = { '' }; end
if isempty(opt.variable4), opt.values4 = { '' }; end
% build command list for history
% ------------------------------
listcom = { 'name' opt.name 'delfiles' opt.delfiles 'defaultdesign' opt.defaultdesign };
if ~isempty(opt.variable1), listcom = { listcom{:} 'variable1' opt.variable1 'values1' opt.values1 'vartype1' opt.vartype1 }; end
if ~isempty(opt.variable2), listcom = { listcom{:} 'variable2' opt.variable2 'values2' opt.values2 'vartype2' opt.vartype2 }; end
if ~isempty(opt.variable3), listcom = { listcom{:} 'variable3' opt.variable3 'values3' opt.values3 'vartype3' opt.vartype3 }; end
if ~isempty(opt.variable4), listcom = { listcom{:} 'variable4' opt.variable4 'values4' opt.values4 'vartype4' opt.vartype4 }; end
if ~isempty(opt.subjselect), listcom = { listcom{:} 'subjselect' opt.subjselect }; end
if ~isempty(opt.datselect), listcom = { listcom{:} 'datselect' opt.datselect }; end
if ~isempty(opt.filepath), listcom = { listcom{:} 'filepath' opt.filepath }; end
if ~isempty(opt.datselect), listcom = { listcom{:} 'datselect' opt.datselect }; end
% select specific subjects
% ------------------------
datsubjects = { STUDY.datasetinfo.subject };
if ~isempty(opt.subjselect)
allsubjects = opt.subjselect;
else
allsubjects = unique_bc( datsubjects );
end
% delete design files
% ---------------------
if strcmpi(opt.delfiles, 'on')
myfprintf(opt.verbose, 'Deleting all files pertaining to design %d\n', designind);
for index = 1:length(ALLEEG)
files = fullfile(ALLEEG(index).filepath, sprintf(opt.verbose, 'design%d*.*', designind));
files = dir(files);
for indf = 1:length(files)
delete(fullfile(ALLEEG(index).filepath, files(indf).name));
end
end
elseif strcmpi(opt.delfiles, 'limited')
myfprintf(opt.verbose, 'Deleting all files for STUDY design %d\n', designind);
for index = 1:length(STUDY.design(designind).cell)
filedir = [ STUDY.design(designind).cell(index).filebase '.dat*' ];
filepath = fileparts(filedir);
files = dir(filedir);
for indf = 1:length(files)
%disp(fullfile(filepath, files(indf).name));
delete(fullfile(filepath, files(indf).name));
end
end
for index = 1:length(STUDY.design(designind).cell)
filedir = [ STUDY.design(designind).cell(index).filebase '.ica*' ];
filepath = fileparts(filedir);
files = dir(filedir);
for indf = 1:length(files)
%disp(fullfile(filepath, files(indf).name));
delete(fullfile(filepath, files(indf).name));
end
end
end
% preselect data
% --------------
datselect = [1:length(STUDY.datasetinfo)];
if ~isempty(opt.datselect)
fprintf(2, '**********************************\n')
fprintf(2, 'The ''datselect'' option is obsolete\n')
fprintf(2, '**********************************\n\n')
error('This version of EEGLAB no longer support ''datselect''; please remove it from the list of inputs')
for ind = 1:2:length(opt.datselect)
[ dattmp, dattrialstmp ] = std_selectdataset( STUDY, ALLEEG, opt.datselect{ind}, opt.datselect{ind+1});
datselect = intersect_bc(datselect, dattmp);
dattrialselect = intersectcell(dattrialselect, dattrialstmp);
end
end
datselect = intersect_bc(datselect, strmatchmult(allsubjects, datsubjects));
des.name = opt.name;
des.filepath = opt.filepath;
if ~isempty(opt.variable1)
des.variable(1).label = opt.variable1;
des.variable(1).value = opt.values1;
des.variable(1).vartype = opt.vartype1;
des.variable(1).pairing = paired{m1};
end
if ~isempty(opt.variable2)
des.variable(2).label = opt.variable2;
des.variable(2).value = opt.values2;
des.variable(2).vartype = opt.vartype2;
des.variable(2).pairing = paired{m2};
end
if ~isempty(opt.variable3)
des.variable(3).label = opt.variable3;
des.variable(3).value = opt.values3;
des.variable(3).vartype = opt.vartype3;
des.variable(3).pairing = paired{m3};
end
if ~isempty(opt.variable4)
des.variable(4).label = opt.variable4;
des.variable(4).value = opt.values4;
des.variable(4).vartype = opt.vartype4;
des.variable(4).pairing = paired{m4};
end
des.include = opt.datselect;
des.cases.label = 'subject';
des.cases.value = opt.subjselect;
if isempty(des.cases.value), des.cases.value = STUDY.subject; end
fieldorder = { 'name' 'filepath' 'variable' 'cases' 'include' };
if ~isfield(des, 'variable'), des.variable = []; end
des = orderfields(des, fieldorder);
try
STUDY.design = orderfields(STUDY.design, fieldorder);
catch
STUDY.design = [];
end
if ~isfield(STUDY, 'design') || isempty(STUDY.design)
STUDY.design = des;
else
STUDY.design(designind) = des; % fill STUDY.design
end
STUDY = std_addvarlevel(STUDY, designind);
% remove empty variables
if designind <= length(STUDY.design)
for iVar = length(STUDY.design(designind).variable):-1:1
if isempty(STUDY.design(designind).variable(iVar).label)
STUDY.design(designind).variable(iVar) = [];
end
end
end
% reorder variable putting single value var at the end
% this allows selecting some specific conditions and still processing the
% data in EEGLAB standard stat functions
if length(STUDY.design(designind).variable) > 2
if all(cellfun(@(x)isequal(x, 'categorical'), { STUDY.design(designind).variable(3).vartype })) % only categorical
varLen = cellfun(@(x)length(x), {STUDY.design(designind).variable.value});
indLen1 = find(varLen == 1);
STUDY.design(designind).variable = [ STUDY.design(designind).variable(setdiff(1:length(varLen), indLen1)) STUDY.design(designind).variable(indLen1) ];
end
end
% select the new design in the STUDY output
% -----------------------------------------
STUDY.currentdesign = designind;
STUDY = std_selectdesign(STUDY, ALLEEG, designind);
STUDY.cache = [];
% build output command
% --------------------
com = sprintf('STUDY = std_makedesign(STUDY, ALLEEG, %d, %s);', designind, vararg2str( listcom ) );
% ---------------------------------------------------
% return intersection of cell arrays
% ----------------------------------
function res = intersectcell(a, b, c)
if nargin > 2
res = intersectcell(a, intersectcell(b, c));
else
for index = 1:length(a)
res{index} = intersect_bc(a{index}, b{index});
end
end
% perform multi strmatch
% ----------------------
function res = strmatchmult(a, b)
res = [];
for index = 1:length(a)
res = [ res strmatch(a{index}, b, 'exact')' ];
end
% remove blanks
% -------------
function res = rmblk(a)
if iscell(a), a = cell2str(a); end
if ~ischar(a), a = num2str(a); end
res = a;
res(find(res == ' ')) = '_';
res(find(res == '\')) = '_';
res(find(res == '/')) = '_';
res(find(res == ':')) = '_';
res(find(res == ';')) = '_';
res(find(res == '"')) = '_';
function strval = cell2str(vals);
strval = vals{1};
for ind = 2:length(vals)
strval = [ strval ' - ' vals{ind} ];
end
function tmpfile = checkfilelength(tmpfile)
if length(tmpfile) > 120
tmpfile = tmpfile(1:120);
end
function myfprintf(verbose, varargin)
if strcmpi(verbose, 'on'), fprintf(varargin{:}); end