From f71d554d3b3e359b356d499acdd339b0c1943010 Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Sun, 20 Aug 2023 20:44:35 +0200 Subject: [PATCH] new branch --- src/pspm_check_model.m | 125 +++++++++++++++++++++++++++++++++++++++++ src/pspm_dcm.m | 116 +++----------------------------------- src/pspm_glm.m | 25 ++------- src/pspm_sf.m | 29 +++------- 4 files changed, 145 insertions(+), 150 deletions(-) create mode 100644 src/pspm_check_model.m diff --git a/src/pspm_check_model.m b/src/pspm_check_model.m new file mode 100644 index 000000000..78fef718e --- /dev/null +++ b/src/pspm_check_model.m @@ -0,0 +1,125 @@ +function model = pspm_check_model(model, modeltype) + +% General checks +if ~isstruct(model) + warning('ID:invalid_input', 'Model must be a struct.'); + model = struct('invalid', 1); + return +else + model.invalid = 1; + if isempty(model) + warning('ID:invalid_input', 'Model is empty.'); + return + end +end + +% 1. Reject missing mandatory fields common to all models +if ~isfield(model, 'datafile') + warning('ID:invalid_input', 'No input data file specified.'); return; +elseif ~isfield(model, 'modelfile') + warning('ID:invalid_input', 'No output model file specified.'); return; +elseif ~isfield(model, 'timing') + warning('ID:invalid_input', 'No event onsets specified.'); return; +end + +% 2. Reject wrong type of mandatory fields +if ~iscell(model.datafile) && ~ischar(model.datafile) + warning('ID:invalid_input', 'Input data must be a cell or string.'); return; +elseif ~ischar(model.modelfile) + warning('ID:invalid_input', 'Output model must be a string.'); return; +elseif ~ischar(model.timing) && ~iscell(model.timing) && ~isstruct(model.timing) + warning('ID:invalid_input', 'Event definition must be a string, cell array, or (for GLM) struct.'); return; +end + +% 3. Fill missing fields common to all models, and accept only allowed values +if ~isfield(model, 'norm') + model.norm = 0; +elseif ~any(ismember(model.norm, [0, 1])) + warning('ID:invalid_input', 'Normalisation must be specified as 0 or 1.'); return; +end +if ~isfield(model, 'missing') + model.missing = cell(nFile, 1); +elseif ischar(model.missing) || isnumeric(model.missing) + model.missing = {model.missing}; +elseif ~iscell(model.missing) + warning('ID:invalid_input',... + 'Missing values must be a filename, matrix, or cell array of these.'); + return +end + +% 4. Check that data files, timing, and missing values are cell arrays with +% matching size +if ischar(model.datafile) + model.datafile = {model.datafile}; + model.timing = {model.timing}; +end + +nFile = numel(model.datafile); +if nFile ~= numel(model.timing) + warning('ID:number_of_elements_dont_match',... + 'Session numbers of data files and event definitions do not match.'); + return +end +if nFile ~= numel(model.missing) + warning('ID:number_of_elements_dont_match',... + 'Same number of data files and missing value definitions is needed.'); + return +end + +% GLM-specific checks +% ------------------------------------------------------------------------- +if strcmpi(modeltype, 'glm') + % Reject missing mandatory fields + if ~isfield(model, 'timeunits') + warning('ID:invalid_input', 'No timeunits specified.'); return; + end +end + + +% DCM-specific check +% ------------------------------------------------------------------------- +if strcmpi(modeltype, 'dcm') + if ~ischar(model.timing) && ~iscell(model.timing) + warning('ID:invalid_input', 'Event definition must be a string or cell array.'); return; + end + % Fill missing fields and detect wrong values + if ~isfield(model, 'channel') + model.channel = 'scr'; % this returns the last SCR channel + elseif ~isnumeric(model.channel) && ~strcmp(model.channel,'scr') + warning('ID:invalid_input', 'Channel number must be numeric.'); return; + end + if ~isfield(model, 'constrained') + model.constrained = 0; + elseif ~any(ismember(model.constrained, [0, 1])) + warning('ID:invalid_input', 'Constrained model must be specified as 0 or 1.'); return; + end + if ~isfield(model, 'substhresh') + model.substhresh = 2; + elseif ~isnumeric(model.substhresh) + warning('ID:invalid_input', 'Subsession threshold must be numeric.'); + return; + end +end + + + +% General checks that require GLM-specific checks in case of GLM +% ------------------------------------------------------------------------- +if ~isfield(model, 'filter') + if strcmpi(modeltype, 'glm') + model.filter = settings.glm(modno).filter; +elseif strcmpi(modeltype, 'dcm') + model.filter = settings.dcm{1}.filter; +elseif ~isfield(model.filter, 'down') || ~isnumeric(model.filter.down) + warning('ID:invalid_input', 'Filter structure needs a numeric ''down'' field.'); return; +end + + +% 2.7 check filter -- + + + +try model.lasttrialcutoff; catch, model.lasttrialcutoff = 7; end + + +model.invalid = 0; \ No newline at end of file diff --git a/src/pspm_dcm.m b/src/pspm_dcm.m index f85524182..2f0e87391 100644 --- a/src/pspm_dcm.m +++ b/src/pspm_dcm.m @@ -177,82 +177,21 @@ warnings = {}; %% 2 Check input arguments & set defaults -% 2.1 check input -if nargin < 1 - warning('ID:invalid_input', 'No data to work on.'); - return -elseif nargin < 2 - options = struct(); -end -if ~isfield(model, 'datafile') - warning('ID:invalid_input', 'No input data file specified.'); return; -elseif ~isfield(model, 'modelfile') - warning('ID:invalid_input', 'No output model file specified.'); return; -elseif ~isfield(model, 'timing') - warning('ID:invalid_input', 'No event onsets specified.'); return; -end - -% 2.2 check faulty input -if ~iscell(model.datafile) && ~ischar(model.datafile) - warning('ID:invalid_input', 'Input data must be a cell or string.'); return; -elseif ~ischar(model.modelfile) - warning('ID:invalid_input', 'Output model must be a string.'); return; -elseif ~ischar(model.timing) && ~iscell(model.timing) - warning('ID:invalid_input', 'Event definition must be a string or cell array.'); return; -end - -% 2.3 get further input or set defaults -- -% check data channel -- -if ~isfield(model, 'channel') - model.channel = 'scr'; % this returns the last SCR channel -elseif ~isnumeric(model.channel) && ~strcmp(model.channel,'scr') - warning('ID:invalid_input', 'Channel number must be numeric.'); return; -end - -% 2.4 check normalisation -- -if ~isfield(model, 'norm') - model.norm = 0; -elseif ~any(ismember(model.norm, [0, 1])) - warning('ID:invalid_input', 'Normalisation must be specified as 0 or 1.'); return; -end - -% 2.5 check constrained model -- -if ~isfield(model, 'constrained') - model.constrained = 0; -elseif ~any(ismember(model.constrained, [0, 1])) - warning('ID:invalid_input', 'Constrained model must be specified as 0 or 1.'); return; -end - -% 2.6 check substhresh -- -if ~isfield(model, 'substhresh') - model.substhresh = 2; -elseif ~isnumeric(model.substhresh) - warning('ID:invalid_input', 'Subsession threshold must be numeric.'); - return; -end +% 2.1 check missing input -- +if nargin < 1; errmsg = 'Nothing to do.'; warning('ID:invalid_input', errmsg); return +elseif nargin < 2; options = struct(); end -% 2.7 check filter -- -if ~isfield(model, 'filter') - model.filter = settings.dcm{1}.filter; -elseif ~isfield(model.filter, 'down') || ~isnumeric(model.filter.down) - warning('ID:invalid_input', 'Filter structure needs a numeric ''down'' field.'); return; +% 2.2 check model +model = pspm_check_model(model, 'dcm'); +if model.invalid + return end -if ~isstruct(options) - warning('ID:invalid_input', '''options'' must be a struct.'); - return; -end - - -% 2.8 set and check options --- +% 2.3 check options (could be re-factored into pspm_options) options = pspm_options(options, 'dcm'); if options.invalid return end - -try model.lasttrialcutoff; catch, model.lasttrialcutoff = 7; end - -% 2.9 check option fields -- % numeric fields num_fields = {'depth', 'sfpre', 'sfpost', 'sffreq', 'sclpre', ... 'sclpost', 'aSCR_sigma_offset'}; @@ -289,44 +228,6 @@ return end -if ischar(model.datafile) - model.datafile = {model.datafile}; - model.timing = {model.timing}; -end - -nFile = numel(model.datafile); -if ~isfield(model, 'missing') - model.missing = cell(nFile, 1); -elseif ischar(model.missing) || isnumeric(model.missing) - model.missing = {model.missing}; -elseif ~iscell(model.missing) - warning('ID:invalid_input',... - 'Missing values must be a filename, matrix, or cell array of these.'); - return -end -if nFile ~= numel(model.timing) - warning('ID:number_of_elements_dont_match',... - 'Session numbers of data files and event definitions do not match.'); - return -end -if nFile ~= numel(model.missing) - warning('ID:number_of_elements_dont_match',... - 'Same number of data files and missing value definitions is needed.'); - return -end - -% 2.11 Fill model values -try model.aSCR; catch, model.aSCR = 0; end -try model.eSCR; catch, model.eSCR = 0; end -try model.meanSCR; catch, model.meanSCR = 0; end -% These parameters were set with default fallback values but will be -% determined later by processing -% try model.fixevents; catch, warning('model.fixevents not defined.'); end -% try model.flexevents; catch, warning('model.flexevents not defined.'); end -% try model.missing_data; catch, warning('model.missing_data not defined.'); end -% These parameters do not need to have a default value and will be -% determined later - %% 3 Check, get and prepare data % split into subsessions @@ -348,7 +249,6 @@ sr{iSn} = data{iSn}{end}.header.sr; model.filter.sr = sr{iSn}; - % load and check existing missing data (if defined) if ~isempty(model.missing{iSn}) [~, missing{iSn}] = pspm_get_timing('missing', ... diff --git a/src/pspm_glm.m b/src/pspm_glm.m index 2b05b4f51..915266358 100644 --- a/src/pspm_glm.m +++ b/src/pspm_glm.m @@ -158,14 +158,9 @@ % 2.1 check missing input -- if nargin < 1; errmsg = 'Nothing to do.'; warning('ID:invalid_input', errmsg); return elseif nargin < 2; options = struct(); end +model = pspm_check_model(model, 'glm'); + fprintf('Computing GLM: %s ...\n', model.modelfile); -if ~isfield(model, 'datafile') - warning('ID:invalid_input', 'No input data file specified.'); return; -elseif ~isfield(model, 'modelfile') - warning('ID:invalid_input', 'No output model file specified.'); return; -elseif ~isfield(model, 'timeunits') - warning('ID:invalid_input', 'No timeunits specified.'); return; -end % 2.2 check existing -- % whether field timing doesnt exist, field is emtpy or field is cell with empty entries if ~isfield(model, 'timing') || isempty(model.timing) || ... @@ -183,11 +178,7 @@ model.latency = 'fixed'; end % 2.4 check faulty input -- -if ~ischar(model.datafile) && ~iscell(model.datafile) - warning('ID:invalid_input', 'Input data must be a cell or string.'); return; -elseif ~ischar(model.modelfile) - warning('ID:invalid_input', 'Output model must be a string.'); return; -elseif ~ischar(model.timing) && ~iscell(model.timing) && ~isstruct(model.timing) +if ~ischar(model.timing) && ~iscell(model.timing) && ~isstruct(model.timing) warning('ID:invalid_input', 'Event onsets must be a string, cell, or struct.'); return; elseif ~ischar(model.timeunits) || ~ismember(model.timeunits, {'seconds', 'markers', 'samples','markervalues'}) warning('ID:invalid_input', 'Timeunits (%s) not recognised; only ''seconds'', ''markers'' and ''samples'' are supported', model.timeunits); return; @@ -215,12 +206,7 @@ elseif ~isnumeric(model.channel) && ~ismember(model.channel, {settings.channeltypes.type}) warning('ID:invalid_input', 'Channel number must be numeric.'); return; end -% 2.7 check normalisation -- -if ~isfield(model, 'norm') - model.norm = 0; -elseif ~ismember(model.norm, [0, 1]) - warning('ID:invalid_input', 'Normalisation must be specified as 0 or 1.'); return; -end + % 2.8 check mean centering -- if ~isfield(model,'centering') model.centering = 1; @@ -274,9 +260,6 @@ end %% 4 check filter -if ~isfield(model, 'filter') - model.filter = settings.glm(modno).filter; -end % 4.1 set default model.filter.down -- if strcmpi(model.filter.down, 'none') || ... isnumeric(model.filter.down) && isnan(model.filter.down) diff --git a/src/pspm_sf.m b/src/pspm_sf.m index b725f6713..0d8539066 100644 --- a/src/pspm_sf.m +++ b/src/pspm_sf.m @@ -85,28 +85,15 @@ end %% 2 Check input % 2.1 Check missing input -- -if nargin<1 - warning('ID:invalid_input', 'Nothing to do.'); return; -elseif nargin<2 - options = struct(); -end -if ~isfield(model, 'datafile') - warning('ID:invalid_input', 'No input data file specified.'); return; -elseif ~isfield(model, 'modelfile') - warning('ID:invalid_input', 'No output model file specified.'); return; -elseif ~isfield(model, 'timeunits') - warning('ID:invalid_input', 'No timeunits specified.'); return; -elseif ~isfield(model, 'timing') && ~strcmpi(model.timeunits, 'file') - warning('ID:invalid_input', 'No epochs specified.'); return; -end +if nargin < 1; errmsg = 'Nothing to do.'; warning('ID:invalid_input', errmsg); return +elseif nargin < 2; options = struct(); end +model = pspm_check_model(model, 'sf'); + + + + % 2.2 Check faulty input -- -if ~ischar(model.datafile) && ~iscell(model.datafile) - warning('ID:invalid_input', 'Input data must be a cell or string.'); return; -elseif ~ischar(model.modelfile) && ~iscell(model.modelfile) - warning('ID:invalid_input', 'Output model must be a string.'); return; -elseif ~ischar(model.timing) && ~iscell(model.timing) && ~isnumeric(model.timing) - warning('ID:invalid_input', 'Event onsets must be a string, cell, or struct.'); return; -elseif ~ischar(model.timeunits) || ~ismember(model.timeunits, {'seconds', 'markers', 'samples', 'whole'}) +if ~ischar(model.timeunits) || ~ismember(model.timeunits, {'seconds', 'markers', 'samples', 'whole'}) warning('ID:invalid_input',... 'Timeunits (%s) not recognised; ',... 'only ''seconds'', ''markers'', ''samples'' and ''whole'' are supported',...