From 84de37ba32e792d0f9d1bcd328e15a5e9cbd5c2f Mon Sep 17 00:00:00 2001 From: "teddy.chao" Date: Sun, 18 Jun 2023 22:44:26 +0100 Subject: [PATCH 01/26] Update pspm_glm.m --- src/pspm_glm.m | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/pspm_glm.m b/src/pspm_glm.m index 3cf10d500..d04820685 100644 --- a/src/pspm_glm.m +++ b/src/pspm_glm.m @@ -1,4 +1,4 @@ -function varargout = pspm_glm(model, options) +function glm = pspm_glm(model, options) % ● Description % pspm_glm specifies a within subject general linear convolution model of % predicted signals and calculates amplitude estimates for these responses. @@ -260,9 +260,9 @@ warning('ID:invalid_input', 'Could not load the specified markerchannel'); return end - if length(data) > 1 - data = data{end}; - end + % if length(data) > 1 + % data = data{end}; + % end switch class(options.marker_chan_num_event) case 'double' if options.marker_chan_num_event>length(data) @@ -344,7 +344,8 @@ 'functions are not allowed.']); return; end catch - warning('ID:invalid_fhandle', 'Specified basis function %s doesn''t exist or is faulty', func2str(model.bf.fhandle)); return; + warning('ID:invalid_fhandle', 'Specified basis function %s doesn''t exist or is faulty', func2str(model.bf.fhandle)); + return; end % 5.1 set shiftbf if bf_x(1) < 0 @@ -865,12 +866,4 @@ %% 18 User output fprintf(' done. \n'); -sts = 1; -switch nargout - case 1 - varargout{1} = glm; - case 2 - varargout{1} = sts; - varargout{2} = glm; -end -return +return \ No newline at end of file From 1bde97059b0f0a7d013e0972cc7790c9caf94ec8 Mon Sep 17 00:00:00 2001 From: "teddy.chao" Date: Mon, 19 Jun 2023 12:39:05 +0100 Subject: [PATCH 02/26] update glm --- src/pspm_glm.m | 23 ++++++++++++----------- src/pspm_options.m | 2 +- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/pspm_glm.m b/src/pspm_glm.m index d04820685..25affce3b 100644 --- a/src/pspm_glm.m +++ b/src/pspm_glm.m @@ -260,9 +260,6 @@ warning('ID:invalid_input', 'Could not load the specified markerchannel'); return end - % if length(data) > 1 - % data = data{end}; - % end switch class(options.marker_chan_num_event) case 'double' if options.marker_chan_num_event>length(data) @@ -274,14 +271,18 @@ data{options.marker_chan_num_event}.header.sr; end case 'char' - if strcmp(options.marker_chan_num_event, 'first') - events{iFile} = data{1}.data(:) * data{1}.header.sr; - elseif strcmp(options.marker_chan_num_event, 'last') - events{iFile} = data{end}.data(:) * data{end}.header.sr; + if length(data) == 1 + events{iFile} = data.data(:) * data{1}.header.sr; else - warning('ID:invalid_input', ... - 'options.marker_chan_num_event can only specify first or last channel as a char.'); - return + if strcmp(options.marker_chan_num_event, 'first') + events{iFile} = data{1}.data(:) * data{1}.header.sr; + elseif strcmp(options.marker_chan_num_event, 'last') + events{iFile} = data{end}.data(:) * data{end}.header.sr; + else + warning('ID:invalid_input', ... + 'options.marker_chan_num_event can only specify first or last channel as a char.'); + return + end end end if strcmp(model.timeunits,'markervalues') @@ -866,4 +867,4 @@ %% 18 User output fprintf(' done. \n'); -return \ No newline at end of file +return diff --git a/src/pspm_options.m b/src/pspm_options.m index 09ee2086d..41f4510de 100644 --- a/src/pspm_options.m +++ b/src/pspm_options.m @@ -271,7 +271,7 @@ options = autofill(options, 'norm', 0, 1 ); options = autofill(options, 'overwrite', 0, 1 ); options = autofill(options, 'marker_chan_num', 'marker', '*Num*Char' ); - options = autofill(options, 'marker_chan_num_event', 'first', '*Num*Char' ); + options = autofill(options, 'marker_chan_num_event', 'last', '*Num*Char' ); if ~isfield(options, 'exclude_missing') options.exclude_missing = struct('segment_length',-1,'cutoff',0); else From 1ef68b23eed693bac8911f89104b7e5a1535f8ba Mon Sep 17 00:00:00 2001 From: "teddy.chao" Date: Mon, 19 Jun 2023 12:50:17 +0100 Subject: [PATCH 03/26] update sf --- src/pspm_options.m | 1 + src/pspm_sf.m | 26 +++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/pspm_options.m b/src/pspm_options.m index 41f4510de..0ea56e2eb 100644 --- a/src/pspm_options.m +++ b/src/pspm_options.m @@ -411,6 +411,7 @@ options = autofill(options,'dispwin', 1, 0 ); options = autofill(options,'fresp', 0.5, '>=', 0 ); options = autofill(options,'marker_chan_num', 1, '*Int*Char' ); + options = autofill(options,'marker_chan_num_event', 'last', '*Int*Char' ); options = autofill(options,'overwrite', 1, 0 ); options = autofill(options,'threshold', 0.1, '>', 0 ); options = autofill(options,'theta', [0.923581, ... diff --git a/src/pspm_sf.m b/src/pspm_sf.m index 93fdaadd5..1bbfb72d8 100644 --- a/src/pspm_sf.m +++ b/src/pspm_sf.m @@ -225,7 +225,31 @@ return; end end - events = data{1}.data; + switch class(options.marker_chan_num_event) + case 'double' + if options.marker_chan_num_event>length(data) + warning('ID:invalid_input', ... + 'options.marker_chan_num_event exceeds the length of data'); + return + else + events = data{options.marker_chan_num_event}.data(:); + end + case 'char' + if length(data) == 1 + events{iFile} = data.data(:); + else + if strcmp(options.marker_chan_num_event, 'first') + events{iFile} = data{1}.data(:); + elseif strcmp(options.marker_chan_num_event, 'last') + events{iFile} = data{end}.data(:); + else + warning('ID:invalid_input', ... + 'options.marker_chan_num_event can only specify first or last channel as a char.'); + return + end + end + end + end end for iEpoch = 1:size(epochs{iFile}, 1) if iEpoch > 1, fprintf('\n\t\t\t'); end From f9d14769e45eea8b589bb9cb9dc5b5a15e392480 Mon Sep 17 00:00:00 2001 From: "teddy.chao" Date: Mon, 19 Jun 2023 13:06:15 +0100 Subject: [PATCH 04/26] update glm --- src/pspm_glm.m | 28 ++++++++++++---------------- src/pspm_sf.m | 27 +++++++++++---------------- 2 files changed, 23 insertions(+), 32 deletions(-) diff --git a/src/pspm_glm.m b/src/pspm_glm.m index 25affce3b..a2b0e4988 100644 --- a/src/pspm_glm.m +++ b/src/pspm_glm.m @@ -271,23 +271,19 @@ data{options.marker_chan_num_event}.header.sr; end case 'char' - if length(data) == 1 - events{iFile} = data.data(:) * data{1}.header.sr; + if strcmp(options.marker_chan_num_event, 'first') + events{iFile} = data{1}.data(:) * data{1}.header.sr; + elseif strcmp(options.marker_chan_num_event, 'last') + events{iFile} = data{end}.data(:) * data{end}.header.sr; else - if strcmp(options.marker_chan_num_event, 'first') - events{iFile} = data{1}.data(:) * data{1}.header.sr; - elseif strcmp(options.marker_chan_num_event, 'last') - events{iFile} = data{end}.data(:) * data{end}.header.sr; - else - warning('ID:invalid_input', ... - 'options.marker_chan_num_event can only specify first or last channel as a char.'); - return - end + warning('ID:invalid_input', ... + 'options.marker_chan_num_event can only specify first or last channel as a char.'); + return end end - if strcmp(model.timeunits,'markervalues') - model.timing{iFile}.markerinfo = data{end}.markerinfo; - end + end + if strcmp(model.timeunits,'markervalues') + model.timing{iFile}.markerinfo = data{end}.markerinfo; end end if nFile > 1 && any(diff(sr) > 0) @@ -305,7 +301,7 @@ isnumeric(model.filter.down) && isnan(model.filter.down) model.filter.down = min(sr); else -% 4.2 check value of model.filter.down -- + % 4.2 check value of model.filter.down -- if ~isfield(model.filter, 'down') || ~isnumeric(model.filter.down) % tested because the field is used before the call of % pspm_prepdata (everything else is tested there) @@ -699,7 +695,7 @@ tmp.col = {}; end end -% 14.3 mean centering -- + % 14.3 mean centering -- if model.centering for iXCol=1:size(tmp.XC{iCond},2) tmp.XC{iCond}(:,iXCol) = tmp.XC{iCond}(:,iXCol) - mean(tmp.XC{iCond}(:,iXCol)); diff --git a/src/pspm_sf.m b/src/pspm_sf.m index 1bbfb72d8..dbe67fbc9 100644 --- a/src/pspm_sf.m +++ b/src/pspm_sf.m @@ -227,28 +227,23 @@ end switch class(options.marker_chan_num_event) case 'double' - if options.marker_chan_num_event>length(data) + if options.marker_chan_num_event > length(data) warning('ID:invalid_input', ... 'options.marker_chan_num_event exceeds the length of data'); return else - events = data{options.marker_chan_num_event}.data(:); + event = data{options.marker_chan_num_event}.data(:); end case 'char' - if length(data) == 1 - events{iFile} = data.data(:); + if strcmp(options.marker_chan_num_event, 'first') + event = data{1}.data(:); + elseif strcmp(options.marker_chan_num_event, 'last') + event = data{end}.data(:); else - if strcmp(options.marker_chan_num_event, 'first') - events{iFile} = data{1}.data(:); - elseif strcmp(options.marker_chan_num_event, 'last') - events{iFile} = data{end}.data(:); - else - warning('ID:invalid_input', ... - 'options.marker_chan_num_event can only specify first or last channel as a char.'); - return - end + warning('ID:invalid_input', ... + 'options.marker_chan_num_event can only specify first or last channel as a char.'); + return end - end end end for iEpoch = 1:size(epochs{iFile}, 1) @@ -262,7 +257,7 @@ case 'samples' win = round(epochs{iFile}(iEpoch, :) * sr(datatype(k)) / sr(1)); case 'markers' - win = round(events(epochs{1}(iEpoch, :)) * sr(datatype(k))); + win = round(event(epochs{1}(iEpoch, :)) * sr(datatype(k))); case 'whole' win = [1 numel(Y{datatype(k)})]; end @@ -299,7 +294,7 @@ sf.infos.file = model.modelfile{iFile}; sf.modelfile = model.modelfile{iFile}; sf.data = Y; - if exist('events','var'), sf.events = events; end + if exist('event','var'), sf.events = event; end sf.input = model; sf.options = options; sf.modeltype = 'sf'; From 8cd0cba3379083e8878a21d8d6717c27ab781a1d Mon Sep 17 00:00:00 2001 From: "teddy.chao" Date: Mon, 19 Jun 2023 13:31:59 +0100 Subject: [PATCH 05/26] Update pspm_glm.m --- src/pspm_glm.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pspm_glm.m b/src/pspm_glm.m index a2b0e4988..c718d974e 100644 --- a/src/pspm_glm.m +++ b/src/pspm_glm.m @@ -281,9 +281,9 @@ return end end - end - if strcmp(model.timeunits,'markervalues') - model.timing{iFile}.markerinfo = data{end}.markerinfo; + if strcmp(model.timeunits,'markervalues') + model.timing{iFile}.markerinfo = data{end}.markerinfo; + end end end if nFile > 1 && any(diff(sr) > 0) From 6e72bae373db589bd6d221209cc9689dcab783b5 Mon Sep 17 00:00:00 2001 From: "teddy.chao" Date: Mon, 19 Jun 2023 15:12:12 +0100 Subject: [PATCH 06/26] update sf --- src/pspm_glm.m | 14 +++++++++----- src/pspm_sf.m | 40 +++++++++++++++++++++------------------- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/pspm_glm.m b/src/pspm_glm.m index c718d974e..94464b2e5 100644 --- a/src/pspm_glm.m +++ b/src/pspm_glm.m @@ -244,19 +244,23 @@ end %% 3 Check & get data -fprintf('Getting data ...'); nFile = numel(model.datafile); for iFile = 1:nFile - [sts, ~, data] = pspm_load_data(model.datafile{iFile}, model.channel); - if sts < 1 + % 3.1 User output + fprintf('GLM analysis: %s ...', model.datafile{iFile}); + % 3.2 Check whether model file exists + if ~pspm_overwrite(model.modelfile, options) return end + % 3.3 get and filter data + [sts_load_data, ~, data] = pspm_load_data(model.datafile{iFile}, model.channel); + if sts_load_data == -1, return; end y{iFile} = data{end}.data(:); sr(iFile) = data{end}.header.sr; fprintf('.'); if any(strcmp(model.timeunits, {'marker', 'markers','markervalues'})) - [sts, ~, data] = pspm_load_data(model.datafile{iFile}, options.marker_chan_num); - if sts < 1 + [sts_load_data, ~, data] = pspm_load_data(model.datafile{iFile}, options.marker_chan_num); + if sts_load_data == -1 warning('ID:invalid_input', 'Could not load the specified markerchannel'); return end diff --git a/src/pspm_sf.m b/src/pspm_sf.m index dbe67fbc9..e43324cca 100644 --- a/src/pspm_sf.m +++ b/src/pspm_sf.m @@ -9,7 +9,7 @@ % ├───────.datafile: one data filename or cell array of filenames. % ├──────.modelfile: one data filename or cell array of filenames. % ├─────────.timing: can be one of the following -% │ - an SPM style onset file with two event types: onset & +% │ - an SPM style onset file with two events types: onset & % │ offset (names are ignored) % │ - a .mat file with a variable 'epochs', see below % │ - a two-column text file with on/offsets @@ -175,7 +175,8 @@ return end %% 3 Get data -for iFile = 1:numel(model.datafile) +nFile = numel(model.datafile); +for iFile = 1:nFile % 3.1 User output fprintf('SF analysis: %s ...', model.datafile{iFile}); % 3.2 Check whether model file exists @@ -184,14 +185,15 @@ end % 3.3 get and filter data [sts_load_data, ~, data] = pspm_load_data(model.datafile{iFile}, model.channel); - if sts_load_data < 0, return; end - Y{1} = data{1}.data; sr(1) = data{1}.header.sr; + if sts_load_data < 1, return; end model.filter.sr = sr(1); - [sts_prepdata, Y{2}, sr(2)] = pspm_prepdata(data{1}.data, model.filter); + [sts_prepdata, y{2}, sr(2)] = pspm_prepdata(data{1}.data, model.filter); if sts_prepdata == -1 warning('ID:invalid_input', 'Call of pspm_prepdata failed.'); return; end + y{iFile} = data{end}.data(:); + sr(iFile) = data{end}.header.sr; % 3.4 Check data units if ~strcmpi(data{1}.header.units, 'uS') && any(strcmpi('dcm', method)) fprintf(['\nYour data units are stored as %s, ',... @@ -202,8 +204,8 @@ % 3.5 Get marker data if any(strcmp(model.timeunits, {'marker', 'markers'})) if options.marker_chan_num - [nsts, ~, ndata] = pspm_load_data(model.datafile, options.marker_chan_num); - if nsts == -1 + [sts, ~, ndata] = pspm_load_data(model.datafile, options.marker_chan_num); + if sts < 1 warning('ID:invalid_input', 'Could not load data'); return; end @@ -212,8 +214,8 @@ ['Channel %i is no marker channel. ',... 'The first marker channel in the file is used instead'],... options.marker_chan_num); - [nsts, ~, ~] = pspm_load_data(model.datafile, 'marker'); - if nsts == -1 + [sts_load_data, ~, ~] = pspm_load_data(model.datafile, 'marker'); + if sts_load_data == -1 warning('ID:invalid_input', 'Could not load data'); return; end @@ -232,13 +234,13 @@ 'options.marker_chan_num_event exceeds the length of data'); return else - event = data{options.marker_chan_num_event}.data(:); + events{iFile} = data{options.marker_chan_num_event}.data(:); end case 'char' if strcmp(options.marker_chan_num_event, 'first') - event = data{1}.data(:); + events{iFile} = data{1}.data(:); elseif strcmp(options.marker_chan_num_event, 'last') - event = data{end}.data(:); + events{iFile} = data{end}.data(:); else warning('ID:invalid_input', ... 'options.marker_chan_num_event can only specify first or last channel as a char.'); @@ -257,16 +259,16 @@ case 'samples' win = round(epochs{iFile}(iEpoch, :) * sr(datatype(k)) / sr(1)); case 'markers' - win = round(event(epochs{1}(iEpoch, :)) * sr(datatype(k))); + win = round(events(epochs{1}(iEpoch, :)) * sr(datatype(k))); case 'whole' - win = [1 numel(Y{datatype(k)})]; + win = [1 numel(y{datatype(k)})]; end - if any(win > numel(Y{datatype(k)}) + 1) || any(win < 0) + if any(win > numel(y{datatype(k)}) + 1) || any(win < 0) warning('\nEpoch %2.0f outside of file %s ...', iEpoch, model.modelfile{iFile}); else % correct issues with using 'round' win(1) = max(win(1), 1); - win(2) = min(win(2), numel(Y{datatype(k)})); + win(2) = min(win(2), numel(y{datatype(k)})); end % 3.6 collect information sf.model{k}(iEpoch).modeltype = method{k}; @@ -275,7 +277,7 @@ sf.model{k}(iEpoch).samples = win; sf.model{k}(iEpoch).sr = sr(datatype(k)); % - escr = Y{datatype(k)}(win(1):win(end)); + escr = y{datatype(k)}(win(1):win(end)); sf.model{k}(iEpoch).data = escr; % 3.7 do the analysis and collect results invrs = fhandle{k}(escr, sr(datatype(k)), options); @@ -293,8 +295,8 @@ sf.infos.date = date; sf.infos.file = model.modelfile{iFile}; sf.modelfile = model.modelfile{iFile}; - sf.data = Y; - if exist('event','var'), sf.events = event; end + sf.data = y; + if exist('events','var'), sf.events = events; end sf.input = model; sf.options = options; sf.modeltype = 'sf'; From 879ef029ed8db3e436731b0cf04fe29455384ff6 Mon Sep 17 00:00:00 2001 From: "teddy.chao" Date: Mon, 19 Jun 2023 15:49:02 +0100 Subject: [PATCH 07/26] update glm and sf --- src/pspm_glm.m | 6 +++--- src/pspm_sf.m | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pspm_glm.m b/src/pspm_glm.m index 94464b2e5..c068c13b8 100644 --- a/src/pspm_glm.m +++ b/src/pspm_glm.m @@ -249,9 +249,9 @@ % 3.1 User output fprintf('GLM analysis: %s ...', model.datafile{iFile}); % 3.2 Check whether model file exists - if ~pspm_overwrite(model.modelfile, options) - return - end + % if ~pspm_overwrite(model.modelfile, options) + % return + % end % 3.3 get and filter data [sts_load_data, ~, data] = pspm_load_data(model.datafile{iFile}, model.channel); if sts_load_data == -1, return; end diff --git a/src/pspm_sf.m b/src/pspm_sf.m index e43324cca..486a2a04b 100644 --- a/src/pspm_sf.m +++ b/src/pspm_sf.m @@ -186,7 +186,9 @@ % 3.3 get and filter data [sts_load_data, ~, data] = pspm_load_data(model.datafile{iFile}, model.channel); if sts_load_data < 1, return; end - model.filter.sr = sr(1); + y{1} = data{1}.data; + sr(1) = data{1}.header.sr; + model.filter.sr = data{end}.header.sr; [sts_prepdata, y{2}, sr(2)] = pspm_prepdata(data{1}.data, model.filter); if sts_prepdata == -1 warning('ID:invalid_input', 'Call of pspm_prepdata failed.'); From 5e9f40d4d8296467684ee51a2f97939e1f81f3b8 Mon Sep 17 00:00:00 2001 From: "teddy.chao" Date: Mon, 26 Jun 2023 14:43:09 +0100 Subject: [PATCH 08/26] Allow marker_chan_num_event to be 1 as default --- src/pspm_glm.m | 7 +------ src/pspm_options.m | 4 ++-- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/pspm_glm.m b/src/pspm_glm.m index c068c13b8..492b21d86 100644 --- a/src/pspm_glm.m +++ b/src/pspm_glm.m @@ -229,6 +229,7 @@ end % 2.9 check options -- options = pspm_options(options, 'glm'); +options.overwrite = pspm_overwrite(model.modelfile, options.overwrite); if options.invalid return end @@ -246,12 +247,6 @@ %% 3 Check & get data nFile = numel(model.datafile); for iFile = 1:nFile - % 3.1 User output - fprintf('GLM analysis: %s ...', model.datafile{iFile}); - % 3.2 Check whether model file exists - % if ~pspm_overwrite(model.modelfile, options) - % return - % end % 3.3 get and filter data [sts_load_data, ~, data] = pspm_load_data(model.datafile{iFile}, model.channel); if sts_load_data == -1, return; end diff --git a/src/pspm_options.m b/src/pspm_options.m index 0ea56e2eb..474b51ca8 100644 --- a/src/pspm_options.m +++ b/src/pspm_options.m @@ -271,7 +271,7 @@ options = autofill(options, 'norm', 0, 1 ); options = autofill(options, 'overwrite', 0, 1 ); options = autofill(options, 'marker_chan_num', 'marker', '*Num*Char' ); - options = autofill(options, 'marker_chan_num_event', 'last', '*Num*Char' ); + options = autofill(options, 'marker_chan_num_event', 1, '*Int' ); if ~isfield(options, 'exclude_missing') options.exclude_missing = struct('segment_length',-1,'cutoff',0); else @@ -411,7 +411,7 @@ options = autofill(options,'dispwin', 1, 0 ); options = autofill(options,'fresp', 0.5, '>=', 0 ); options = autofill(options,'marker_chan_num', 1, '*Int*Char' ); - options = autofill(options,'marker_chan_num_event', 'last', '*Int*Char' ); + options = autofill(options,'marker_chan_num_event', 1, '*Int' ); options = autofill(options,'overwrite', 1, 0 ); options = autofill(options,'threshold', 0.1, '>', 0 ); options = autofill(options,'theta', [0.923581, ... From 54ff9c88f49ac7f6200ceb3171bb885f9297576e Mon Sep 17 00:00:00 2001 From: "teddy.chao" Date: Mon, 26 Jun 2023 15:02:24 +0100 Subject: [PATCH 09/26] fix model.datafile data session --- src/pspm_sf.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pspm_sf.m b/src/pspm_sf.m index 486a2a04b..8bf7fecf4 100644 --- a/src/pspm_sf.m +++ b/src/pspm_sf.m @@ -206,7 +206,7 @@ % 3.5 Get marker data if any(strcmp(model.timeunits, {'marker', 'markers'})) if options.marker_chan_num - [sts, ~, ndata] = pspm_load_data(model.datafile, options.marker_chan_num); + [sts, ~, ndata] = pspm_load_data(model.datafile{iFile}, options.marker_chan_num); if sts < 1 warning('ID:invalid_input', 'Could not load data'); return; @@ -216,14 +216,14 @@ ['Channel %i is no marker channel. ',... 'The first marker channel in the file is used instead'],... options.marker_chan_num); - [sts_load_data, ~, ~] = pspm_load_data(model.datafile, 'marker'); + [sts_load_data, ~, ~] = pspm_load_data(model.datafile{iFile}, 'marker'); if sts_load_data == -1 warning('ID:invalid_input', 'Could not load data'); return; end end else - [nsts, ~, ~] = pspm_load_data(model.datafile, 'marker'); + [nsts, ~, ~] = pspm_load_data(model.datafile{iFile}, 'marker'); if nsts == -1 warning('ID:invalid_input', 'Could not load data'); return; From ef5cdaf5e43bd935a512e84eae8499e5bdd89584 Mon Sep 17 00:00:00 2001 From: "teddy.chao" Date: Mon, 26 Jun 2023 15:18:50 +0100 Subject: [PATCH 10/26] Update pspm_sf.m --- src/pspm_sf.m | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/pspm_sf.m b/src/pspm_sf.m index 8bf7fecf4..d6e159621 100644 --- a/src/pspm_sf.m +++ b/src/pspm_sf.m @@ -186,16 +186,14 @@ % 3.3 get and filter data [sts_load_data, ~, data] = pspm_load_data(model.datafile{iFile}, model.channel); if sts_load_data < 1, return; end - y{1} = data{1}.data; - sr(1) = data{1}.header.sr; model.filter.sr = data{end}.header.sr; [sts_prepdata, y{2}, sr(2)] = pspm_prepdata(data{1}.data, model.filter); if sts_prepdata == -1 warning('ID:invalid_input', 'Call of pspm_prepdata failed.'); return; end - y{iFile} = data{end}.data(:); - sr(iFile) = data{end}.header.sr; + y{iFile} = data{1}.data(:); + sr(iFile) = data{1}.header.sr; % 3.4 Check data units if ~strcmpi(data{1}.header.units, 'uS') && any(strcmpi('dcm', method)) fprintf(['\nYour data units are stored as %s, ',... From d98d9b7b5648394f704aa51ebc3ee2dd11d6f5ba Mon Sep 17 00:00:00 2001 From: "teddy.chao" Date: Mon, 26 Jun 2023 15:23:20 +0100 Subject: [PATCH 11/26] Update pspm_sf.m --- src/pspm_sf.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pspm_sf.m b/src/pspm_sf.m index d6e159621..a47c496ec 100644 --- a/src/pspm_sf.m +++ b/src/pspm_sf.m @@ -192,14 +192,14 @@ warning('ID:invalid_input', 'Call of pspm_prepdata failed.'); return; end - y{iFile} = data{1}.data(:); - sr(iFile) = data{1}.header.sr; + y{iFile} = data{end}.data(:); % always use last data channels + sr(iFile) = data{end}.header.sr; % 3.4 Check data units - if ~strcmpi(data{1}.header.units, 'uS') && any(strcmpi('dcm', method)) + if ~strcmpi(data{end}.header.units, 'uS') && any(strcmpi('dcm', method)) fprintf(['\nYour data units are stored as %s, ',... 'and the method will apply an amplitude threshold in uS. ',... 'Please check your results.\n'], ... - data{1}.header.units); + data{end}.header.units); end % 3.5 Get marker data if any(strcmp(model.timeunits, {'marker', 'markers'})) From 8bcf17e571afd6adff3c95be339ab793982a064c Mon Sep 17 00:00:00 2001 From: "teddy.chao" Date: Mon, 26 Jun 2023 15:44:33 +0100 Subject: [PATCH 12/26] update marker_chan_num_event --- src/pspm_glm.m | 27 +++++++-------------------- src/pspm_sf.m | 25 ++++++------------------- 2 files changed, 13 insertions(+), 39 deletions(-) diff --git a/src/pspm_glm.m b/src/pspm_glm.m index 492b21d86..556aebfa1 100644 --- a/src/pspm_glm.m +++ b/src/pspm_glm.m @@ -259,26 +259,13 @@ warning('ID:invalid_input', 'Could not load the specified markerchannel'); return end - switch class(options.marker_chan_num_event) - case 'double' - if options.marker_chan_num_event>length(data) - warning('ID:invalid_input', ... - 'options.marker_chan_num_event exceeds the length of data'); - return - else - events{iFile} = data{options.marker_chan_num_event}.data(:) * ... - data{options.marker_chan_num_event}.header.sr; - end - case 'char' - if strcmp(options.marker_chan_num_event, 'first') - events{iFile} = data{1}.data(:) * data{1}.header.sr; - elseif strcmp(options.marker_chan_num_event, 'last') - events{iFile} = data{end}.data(:) * data{end}.header.sr; - else - warning('ID:invalid_input', ... - 'options.marker_chan_num_event can only specify first or last channel as a char.'); - return - end + if options.marker_chan_num_event>length(data) + warning('ID:invalid_input', ... + 'options.marker_chan_num_event exceeds the length of data'); + return + else + events{iFile} = data{options.marker_chan_num_event}.data(:) * ... + data{options.marker_chan_num_event}.header.sr; end if strcmp(model.timeunits,'markervalues') model.timing{iFile}.markerinfo = data{end}.markerinfo; diff --git a/src/pspm_sf.m b/src/pspm_sf.m index a47c496ec..427cc5fce 100644 --- a/src/pspm_sf.m +++ b/src/pspm_sf.m @@ -227,25 +227,12 @@ return; end end - switch class(options.marker_chan_num_event) - case 'double' - if options.marker_chan_num_event > length(data) - warning('ID:invalid_input', ... - 'options.marker_chan_num_event exceeds the length of data'); - return - else - events{iFile} = data{options.marker_chan_num_event}.data(:); - end - case 'char' - if strcmp(options.marker_chan_num_event, 'first') - events{iFile} = data{1}.data(:); - elseif strcmp(options.marker_chan_num_event, 'last') - events{iFile} = data{end}.data(:); - else - warning('ID:invalid_input', ... - 'options.marker_chan_num_event can only specify first or last channel as a char.'); - return - end + if options.marker_chan_num_event > length(data) + warning('ID:invalid_input', ... + 'options.marker_chan_num_event exceeds the length of data'); + return + else + events{iFile} = data{options.marker_chan_num_event}.data(:); end end for iEpoch = 1:size(epochs{iFile}, 1) From 7f1e3512679a0beb54b0e0fa0ec956071d7c2680 Mon Sep 17 00:00:00 2001 From: "teddy.chao" Date: Mon, 26 Jun 2023 16:03:56 +0100 Subject: [PATCH 13/26] Update pspm_glm.m --- src/pspm_glm.m | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pspm_glm.m b/src/pspm_glm.m index 556aebfa1..9901e8281 100644 --- a/src/pspm_glm.m +++ b/src/pspm_glm.m @@ -245,6 +245,7 @@ end %% 3 Check & get data +fprintf('Getting data ...'); nFile = numel(model.datafile); for iFile = 1:nFile % 3.3 get and filter data From 4a37d1e4455aec3d15f832b3bca07ba431393d10 Mon Sep 17 00:00:00 2001 From: "teddy.chao" Date: Mon, 24 Jul 2023 14:58:18 +0100 Subject: [PATCH 14/26] Update pspm_sf.m --- src/pspm_sf.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pspm_sf.m b/src/pspm_sf.m index 142cb9beb..dbe0615b1 100644 --- a/src/pspm_sf.m +++ b/src/pspm_sf.m @@ -166,8 +166,8 @@ if strcmpi(model.timeunits, 'whole') epochs = repmat({[1 1]}, numel(model.datafile), 1); else - for iSn = 1:numel(model.datafile) - [sts_get_timing, epochs{iSn}] = pspm_get_timing('epochs', model.timing{iSn}, model.timeunits); + for iFile = 1:numel(model.datafile) + [sts_get_timing, epochs{iFile}] = pspm_get_timing('epochs', model.timing{iFile}, model.timeunits); if sts_get_timing == -1 warning('ID:invalid_input', 'Call of pspm_get_timing failed.'); return; @@ -230,7 +230,7 @@ [~, sortindx] = sort(missing{iFile}(:, 1)); missing{iFile} = missing{iFile}(sortindx,:); % check for overlap and merge - for k = 2:size(missing{iSn}, 1) + for k = 2:size(missing{iFile}, 1) if missing{iFile}(k, 1) <= missing{iFile}(k - 1, 2) missing{iFile}(k, 1) = missing{iFile}(k - 1, 1); missing{iFile}(k - 1, :) = []; From 50ab555f04c5d7dd808dae4fa4c8ebf48389a29d Mon Sep 17 00:00:00 2001 From: "teddy.chao" Date: Mon, 7 Aug 2023 11:14:31 +0100 Subject: [PATCH 15/26] remove marker_chan_num_event for sf --- src/pspm_options.m | 1 - src/pspm_sf.m | 8 +------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/pspm_options.m b/src/pspm_options.m index 474b51ca8..da3cf95c2 100644 --- a/src/pspm_options.m +++ b/src/pspm_options.m @@ -411,7 +411,6 @@ options = autofill(options,'dispwin', 1, 0 ); options = autofill(options,'fresp', 0.5, '>=', 0 ); options = autofill(options,'marker_chan_num', 1, '*Int*Char' ); - options = autofill(options,'marker_chan_num_event', 1, '*Int' ); options = autofill(options,'overwrite', 1, 0 ); options = autofill(options,'threshold', 0.1, '>', 0 ); options = autofill(options,'theta', [0.923581, ... diff --git a/src/pspm_sf.m b/src/pspm_sf.m index 427cc5fce..b940b1ad8 100644 --- a/src/pspm_sf.m +++ b/src/pspm_sf.m @@ -227,13 +227,7 @@ return; end end - if options.marker_chan_num_event > length(data) - warning('ID:invalid_input', ... - 'options.marker_chan_num_event exceeds the length of data'); - return - else - events{iFile} = data{options.marker_chan_num_event}.data(:); - end + events{iFile} = data{1}.data(:); end for iEpoch = 1:size(epochs{iFile}, 1) if iEpoch > 1, fprintf('\n\t\t\t'); end From 7d3faa146c105d1ddde674479fc8a4ff92893a9f Mon Sep 17 00:00:00 2001 From: "teddy.chao" Date: Mon, 7 Aug 2023 11:17:31 +0100 Subject: [PATCH 16/26] update pspm_glm remove the option field marker_chan_num_event for glm --- src/pspm_glm.m | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/pspm_glm.m b/src/pspm_glm.m index 9901e8281..dafcdcde2 100644 --- a/src/pspm_glm.m +++ b/src/pspm_glm.m @@ -260,14 +260,8 @@ warning('ID:invalid_input', 'Could not load the specified markerchannel'); return end - if options.marker_chan_num_event>length(data) - warning('ID:invalid_input', ... - 'options.marker_chan_num_event exceeds the length of data'); - return - else - events{iFile} = data{options.marker_chan_num_event}.data(:) * ... - data{options.marker_chan_num_event}.header.sr; - end + % marker_chan_num_event is removed and the first marker_channel is now used + events{iFile} = data{1}.data(:) * data{1}.header.sr; if strcmp(model.timeunits,'markervalues') model.timing{iFile}.markerinfo = data{end}.markerinfo; end From e70e2bbb4b4c9f78e4a2510733242d2d119003dc Mon Sep 17 00:00:00 2001 From: "teddy.chao" Date: Mon, 7 Aug 2023 13:13:08 +0100 Subject: [PATCH 17/26] update pspm_dcm pspm_dcm to use the last data channel --- src/pspm_dcm.m | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/pspm_dcm.m b/src/pspm_dcm.m index 732368585..54fc7c4e7 100644 --- a/src/pspm_dcm.m +++ b/src/pspm_dcm.m @@ -356,12 +356,16 @@ else missing{iSn} = []; end - model.filter.sr = data{iSn}{1}.header.sr; + y{iSn} = data{iSn}{end}.data; + % to use the last channel in data channels + % this is consistent to sf and glm + sr{iSn} = data{iSn}{end}.header.sr; + model.filter.sr = sr; % try to find missing epochs according to subsession threshold - n_data = size(data{iSn}{1}.data,1); + n_data = size(y{iSn},1); if isempty(missing{iSn}) - nan_epochs = isnan(data{iSn}{1}.data); + nan_epochs = isnan(y{iSn}); d_nan_ep = transpose(diff(nan_epochs)); nan_ep_start = find(d_nan_ep == 1); @@ -388,19 +392,19 @@ % classify if epoch should be considered % true for duration > substhresh and for missing epochs - ignore_epochs = diff(miss_epochs, 1, 2)/data{iSn}{1}.header.sr > ... + ignore_epochs = diff(miss_epochs, 1, 2)/sr{iSn} > ... model.substhresh; else % use missing epochs as specified by file - miss_epochs = pspm_time2index(missing{iSn},data{iSn}{1}.header.sr); + miss_epochs = pspm_time2index(missing{iSn}, sr{iSn}); ignore_epochs = diff(missing{iSn}, 1, 2) > model.substhresh; % and set data to NaN to enable later detection of `short` missing % epochs for k = 1:size(miss_epochs, 1) flanks = round(miss_epochs(k,:)); - data{iSn}{1}.data(flanks(1):flanks(2)) = NaN; + y{iSn}(flanks(1):flanks(2)) = NaN; end end @@ -436,17 +440,17 @@ n_sbs = numel(se_start); % enabled subsessions subsessions(end+(1:n_sbs), 1:4) = [ones(n_sbs,1)*iSn, ... - [se_start, se_stop]/data{iSn}{1}.header.sr, ... + [se_start, se_stop]/sr{iSn}, ... zeros(n_sbs,1)]; % missing epochs n_miss = sum(ignore_epochs); subsessions(end+(1:n_miss), 1:4) = [ones(n_miss,1)*iSn, ... - miss_epochs(i_e,:)/data{iSn}{1}.header.sr, ... + miss_epochs(i_e,:)/sr{iSn}, ... ones(n_miss,1)]; else subsessions(end+1,1:4) = [iSn, ... - [1, numel(data{iSn}{1}.data)]/data{iSn}{1}.header.sr, 0]; + [1, numel(y{iSn})]/sr{iSn}, 0]; end From 2dcd038f4798e6ba4c79feb978b12471dbf7cfbb Mon Sep 17 00:00:00 2001 From: "teddy.chao" Date: Mon, 7 Aug 2023 13:42:54 +0100 Subject: [PATCH 18/26] Update pspm_dcm.m --- src/pspm_dcm.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pspm_dcm.m b/src/pspm_dcm.m index 54fc7c4e7..56f4920ef 100644 --- a/src/pspm_dcm.m +++ b/src/pspm_dcm.m @@ -360,7 +360,7 @@ % to use the last channel in data channels % this is consistent to sf and glm sr{iSn} = data{iSn}{end}.header.sr; - model.filter.sr = sr; + model.filter.sr = sr{iSn}; % try to find missing epochs according to subsession threshold n_data = size(y{iSn},1); From 43757687760212b6f37cea23a155b2529452dcaa Mon Sep 17 00:00:00 2001 From: "teddy.chao" Date: Mon, 7 Aug 2023 15:23:44 +0100 Subject: [PATCH 19/26] Update pspm_options.m --- src/pspm_options.m | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pspm_options.m b/src/pspm_options.m index cca5eea9c..df119262c 100644 --- a/src/pspm_options.m +++ b/src/pspm_options.m @@ -271,7 +271,6 @@ options = autofill(options, 'norm', 0, 1 ); options = autofill(options, 'overwrite', 0, 1 ); options = autofill(options, 'marker_chan_num', 'marker', '*Num*Char' ); - options = autofill(options, 'marker_chan_num_event', 1, '*Int' ); if ~isfield(options, 'exclude_missing') options.exclude_missing = struct('segment_length',-1,'cutoff',0); else From f37c287caf5e367537d528fcd35912c9d70ed5ce Mon Sep 17 00:00:00 2001 From: "teddy.chao" Date: Mon, 7 Aug 2023 15:26:53 +0100 Subject: [PATCH 20/26] Update pspm_sf.m --- src/pspm_sf.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pspm_sf.m b/src/pspm_sf.m index 2ebdb757d..6ef3d7648 100644 --- a/src/pspm_sf.m +++ b/src/pspm_sf.m @@ -9,7 +9,7 @@ % ├───────.datafile: one data filename or cell array of filenames. % ├──────.modelfile: one data filename or cell array of filenames. % ├─────────.timing: can be one of the following -% │ - an SPM style onset file with two events types: onset & +% │ - an SPM style onset file with two event types: onset & % │ offset (names are ignored) % │ - a .mat file with a variable 'epochs', see below % │ - a two-column text file with on/offsets @@ -35,7 +35,7 @@ % ┌─────────options % ├──────.overwrite: [logical] [default: determined by pspm_overwrite] % │ Define whether to overwrite existing output files or not. -% ├.marker_chan_num: [integer] +% ├.marker_chan_num: [integer] % │ marker channel number % │ if undefined or 0, first marker channel is used. % │ * Additional options for individual methods: From c13b609402d857f81944e76326325ab9f3f89617 Mon Sep 17 00:00:00 2001 From: "teddy.chao" Date: Mon, 7 Aug 2023 15:29:39 +0100 Subject: [PATCH 21/26] Update pspm_sf.m --- src/pspm_sf.m | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pspm_sf.m b/src/pspm_sf.m index 6ef3d7648..33d1a964e 100644 --- a/src/pspm_sf.m +++ b/src/pspm_sf.m @@ -209,13 +209,12 @@ [sts_load_data, ~, data] = pspm_load_data(model.datafile{iFile}, model.channel); if sts_load_data < 1, return; end model.filter.sr = data{end}.header.sr; - [sts_prepdata, y{2}, sr(2)] = pspm_prepdata(data{1}.data, model.filter); + [sts_prepdata, y{iFile}, sr{iFile}] = pspm_prepdata(data{end}.data, model.filter); + % always use last data channels if sts_prepdata == -1 warning('ID:invalid_input', 'Call of pspm_prepdata failed.'); return; end - y{iFile} = data{end}.data(:); % always use last data channels - sr(iFile) = data{end}.header.sr; % 3.4 Check data units if ~strcmpi(data{end}.header.units, 'uS') && any(strcmpi('dcm', method)) fprintf(['\nYour data units are stored as %s, ',... From 00ccaafac8e3f0274b3b8d904b6a965c04294070 Mon Sep 17 00:00:00 2001 From: "teddy.chao" Date: Mon, 7 Aug 2023 16:05:46 +0100 Subject: [PATCH 22/26] Update pspm_sf.m --- src/pspm_sf.m | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/pspm_sf.m b/src/pspm_sf.m index 33d1a964e..9d6fba3c4 100644 --- a/src/pspm_sf.m +++ b/src/pspm_sf.m @@ -207,9 +207,11 @@ end % 3.3 get and filter data [sts_load_data, ~, data] = pspm_load_data(model.datafile{iFile}, model.channel); - if sts_load_data < 1, return; end - model.filter.sr = data{end}.header.sr; - [sts_prepdata, y{iFile}, sr{iFile}] = pspm_prepdata(data{end}.data, model.filter); + if sts_load_data < 0, return; end + y{1} = data{end}.data; + sr(1) = data{end}.header.sr; + model.filter.sr = sr(1); + [sts_prepdata, y{2}, sr(2)] = pspm_prepdata(data{end}.data, model.filter); % always use last data channels if sts_prepdata == -1 warning('ID:invalid_input', 'Call of pspm_prepdata failed.'); From 67367b32c37b83cb1497e784619b12acce5771d9 Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Fri, 11 Aug 2023 10:15:48 +0200 Subject: [PATCH 23/26] fixes to marker handling --- src/pspm_cfg/pspm_cfg_glm.m | 2 +- src/pspm_glm.m | 2 +- src/pspm_merge.m | 2 +- src/pspm_options.m | 27 +- src/pspm_sf.m | 382 +++++++++++++------------- src/pspm_trim.m | 525 ++++++++++++++++++------------------ 6 files changed, 470 insertions(+), 470 deletions(-) diff --git a/src/pspm_cfg/pspm_cfg_glm.m b/src/pspm_cfg/pspm_cfg_glm.m index 36ca8012b..b16fc5128 100644 --- a/src/pspm_cfg/pspm_cfg_glm.m +++ b/src/pspm_cfg/pspm_cfg_glm.m @@ -309,7 +309,7 @@ mrk_chan.strtype = 'i'; mrk_chan.val = {0}; mrk_chan.num = [1 1]; -mrk_chan.help = {['Indicate the marker channel. By default the last marker channel is ' ... +mrk_chan.help = {['Indicate the marker channel. By default the first marker channel is ' ... 'assumed to contain the relevant markers.'], ['Markers are only used if you have ' ... 'specified the time units as �markers�.']}; diff --git a/src/pspm_glm.m b/src/pspm_glm.m index dafcdcde2..8603718d0 100644 --- a/src/pspm_glm.m +++ b/src/pspm_glm.m @@ -260,7 +260,7 @@ warning('ID:invalid_input', 'Could not load the specified markerchannel'); return end - % marker_chan_num_event is removed and the first marker_channel is now used + % first marker_channel is used events{iFile} = data{1}.data(:) * data{1}.header.sr; if strcmp(model.timeunits,'markervalues') model.timing{iFile}.markerinfo = data{end}.markerinfo; diff --git a/src/pspm_merge.m b/src/pspm_merge.m index 2ce06fdea..aafbea843 100644 --- a/src/pspm_merge.m +++ b/src/pspm_merge.m @@ -17,7 +17,7 @@ % │ Define whether to overwrite existing output files or not. % │ Default value: determined by pspm_overwrite. % └.marker_chan_num: 2 marker channel numbers - if undefined -% or 0, first marker channel is used +% or 0, first marker channel of each file is used % ● History % Introduced In PsPM 3.0 % Written in 2008-2015 by Dominik R Bach (UZH, WTCN) diff --git a/src/pspm_options.m b/src/pspm_options.m index df119262c..0f18eb92c 100644 --- a/src/pspm_options.m +++ b/src/pspm_options.m @@ -34,6 +34,15 @@ text_optional_channel_invalid_char = ... 'options.channel is not a valid channel type.'; %% 2 Main Processing + +% deal with global settings +if (isfield(options, 'marker_chan_num') && ... + numel(options.marker_chan_num) == 1 && ... + options.marker_chan_num == 0) + options.marker_chan_num = 'marker'; +end + +% deal with function-specific settings switch FunName case 'blink_saccade_filt' % 2.1 pspm_blink_saccade_filt -- @@ -302,10 +311,12 @@ options = autofill(options, 'zscored', 0, 1 ); case 'merge' %% 2.32 pspm_merge - options = autofill(options, 'marker_chan_num', [1,2], '*Num' ); - if ~all(size(options.marker_chan_num(:))==[2,1]) - options.invalid = 1; - return + options = autofill(options, 'marker_chan_num', [0, 0], '*Num*Char' ); % NOTE: the default values will be converted to 'marker' by a subsequent call to pspm_trim + if isnumeric(options.marker_chan_num) + if ~all(size(options.marker_chan_num(:))==[2,1]) + options.invalid = 1; + return + end end options = autofill(options, 'overwrite', 0, 1 ); case 'pfm' @@ -409,7 +420,7 @@ options = autofill(options,'dispsmallwin', 0, 1 ); options = autofill(options,'dispwin', 1, 0 ); options = autofill(options,'fresp', 0.5, '>=', 0 ); - options = autofill(options,'marker_chan_num', 1, '*Int*Char' ); + options = autofill(options,'marker_chan_num', 'marker', '*Int*Char' ); options = autofill(options,'overwrite', 1, 0 ); options = autofill(options,'threshold', 0.1, '>', 0 ); options = autofill(options,'missingthresh', 2, '>', 0 ); @@ -461,7 +472,7 @@ case 'trim' % 2.46 pspm_trim -- options = autofill(options, 'drop_offset_markers', 0, '*Int' ); - options = autofill(options, 'marker_chan_num', 1, '*Int*Char' ); + options = autofill(options, 'marker_chan_num', 'marker', '*Int*Char' ); options = autofill(options, 'overwrite', 0, 1 ); case 'write_channel' % 2.47 pspm_write_channel -- @@ -861,7 +872,7 @@ if options.manual_chosen == 1 || ... (options.manual_chosen == 0 && strcmpi(options.model_strc.modeltype,'glm')) if ~isfield(options, 'marker_chan') - options.marker_chan = repmat({1}, numel(options.data_fn),1); + options.marker_chan = repmat({'marker'}, numel(options.data_fn),1); elseif ~iscell(options.marker_chan) options.marker_chan = repmat({options.marker_chan}, size(options.data_fn)); end @@ -972,8 +983,6 @@ return end end -else - options.marker_chan_num = 'marker'; end if isfield(options,'exclude_missing') if ~(isfield(options.exclude_missing, 'segment_length') && ... diff --git a/src/pspm_sf.m b/src/pspm_sf.m index 9d6fba3c4..e4cd17c0d 100644 --- a/src/pspm_sf.m +++ b/src/pspm_sf.m @@ -72,269 +72,263 @@ %% 1 Initialise global settings if isempty(settings) - pspm_init; + pspm_init; end outfile = []; sts = -1; %% 2 Check input % 2.1 Check missing input if nargin<1 - warning('ID:invalid_input', 'Nothing to do.'); return; + warning('ID:invalid_input', 'Nothing to do.'); return; elseif nargin<2 - options = struct(); + options = struct(); end if ~isfield(model, 'datafile') - warning('ID:invalid_input', 'No input data file specified.'); return; + warning('ID:invalid_input', 'No input data file specified.'); return; elseif ~isfield(model, 'modelfile') - warning('ID:invalid_input', 'No output model file specified.'); return; + warning('ID:invalid_input', 'No output model file specified.'); return; elseif ~isfield(model, 'timeunits') - warning('ID:invalid_input', 'No timeunits specified.'); return; + warning('ID:invalid_input', 'No timeunits specified.'); return; elseif ~isfield(model, 'timing') && ~strcmpi(model.timeunits, 'file') - warning('ID:invalid_input', 'No epochs specified.'); return; + warning('ID:invalid_input', 'No epochs specified.'); return; end % 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; + 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; + 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; + 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'}) - warning('ID:invalid_input',... - 'Timeunits (%s) not recognised; ',... - 'only ''seconds'', ''markers'', ''samples'' and ''whole'' are supported',... - model.timeunits); return; + warning('ID:invalid_input',... + 'Timeunits (%s) not recognised; ',... + 'only ''seconds'', ''markers'', ''samples'' and ''whole'' are supported',... + model.timeunits); return; end % 2.3 Convert single file input to cell -- if ischar(model.datafile) - model.datafile={model.datafile}; + model.datafile={model.datafile}; end if ischar(model.timing) || isnumeric(model.timing) - model.timing = {model.timing}; + model.timing = {model.timing}; end if ischar(model.modelfile) - model.modelfile = {model.modelfile}; + model.modelfile = {model.modelfile}; end % 2.4 Check number of files if ~strcmpi(model.timeunits, 'whole') && numel(model.datafile) ~= numel(model.timing) - warning('ID:number_of_elements_dont_match',... - 'Number of data files and epoch definitions does not match.'); return; + warning('ID:number_of_elements_dont_match',... + 'Number of data files and epoch definitions does not match.'); return; elseif numel(model.datafile) ~= numel(model.modelfile) - warning('ID:number_of_elements_dont_match',... - 'Number of data files and model files does not match.'); return; + warning('ID:number_of_elements_dont_match',... + 'Number of data files and model files does not match.'); return; end % 2.5 check methods if ~isfield(model, 'method') - model.method = {'dcm'}; + model.method = {'dcm'}; elseif ischar(model.method) - model.method={model.method}; + model.method={model.method}; end if ~iscell(model.method) - warning('Method needs to be a char or cell array'); return; + warning('Method needs to be a char or cell array'); return; else - method = cell(numel(model.method), 1); - fhandle = method; - datatype = NaN(numel(model.method)); - for k = 1:numel(model.method) - switch model.method{k} - case {'auc', 'AUC'} - method{k} = 'auc'; - fhandle{k} = @pspm_sf_auc; - datatype(k) = 2; % filtered - case {'DCM', 'dcm'} - method{k} = 'dcm'; - fhandle{k} = @pspm_sf_dcm; - datatype(k) = 2; % filtered - case {'MP', 'mp'} - method{k} = 'mp'; - fhandle{k} = @pspm_sf_mp; - datatype(k) = 2; % filtered - case {'SCL', 'scl', 'level'} - method{k} = 'scl'; - fhandle{k} = @pspm_sf_scl; - datatype(k) = 1; % unfiltered - case 'all' - method = {'scl', 'auc', 'dcm', 'mp'}; - fhandle = {@pspm_sf_scl, @pspm_sf_auc, @pspm_sf_dcm, @pspm_sf_mp}; - datatype = [1 2 2 2]; - otherwise - warning('Method %s not supported', model.method{k}); return; + method = cell(numel(model.method), 1); + fhandle = method; + datatype = NaN(numel(model.method)); + for k = 1:numel(model.method) + switch model.method{k} + case {'auc', 'AUC'} + method{k} = 'auc'; + fhandle{k} = @pspm_sf_auc; + datatype(k) = 2; % filtered + case {'DCM', 'dcm'} + method{k} = 'dcm'; + fhandle{k} = @pspm_sf_dcm; + datatype(k) = 2; % filtered + case {'MP', 'mp'} + method{k} = 'mp'; + fhandle{k} = @pspm_sf_mp; + datatype(k) = 2; % filtered + case {'SCL', 'scl', 'level'} + method{k} = 'scl'; + fhandle{k} = @pspm_sf_scl; + datatype(k) = 1; % unfiltered + case 'all' + method = {'scl', 'auc', 'dcm', 'mp'}; + fhandle = {@pspm_sf_scl, @pspm_sf_auc, @pspm_sf_dcm, @pspm_sf_mp}; + datatype = [1 2 2 2]; + otherwise + warning('Method %s not supported', model.method{k}); return; + end end - end end % 2.6 Check timing if strcmpi(model.timeunits, 'whole') - epochs = repmat({[1 1]}, numel(model.datafile), 1); + epochs = repmat({[1 1]}, numel(model.datafile), 1); else - for iSn = 1:numel(model.datafile) - [sts_get_timing, epochs{iSn}] = pspm_get_timing('epochs', model.timing{iSn}, model.timeunits); - if sts_get_timing == -1 - warning('ID:invalid_input', 'Call of pspm_get_timing failed.'); - return; + for iSn = 1:numel(model.datafile) + [sts_get_timing, epochs{iSn}] = pspm_get_timing('epochs', model.timing{iSn}, model.timeunits); + if sts_get_timing == -1 + warning('ID:invalid_input', 'Call of pspm_get_timing failed.'); + return; + end end - end end % 2.7 Check filter if ~isfield(model, 'filter') - model.filter = settings.dcm{2}.filter; + model.filter = settings.dcm{2}.filter; elseif ~isfield(model.filter, 'down') || ~isnumeric(model.filter.down) - warning('ID:invalid_input', 'Filter structure needs a numeric ''down'' field.'); return; + warning('ID:invalid_input', 'Filter structure needs a numeric ''down'' field.'); return; end % 2.8 Set options try model.channel; catch, model.channel = 'scr'; end options = pspm_options(options, 'sf'); if options.invalid - return + return end % 2.9 Set missing epochs if ~isfield(model, 'missing') - model.missing = cell(numel(model.datafile), 1); + model.missing = cell(numel(model.datafile), 1); elseif ischar(model.missing) || isnumeric(model.missing) - model.missing = {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 + warning('ID:invalid_input',... + 'Missing values must be a filename, matrix, or cell array of these.'); + return end %% 3 Get data nFile = numel(model.datafile); for iFile = 1:nFile - % 3.1 User output - fprintf('SF analysis: %s ...', model.datafile{iFile}); - % 3.2 Check whether model file exists - if ~pspm_overwrite(model.modelfile, options) - return - end - % 3.3 get and filter data - [sts_load_data, ~, data] = pspm_load_data(model.datafile{iFile}, model.channel); - if sts_load_data < 0, return; end - y{1} = data{end}.data; - sr(1) = data{end}.header.sr; - model.filter.sr = sr(1); - [sts_prepdata, y{2}, sr(2)] = pspm_prepdata(data{end}.data, model.filter); - % always use last data channels - if sts_prepdata == -1 - warning('ID:invalid_input', 'Call of pspm_prepdata failed.'); - return; - end - % 3.4 Check data units - if ~strcmpi(data{end}.header.units, 'uS') && any(strcmpi('dcm', method)) - fprintf(['\nYour data units are stored as %s, ',... - 'and the method will apply an amplitude threshold in uS. ',... - 'Please check your results.\n'], ... - data{end}.header.units); - end - % 3.5 Get missing epochs - if ~isempty(model.missing{iFile}) - [~, missing{iFile}] = pspm_get_timing('epochs', ... - model.missing{iFile}, 'seconds'); - % sort missing epochs - if size(missing{iFile}, 1) > 0 - [~, sortindx] = sort(missing{iFile}(:, 1)); - missing{iFile} = missing{iFile}(sortindx,:); - % check for overlap and merge - for k = 2:size(missing{iSn}, 1) - if missing{iFile}(k, 1) <= missing{iFile}(k - 1, 2) - missing{iFile}(k, 1) = missing{iFile}(k - 1, 1); - missing{iFile}(k - 1, :) = []; - end - end + % 3.1 User output + fprintf('SF analysis: %s ...', model.datafile{iFile}); + % 3.2 Check whether model file exists + if ~pspm_overwrite(model.modelfile, options) + return end - else - missing{iFile} = []; - end - % 3.6 Get marker data - if any(strcmp(model.timeunits, {'marker', 'markers'})) - if options.marker_chan_num - [sts, ~, ndata] = pspm_load_data(model.datafile{iFile}, options.marker_chan_num); - if sts < 1 - warning('ID:invalid_input', 'Could not load data'); + % 3.3 get and filter data + [sts_load_data, ~, data] = pspm_load_data(model.datafile{iFile}, model.channel); + if sts_load_data < 0, return; end + y{1} = data{end}.data; + sr(1) = data{end}.header.sr; + model.filter.sr = sr(1); + [sts_prepdata, y{2}, sr(2)] = pspm_prepdata(data{end}.data, model.filter); + % always use last data channels + if sts_prepdata == -1 + warning('ID:invalid_input', 'Call of pspm_prepdata failed.'); return; - end - if ~strcmp(ndata{1}.header.chantype, 'marker') - warning('ID:invalid_option', ... - ['Channel %i is no marker channel. ',... - 'The first marker channel in the file is used instead'],... - options.marker_chan_num); - [sts_load_data, ~, ~] = pspm_load_data(model.datafile{iFile}, 'marker'); - if sts_load_data == -1 - warning('ID:invalid_input', 'Could not load data'); - return; + end + % 3.4 Check data units + if ~strcmpi(data{end}.header.units, 'uS') && any(strcmpi('dcm', method)) + fprintf(['\nYour data units are stored as %s, ',... + 'and the method will apply an amplitude threshold in uS. ',... + 'Please check your results.\n'], ... + data{end}.header.units); + end + % 3.5 Get missing epochs + if ~isempty(model.missing{iFile}) + [~, missing{iFile}] = pspm_get_timing('epochs', ... + model.missing{iFile}, 'seconds'); + % sort missing epochs + if size(missing{iFile}, 1) > 0 + [~, sortindx] = sort(missing{iFile}(:, 1)); + missing{iFile} = missing{iFile}(sortindx,:); + % check for overlap and merge + for k = 2:size(missing{iSn}, 1) + if missing{iFile}(k, 1) <= missing{iFile}(k - 1, 2) + missing{iFile}(k, 1) = missing{iFile}(k - 1, 1); + missing{iFile}(k - 1, :) = []; + end + end end - end else - [nsts, ~, ~] = pspm_load_data(model.datafile{iFile}, 'marker'); - if nsts == -1 - warning('ID:invalid_input', 'Could not load data'); - return; - end + missing{iFile} = []; + end + % 3.6 Get marker data + if any(strcmp(model.timeunits, {'marker', 'markers'})) + [sts, ~, ndata] = pspm_load_data(model.datafile{iFile}, options.marker_chan_num); + if sts < 1 + warning('ID:invalid_input', 'Could not load data'); + return; + elseif ~strcmp(ndata{1}.header.chantype, 'marker') + warning('ID:invalid_option', ... + ['Channel %i is no marker channel. ',... + 'The first marker channel in the file is used instead'],... + options.marker_chan_num); + [sts_load_data, ~, ~] = pspm_load_data(model.datafile{iFile}, 'marker'); + if sts_load_data == -1 + warning('ID:invalid_input', 'Could not load data'); + return; + end + end end + % use first marker channel events{iFile} = data{1}.data(:); - end - for iEpoch = 1:size(epochs{iFile}, 1) - if iEpoch > 1, fprintf('\n\t\t\t'); end - fprintf('epoch %01.0f ...', iEpoch); - for k = 1:numel(method) - fprintf('%s ', method{k}); - switch model.timeunits - case 'seconds' - win = round(epochs{iFile}(iEpoch, :) * sr(datatype(k))); - case 'samples' - win = round(epochs{iFile}(iEpoch, :) * sr(datatype(k)) / sr(1)); - case 'markers' - win = round(events(epochs{1}(iEpoch, :)) * sr(datatype(k))); - case 'whole' - win = [1 numel(y{datatype(k)})]; - end - if any(win > numel(y{datatype(k)}) + 1) || any(win < 0) - warning('\nEpoch %2.0f outside of file %s ...', iEpoch, model.modelfile{iFile}); - else - % correct issues with using 'round' - win(1) = max(win(1), 1); - win(2) = min(win(2), numel(y{datatype(k)})); - end - % 3.6.1 collect information - sf.model{k}(iEpoch).modeltype = method{k}; - sf.model{k}(iEpoch).boundaries = squeeze(epochs{iFile}(iEpoch, :)); - sf.model{k}(iEpoch).timeunits = model.timeunits; - sf.model{k}(iEpoch).samples = win; - sf.model{k}(iEpoch).sr = sr(datatype(k)); - % - escr = y{datatype(k)}(win(1):win(end)); - sf.model{k}(iEpoch).data = escr; - % 3.6.2 do the analysis and collect results - model_analysis = struct('scr', escr, 'sr', sr(datatype(k))); - invrs = fhandle{k}(model_analysis, options); - if any(strcmpi(method{k}, {'dcm', 'mp'})) - sf.model{k}(iEpoch).inv = invrs; - sf.stats(iEpoch, k) = invrs.f; - else - sf.model{k}(iEpoch).stats = invrs; - sf.stats(iEpoch, k) = invrs; - end + + + for iEpoch = 1:size(epochs{iFile}, 1) + if iEpoch > 1, fprintf('\n\t\t\t'); end + fprintf('epoch %01.0f ...', iEpoch); + for k = 1:numel(method) + fprintf('%s ', method{k}); + switch model.timeunits + case 'seconds' + win = round(epochs{iFile}(iEpoch, :) * sr(datatype(k))); + case 'samples' + win = round(epochs{iFile}(iEpoch, :) * sr(datatype(k)) / sr(1)); + case 'markers' + win = round(events(epochs{1}(iEpoch, :)) * sr(datatype(k))); + case 'whole' + win = [1 numel(y{datatype(k)})]; + end + if any(win > numel(y{datatype(k)}) + 1) || any(win < 0) + warning('\nEpoch %2.0f outside of file %s ...', iEpoch, model.modelfile{iFile}); + else + % correct issues with using 'round' + win(1) = max(win(1), 1); + win(2) = min(win(2), numel(y{datatype(k)})); + end + % 3.6.1 collect information + sf.model{k}(iEpoch).modeltype = method{k}; + sf.model{k}(iEpoch).boundaries = squeeze(epochs{iFile}(iEpoch, :)); + sf.model{k}(iEpoch).timeunits = model.timeunits; + sf.model{k}(iEpoch).samples = win; + sf.model{k}(iEpoch).sr = sr(datatype(k)); + % + escr = y{datatype(k)}(win(1):win(end)); + sf.model{k}(iEpoch).data = escr; + % 3.6.2 do the analysis and collect results + model_analysis = struct('scr', escr, 'sr', sr(datatype(k))); + invrs = fhandle{k}(model_analysis, options); + if any(strcmpi(method{k}, {'dcm', 'mp'})) + sf.model{k}(iEpoch).inv = invrs; + sf.stats(iEpoch, k) = invrs.f; + else + sf.model{k}(iEpoch).stats = invrs; + sf.stats(iEpoch, k) = invrs; + end + end + sf.trlnames{iEpoch} = sprintf('Epoch #%d', iEpoch); end - sf.trlnames{iEpoch} = sprintf('Epoch #%d', iEpoch); - end - sf.names = method(:); - sf.infos.date = date; - sf.infos.file = model.modelfile{iFile}; - sf.modelfile = model.modelfile{iFile}; - sf.data = y; - if exist('events','var'), sf.events = events; end - sf.input = model; - sf.options = options; - sf.modeltype = 'sf'; - sf.modality = settings.modalities.sf; - save(model.modelfile{iFile}, 'sf'); - outfile = model.modelfile(iFile); - fprintf('\n'); + sf.names = method(:); + sf.infos.date = date; + sf.infos.file = model.modelfile{iFile}; + sf.modelfile = model.modelfile{iFile}; + sf.data = y; + if exist('events','var'), sf.events = events; end + sf.input = model; + sf.options = options; + sf.modeltype = 'sf'; + sf.modality = settings.modalities.sf; + save(model.modelfile{iFile}, 'sf'); + outfile = model.modelfile(iFile); + fprintf('\n'); end sts = 1; switch nargout - case 1 - varargout{1} = outfile; - case 2 - varargout{1} = sts; - varargout{2} = outfile; + case 1 + varargout{1} = outfile; + case 2 + varargout{1} = sts; + varargout{2} = outfile; end return diff --git a/src/pspm_trim.m b/src/pspm_trim.m index 05f06021f..42b704817 100644 --- a/src/pspm_trim.m +++ b/src/pspm_trim.m @@ -51,327 +51,324 @@ % 1.1 Initialise global settings if isempty(settings) - pspm_init; + pspm_init; end sts = -1; newdatafile = []; % 1.2 Verify the number of input arguments switch nargin - case 0 - warning('ID:invalid_input', 'No data.\n'); - return - case 1 - warning('ID:invalid_input', 'No start or end point given.\n'); - return - case 2 - warning('ID:invalid_input', 'No end point given.\n'); - return - case 3 - warning('ID:invalid_input', 'No reference given.\n'); - return + case 0 + warning('ID:invalid_input', 'No data.\n'); + return + case 1 + warning('ID:invalid_input', 'No start or end point given.\n'); + return + case 2 + warning('ID:invalid_input', 'No end point given.\n'); + return + case 3 + warning('ID:invalid_input', 'No reference given.\n'); + return end % 1.3 Verify the data file if ischar(datafile) || isstruct(datafile) - D = {datafile}; - Dout = D; % the output cell + D = {datafile}; + Dout = D; % the output cell elseif iscell(datafile) - D = datafile; - Dout = D; % the output cell + D = datafile; + Dout = D; % the output cell else - warning('Data file must be a char, cell, or struct.'); + warning('Data file must be a char, cell, or struct.'); end clear datafile % 1.4 Verify the start and end points if ~( ... - (ischar(from) && strcmpi(from, 'none')) || ... - (isnumeric(from) && numel(from) == 1) || ... - (isnumeric(from) && numel(from) == numel(D)) ... - ) - warning('ID:invalid_input', 'No valid start point given.\n'); - return + (ischar(from) && strcmpi(from, 'none')) || ... + (isnumeric(from) && numel(from) == 1) || ... + (isnumeric(from) && numel(from) == numel(D)) ... + ) + warning('ID:invalid_input', 'No valid start point given.\n'); + return end if ~( ... - (ischar(to) && strcmpi(to, 'none')) || ... - (isnumeric(to) && numel(to) == 1) || ... - (isnumeric(to) && numel(to) == numel(D)) ... - ) - warning('ID:invalid_input', 'No end point given'); - return + (ischar(to) && strcmpi(to, 'none')) || ... + (isnumeric(to) && numel(to) == 1) || ... + (isnumeric(to) && numel(to) == numel(D)) ... + ) + warning('ID:invalid_input', 'No end point given'); + return end if numel(from) == 1 - from = ones(1,numel(D)) * from; + from = ones(1,numel(D)) * from; end if numel(to) == 1 - to = ones(1,numel(D)) * to; + to = ones(1,numel(D)) * to; end % 1.5 Verify reference calculate_idx = false; switch(class(reference)) - case 'char' - switch reference - case 'marker' - getmarker = 1; - startmarker = 1; - g_endmarker = []; - case 'file' - getmarker = 0; - otherwise + case 'char' + switch reference + case 'marker' + getmarker = 1; + startmarker = 1; + g_endmarker = []; + case 'file' + getmarker = 0; + otherwise + warning('ID:invalid_input', ... + ['Invalid reference option ''%s'', ',... + 'should be marker or file.'], reference); + return + end + case 'double' + if numel(reference) == 2 + getmarker = 1; + startmarker = reference(1); + g_endmarker = reference(2); + % check if reference markers are valid --- + if startmarker < 1 || g_endmarker < startmarker + warning('ID:invalid_input', 'No valid reference markers.\n'); + return + end + else + warning('ID:invalid_input', 'Invalid reference option ''%s'', should contain only two elements', reference); + return + end + case 'cell' + if numel(reference) == 2 + getmarker = 1; + marker_sta_vals = reference{1}; + marker_end_vals = reference{2}; + calculate_idx = true; + else + warning('ID:invalid_input', ... + 'Invalid reference option ''%s'', should contain only two elements',... + reference); + return + end + otherwise warning('ID:invalid_input', ... - ['Invalid reference option ''%s'', ',... - 'should be marker or file.'], reference); - return - end - case 'double' - if numel(reference) == 2 - getmarker = 1; - startmarker = reference(1); - g_endmarker = reference(2); - % check if reference markers are valid --- - if startmarker < 1 || g_endmarker < startmarker - warning('ID:invalid_input', 'No valid reference markers.\n'); + 'Invalid reference option ''%s'', should be a character, a number, or a cell', reference); return - end - else - warning('ID:invalid_input', 'Invalid reference option ''%s'', should contain only two elements', reference); - return - end - case 'cell' - if numel(reference) == 2 - getmarker = 1; - marker_sta_vals = reference{1}; - marker_end_vals = reference{2}; - calculate_idx = true; - else - warning('ID:invalid_input', ... - 'Invalid reference option ''%s'', should contain only two elements',... - reference); - return - end - otherwise - warning('ID:invalid_input', ... - 'Invalid reference option ''%s'', should be a character, a number, or a cell', reference); - return end % 1.6 Set options if ~exist('options','var') || isempty(options) || ~isstruct(options) - options = struct(); + options = struct(); end options = pspm_options(options, 'trim'); if options.invalid - return + return end %% 2 Work on all data for i_D = 1:numel(D) - % 2.1 Obtain essential file info - datafile = D{i_D}; % Obtain file name - if isstruct(datafile) - fprintf('Trimming ... '); - else - fprintf('Trimming %s ... ', datafile); - end - [sts_load_data, infos, data] = pspm_load_data(datafile, 0); - if ~sts_load_data - warning('ID:invalid_option', 'Failed to load datafile'); - end - % 2.2 Calculate markers if needed - if getmarker == 1 - % 2.2.1 Verify the markers - if options.marker_chan_num - [nsts, ~, ndata] = pspm_load_data(datafile, options.marker_chan_num); - if ~strcmp(ndata{1}.header.chantype, 'marker') - warning('ID:invalid_option', ['Channel %i is no marker ', ... - ' channel. The first marker channel in the file is ', ... - 'used instead'], options.marker_chan_num); - [nsts, ~, ndata] = pspm_load_data(datafile, 'marker'); - end - else - [nsts, ~, ndata] = pspm_load_data(datafile, 'marker'); - end - if nsts > 0 - events = ndata{1}.data; + % 2.1 Obtain essential file info + datafile = D{i_D}; % Obtain file name + if isstruct(datafile) + fprintf('Trimming ... '); else - return + fprintf('Trimming %s ... ', datafile); end - if isempty(events) - warning('ID:marker_out_of_range', 'Marker channel (%i) is empty. Cannot use as a reference.', options.marker_chan_num); - return + [sts_load_data, infos, data] = pspm_load_data(datafile, 0); + if ~sts_load_data + warning('ID:invalid_option', 'Failed to load datafile'); end - % 2.2.2 Caluculate marker idx if specified by marker values or markernames - if calculate_idx - % get idx of starting marker - try_num_start = str2double(marker_sta_vals); - if ~isempty(try_num_start) - startmarker = find(ndata{1}.markerinfo.value == try_num_start,1); - elseif ischar(marker_sta_vals) - startmarker = find(strcmpi(ndata{1}.markerinfo.name,marker_sta_vals),1); - else - warning('ID:invalid_input', 'The value or name of the starting marker must be numeric or a string'); - return - end - % get idx of ending marker - try_num_end = str2double(marker_end_vals); - if ~isempty(try_num_end) - g_endmarker = find(ndata{1}.markerinfo.value == try_num_end,1); - elseif ischar(marker_end_vals) - g_endmarker = find(strcmpi(ndata{1}.markerinfo.name,marker_end_vals),1); - else - warning('ID:invalid_input', 'The value or name of the ending marker must be numeric or a string'); - return - end - % check if the markers are valid - if startmarker < 1 || g_endmarker < startmarker - warning('ID:invalid_input', 'No valid reference markers.\n'); return - end + % 2.2 Calculate markers if needed + if getmarker == 1 + % 2.2.1 Verify the markers + [nsts, ~, ndata] = pspm_load_data(datafile, options.marker_chan_num); + if ~strcmp(ndata{1}.header.chantype, 'marker') + warning('ID:invalid_option', ['Channel %i is no marker ', ... + ' channel. The first marker channel in the file is ', ... + 'used instead'], options.marker_chan_num); + [nsts, ~, ndata] = pspm_load_data(datafile, 'marker'); + end + if nsts > 0 + events = ndata{1}.data; + else + return + end + if isempty(events) + warning('ID:marker_out_of_range', 'Marker channel (%i) is empty. Cannot use as a reference.', options.marker_chan_num); + return + end + % 2.2.2 Caluculate marker idx if specified by marker values or markernames + if calculate_idx + % get idx of starting marker + try_num_start = str2double(marker_sta_vals); + if ~isempty(try_num_start) + startmarker = find(ndata{1}.markerinfo.value == try_num_start,1); + elseif ischar(marker_sta_vals) + startmarker = find(strcmpi(ndata{1}.markerinfo.name,marker_sta_vals),1); + else + warning('ID:invalid_input', 'The value or name of the starting marker must be numeric or a string'); + return + end + % get idx of ending marker + try_num_end = str2double(marker_end_vals); + if ~isempty(try_num_end) + g_endmarker = find(ndata{1}.markerinfo.value == try_num_end,1); + elseif ischar(marker_end_vals) + g_endmarker = find(strcmpi(ndata{1}.markerinfo.name,marker_end_vals),1); + else + warning('ID:invalid_input', 'The value or name of the ending marker must be numeric or a string'); + return + end + % check if the markers are valid + if startmarker < 1 || g_endmarker < startmarker + warning('ID:invalid_input', 'No valid reference markers.\n'); return + end + end + % 2.2.3 set local endmarker depending on global endmarker + if isempty(g_endmarker) + l_endmarker = numel(events); + else + l_endmarker = g_endmarker; + end + clear nsts ninfos ndata end - % 2.2.3 set local endmarker depending on global endmarker - if isempty(g_endmarker) - l_endmarker = numel(events); + + % 2.3 Convert from and to from time points into seconds + if ischar(from) % 'none' + sta_p = 0; + sta_offset = 0; else - l_endmarker = g_endmarker; + if getmarker % 'marker' + sta_p = events(startmarker); + sta_offset = from(i_D); + else % 'file' + sta_p = from(i_D); + sta_offset = 0; + end end - clear nsts ninfos ndata - end - % 2.3 Convert from and to from time points into seconds - if ischar(from) % 'none' - sta_p = 0; - sta_offset = 0; - else - if getmarker % 'marker' - sta_p = events(startmarker); - sta_offset = from(i_D); - else % 'file' - sta_p = from(i_D); - sta_offset = 0; - end - end - if ischar(to) % 'none' - sto_p = infos.duration; - sto_offset = 0; - else - if getmarker % 'marker' - if l_endmarker > numel(events) - warning('ID:marker_out_of_range', ... - ['\nEnd marker (%03.0f) out of file - no ', ... - 'trimming at end.\n'], g_endmarker); + if ischar(to) % 'none' sto_p = infos.duration; sto_offset = 0; - else - sto_p = events(l_endmarker); - sto_offset = to(i_D); - end - else % 'file' - sto_p = to(i_D); - sto_offset = 0; - end - end - % 2.4 Check start and end points - if ((sta_p + sta_offset) < 0) - warning('ID:marker_out_of_range', ['\nStart point (%.2f s) outside', ... - ' file, no trimming at start.'], (sta_p + sta_offset)); - if (sta_p > 0) - sta_offset = -sta_p; else - sta_p = 0; - sta_offset = 0; + if getmarker % 'marker' + if l_endmarker > numel(events) + warning('ID:marker_out_of_range', ... + ['\nEnd marker (%03.0f) out of file - no ', ... + 'trimming at end.\n'], g_endmarker); + sto_p = infos.duration; + sto_offset = 0; + else + sto_p = events(l_endmarker); + sto_offset = to(i_D); + end + else % 'file' + sto_p = to(i_D); + sto_offset = 0; + end end - end - if (sto_p + sto_offset) > infos.duration - warning('ID:marker_out_of_range', ['\nEnd point (%.2f s) outside ', ... - 'file, no trimming at end.'], (sto_p + sto_offset)); - if (sto_p > infos.duration) - sto_p = infos.duration; - sto_offset = 0; - else - sto_offset = infos.duration - sto_p; + % 2.4 Check start and end points + if ((sta_p + sta_offset) < 0) + warning('ID:marker_out_of_range', ['\nStart point (%.2f s) outside', ... + ' file, no trimming at start.'], (sta_p + sta_offset)); + if (sta_p > 0) + sta_offset = -sta_p; + else + sta_p = 0; + sta_offset = 0; + end + end + if (sto_p + sto_offset) > infos.duration + warning('ID:marker_out_of_range', ['\nEnd point (%.2f s) outside ', ... + 'file, no trimming at end.'], (sto_p + sto_offset)); + if (sto_p > infos.duration) + sto_p = infos.duration; + sto_offset = 0; + else + sto_offset = infos.duration - sto_p; + end end - end - % 2.5 Trim file - for k = 1:numel(data) - if ~strcmpi(data{k}.header.units, 'events') % waveform channels - % set start point (`ceil` for protect against having duration < data*sr, - % the `+1` is here because of matlabs convention to start indices from 1) - newstartpoint = ceil((sta_p + sta_offset) * data{k}.header.sr)+1; - if newstartpoint == 0 - newstartpoint = 1; - end - % set end point - newendpoint = floor((sto_p + sto_offset) * data{k}.header.sr); - if newendpoint > numel(data{k}.data), ... - newendpoint = numel(data{k}.data); - end - % trim data - data{k}.data=data{k}.data(newstartpoint:newendpoint); - else % event channels - if options.drop_offset_markers - newendpoint = sto_p; - newstartpoint = sta_p; - else - newendpoint = sto_p + sto_offset; - newstartpoint = sta_p + sta_offset; - end - remove_late = data{k}.data > newendpoint; - data{k}.data(remove_late) = []; - data{k}.data = data{k}.data - newstartpoint; - remove_early = data{k}.data < 0; - data{k}.data(remove_early) = []; + % 2.5 Trim file + for k = 1:numel(data) + if ~strcmpi(data{k}.header.units, 'events') % waveform channels + % set start point (`ceil` for protect against having duration < data*sr, + % the `+1` is here because of matlabs convention to start indices from 1) + newstartpoint = ceil((sta_p + sta_offset) * data{k}.header.sr)+1; + if newstartpoint == 0 + newstartpoint = 1; + end + % set end point + newendpoint = floor((sto_p + sto_offset) * data{k}.header.sr); + if newendpoint > numel(data{k}.data), ... + newendpoint = numel(data{k}.data); + end + % trim data + data{k}.data=data{k}.data(newstartpoint:newendpoint); + else % event channels + if options.drop_offset_markers + newendpoint = sto_p; + newstartpoint = sta_p; + else + newendpoint = sto_p + sto_offset; + newstartpoint = sta_p + sta_offset; + end + remove_late = data{k}.data > newendpoint; + data{k}.data(remove_late) = []; + data{k}.data = data{k}.data - newstartpoint; + remove_early = data{k}.data < 0; + data{k}.data(remove_early) = []; - % move to match data if offset markers should be dropped - if options.drop_offset_markers - data{k}.data = data{k}.data - sta_offset; - end - if isfield(data{k}, 'markerinfo') - % also trim marker info if available - data{k}.markerinfo.value(remove_late) = []; - data{k}.markerinfo.name(remove_late) = []; + % move to match data if offset markers should be dropped + if options.drop_offset_markers + data{k}.data = data{k}.data - sta_offset; + end + if isfield(data{k}, 'markerinfo') + % also trim marker info if available + data{k}.markerinfo.value(remove_late) = []; + data{k}.markerinfo.name(remove_late) = []; - data{k}.markerinfo.value(remove_early) = []; - data{k}.markerinfo.name(remove_early) = []; - end + data{k}.markerinfo.value(remove_early) = []; + data{k}.markerinfo.name(remove_early) = []; + end + end + % save new file + infos.duration = (sto_p + sto_offset) - (sta_p + sta_offset); + infos.trimdate = date; + infos.trimpoints = [(sta_p + sta_offset) (sto_p + sto_offset)]; end - % save new file - infos.duration = (sto_p + sto_offset) - (sta_p + sta_offset); - infos.trimdate = date; - infos.trimpoints = [(sta_p + sta_offset) (sto_p + sto_offset)]; - end - clear savedata - % 2.6 Save data - savedata.data = data; - savedata.infos = infos; - if isstruct(datafile) - sts_load_data = pspm_load_data(savedata, 'none'); - if ~sts_load_data - warning('ID:unable_to_save', 'Cannot save data to the expected file.') - return - end - newdatafile = savedata; - else - [pth, fn, ext] = fileparts(datafile); - newdatafile = fullfile(pth, ['t', fn, ext]); - savedata.infos.trimfile = newdatafile; - options.overwrite = pspm_overwrite(newdatafile, options); - savedata.options = options; - sts_load_data = pspm_load_data(newdatafile, savedata); - if ~sts_load_data - warning('ID:unable_to_save', 'Cannot save data to the expected file.') - return + clear savedata + % 2.6 Save data + savedata.data = data; + savedata.infos = infos; + if isstruct(datafile) + sts_load_data = pspm_load_data(savedata, 'none'); + if ~sts_load_data + warning('ID:unable_to_save', 'Cannot save data to the expected file.') + return + end + newdatafile = savedata; + else + [pth, fn, ext] = fileparts(datafile); + newdatafile = fullfile(pth, ['t', fn, ext]); + savedata.infos.trimfile = newdatafile; + options.overwrite = pspm_overwrite(newdatafile, options); + savedata.options = options; + sts_load_data = pspm_load_data(newdatafile, savedata); + if ~sts_load_data + warning('ID:unable_to_save', 'Cannot save data to the expected file.') + return + end end - end - Dout{i_D} = newdatafile; - fprintf(' done.\n'); + Dout{i_D} = newdatafile; + fprintf(' done.\n'); end % 3 Return value % if cell array of datafiles is being processed, % return cell array of filenames if i_D > 1 - clear newdatafile - newdatafile = Dout; + clear newdatafile + newdatafile = Dout; end sts = 1; switch nargout - case 1 - varargout{1} = newdatafile; - case 2 - varargout{1} = sts; - varargout{2} = newdatafile; + case 1 + varargout{1} = newdatafile; + case 2 + varargout{1} = sts; + varargout{2} = newdatafile; end return From 88cdf00a7e73222065a2179eb49d13f550546562 Mon Sep 17 00:00:00 2001 From: "teddy.chao" Date: Fri, 11 Aug 2023 16:51:37 +0100 Subject: [PATCH 24/26] Update pspm_sf.m --- src/pspm_sf.m | 378 +++++++++++++++++++++++++------------------------- 1 file changed, 189 insertions(+), 189 deletions(-) diff --git a/src/pspm_sf.m b/src/pspm_sf.m index e4cd17c0d..877d286a9 100644 --- a/src/pspm_sf.m +++ b/src/pspm_sf.m @@ -72,263 +72,263 @@ %% 1 Initialise global settings if isempty(settings) - pspm_init; + pspm_init; end outfile = []; sts = -1; %% 2 Check input % 2.1 Check missing input if nargin<1 - warning('ID:invalid_input', 'Nothing to do.'); return; + warning('ID:invalid_input', 'Nothing to do.'); return; elseif nargin<2 - options = struct(); + options = struct(); end if ~isfield(model, 'datafile') - warning('ID:invalid_input', 'No input data file specified.'); return; + warning('ID:invalid_input', 'No input data file specified.'); return; elseif ~isfield(model, 'modelfile') - warning('ID:invalid_input', 'No output model file specified.'); return; + warning('ID:invalid_input', 'No output model file specified.'); return; elseif ~isfield(model, 'timeunits') - warning('ID:invalid_input', 'No timeunits specified.'); return; + warning('ID:invalid_input', 'No timeunits specified.'); return; elseif ~isfield(model, 'timing') && ~strcmpi(model.timeunits, 'file') - warning('ID:invalid_input', 'No epochs specified.'); return; + warning('ID:invalid_input', 'No epochs specified.'); return; end % 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; + 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; + 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; + 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'}) - warning('ID:invalid_input',... - 'Timeunits (%s) not recognised; ',... - 'only ''seconds'', ''markers'', ''samples'' and ''whole'' are supported',... - model.timeunits); return; + warning('ID:invalid_input',... + 'Timeunits (%s) not recognised; ',... + 'only ''seconds'', ''markers'', ''samples'' and ''whole'' are supported',... + model.timeunits); return; end % 2.3 Convert single file input to cell -- if ischar(model.datafile) - model.datafile={model.datafile}; + model.datafile={model.datafile}; end if ischar(model.timing) || isnumeric(model.timing) - model.timing = {model.timing}; + model.timing = {model.timing}; end if ischar(model.modelfile) - model.modelfile = {model.modelfile}; + model.modelfile = {model.modelfile}; end % 2.4 Check number of files if ~strcmpi(model.timeunits, 'whole') && numel(model.datafile) ~= numel(model.timing) - warning('ID:number_of_elements_dont_match',... - 'Number of data files and epoch definitions does not match.'); return; + warning('ID:number_of_elements_dont_match',... + 'Number of data files and epoch definitions does not match.'); return; elseif numel(model.datafile) ~= numel(model.modelfile) - warning('ID:number_of_elements_dont_match',... - 'Number of data files and model files does not match.'); return; + warning('ID:number_of_elements_dont_match',... + 'Number of data files and model files does not match.'); return; end % 2.5 check methods if ~isfield(model, 'method') - model.method = {'dcm'}; + model.method = {'dcm'}; elseif ischar(model.method) - model.method={model.method}; + model.method={model.method}; end if ~iscell(model.method) - warning('Method needs to be a char or cell array'); return; + warning('Method needs to be a char or cell array'); return; else - method = cell(numel(model.method), 1); - fhandle = method; - datatype = NaN(numel(model.method)); - for k = 1:numel(model.method) - switch model.method{k} - case {'auc', 'AUC'} - method{k} = 'auc'; - fhandle{k} = @pspm_sf_auc; - datatype(k) = 2; % filtered - case {'DCM', 'dcm'} - method{k} = 'dcm'; - fhandle{k} = @pspm_sf_dcm; - datatype(k) = 2; % filtered - case {'MP', 'mp'} - method{k} = 'mp'; - fhandle{k} = @pspm_sf_mp; - datatype(k) = 2; % filtered - case {'SCL', 'scl', 'level'} - method{k} = 'scl'; - fhandle{k} = @pspm_sf_scl; - datatype(k) = 1; % unfiltered - case 'all' - method = {'scl', 'auc', 'dcm', 'mp'}; - fhandle = {@pspm_sf_scl, @pspm_sf_auc, @pspm_sf_dcm, @pspm_sf_mp}; - datatype = [1 2 2 2]; - otherwise - warning('Method %s not supported', model.method{k}); return; - end + method = cell(numel(model.method), 1); + fhandle = method; + datatype = NaN(numel(model.method)); + for k = 1:numel(model.method) + switch model.method{k} + case {'auc', 'AUC'} + method{k} = 'auc'; + fhandle{k} = @pspm_sf_auc; + datatype(k) = 2; % filtered + case {'DCM', 'dcm'} + method{k} = 'dcm'; + fhandle{k} = @pspm_sf_dcm; + datatype(k) = 2; % filtered + case {'MP', 'mp'} + method{k} = 'mp'; + fhandle{k} = @pspm_sf_mp; + datatype(k) = 2; % filtered + case {'SCL', 'scl', 'level'} + method{k} = 'scl'; + fhandle{k} = @pspm_sf_scl; + datatype(k) = 1; % unfiltered + case 'all' + method = {'scl', 'auc', 'dcm', 'mp'}; + fhandle = {@pspm_sf_scl, @pspm_sf_auc, @pspm_sf_dcm, @pspm_sf_mp}; + datatype = [1 2 2 2]; + otherwise + warning('Method %s not supported', model.method{k}); return; end + end end % 2.6 Check timing if strcmpi(model.timeunits, 'whole') - epochs = repmat({[1 1]}, numel(model.datafile), 1); + epochs = repmat({[1 1]}, numel(model.datafile), 1); else - for iSn = 1:numel(model.datafile) - [sts_get_timing, epochs{iSn}] = pspm_get_timing('epochs', model.timing{iSn}, model.timeunits); - if sts_get_timing == -1 - warning('ID:invalid_input', 'Call of pspm_get_timing failed.'); - return; - end + for iSn = 1:numel(model.datafile) + [sts_get_timing, epochs{iSn}] = pspm_get_timing('epochs', model.timing{iSn}, model.timeunits); + if sts_get_timing == -1 + warning('ID:invalid_input', 'Call of pspm_get_timing failed.'); + return; end + end end % 2.7 Check filter if ~isfield(model, 'filter') - model.filter = settings.dcm{2}.filter; + model.filter = settings.dcm{2}.filter; elseif ~isfield(model.filter, 'down') || ~isnumeric(model.filter.down) - warning('ID:invalid_input', 'Filter structure needs a numeric ''down'' field.'); return; + warning('ID:invalid_input', 'Filter structure needs a numeric ''down'' field.'); return; end % 2.8 Set options try model.channel; catch, model.channel = 'scr'; end options = pspm_options(options, 'sf'); if options.invalid - return + return end % 2.9 Set missing epochs if ~isfield(model, 'missing') - model.missing = cell(numel(model.datafile), 1); + model.missing = cell(numel(model.datafile), 1); elseif ischar(model.missing) || isnumeric(model.missing) - model.missing = {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 + warning('ID:invalid_input',... + 'Missing values must be a filename, matrix, or cell array of these.'); + return end %% 3 Get data nFile = numel(model.datafile); for iFile = 1:nFile - % 3.1 User output - fprintf('SF analysis: %s ...', model.datafile{iFile}); - % 3.2 Check whether model file exists - if ~pspm_overwrite(model.modelfile, options) - return - end - % 3.3 get and filter data - [sts_load_data, ~, data] = pspm_load_data(model.datafile{iFile}, model.channel); - if sts_load_data < 0, return; end - y{1} = data{end}.data; - sr(1) = data{end}.header.sr; - model.filter.sr = sr(1); - [sts_prepdata, y{2}, sr(2)] = pspm_prepdata(data{end}.data, model.filter); - % always use last data channels - if sts_prepdata == -1 - warning('ID:invalid_input', 'Call of pspm_prepdata failed.'); - return; - end - % 3.4 Check data units - if ~strcmpi(data{end}.header.units, 'uS') && any(strcmpi('dcm', method)) - fprintf(['\nYour data units are stored as %s, ',... - 'and the method will apply an amplitude threshold in uS. ',... - 'Please check your results.\n'], ... - data{end}.header.units); - end - % 3.5 Get missing epochs - if ~isempty(model.missing{iFile}) - [~, missing{iFile}] = pspm_get_timing('epochs', ... - model.missing{iFile}, 'seconds'); - % sort missing epochs - if size(missing{iFile}, 1) > 0 - [~, sortindx] = sort(missing{iFile}(:, 1)); - missing{iFile} = missing{iFile}(sortindx,:); - % check for overlap and merge - for k = 2:size(missing{iSn}, 1) - if missing{iFile}(k, 1) <= missing{iFile}(k - 1, 2) - missing{iFile}(k, 1) = missing{iFile}(k - 1, 1); - missing{iFile}(k - 1, :) = []; - end - end + % 3.1 User output + fprintf('SF analysis: %s ...', model.datafile{iFile}); + % 3.2 Check whether model file exists + if ~pspm_overwrite(model.modelfile, options) + return + end + % 3.3 get and filter data + [sts_load_data, ~, data] = pspm_load_data(model.datafile{iFile}, model.channel); + if sts_load_data < 0, return; end + y{1} = data{end}.data; + sr(1) = data{end}.header.sr; + model.filter.sr = sr(1); + [sts_prepdata, y{2}, sr(2)] = pspm_prepdata(data{end}.data, model.filter); + % always use last data channels + if sts_prepdata == -1 + warning('ID:invalid_input', 'Call of pspm_prepdata failed.'); + return; + end + % 3.4 Check data units + if ~strcmpi(data{end}.header.units, 'uS') && any(strcmpi('dcm', method)) + fprintf(['\nYour data units are stored as %s, ',... + 'and the method will apply an amplitude threshold in uS. ',... + 'Please check your results.\n'], ... + data{end}.header.units); + end + % 3.5 Get missing epochs + if ~isempty(model.missing{iFile}) + [~, missing{iFile}] = pspm_get_timing('epochs', ... + model.missing{iFile}, 'seconds'); + % sort missing epochs + if size(missing{iFile}, 1) > 0 + [~, sortindx] = sort(missing{iFile}(:, 1)); + missing{iFile} = missing{iFile}(sortindx,:); + % check for overlap and merge + for k = 2:size(missing{iSn}, 1) + if missing{iFile}(k, 1) <= missing{iFile}(k - 1, 2) + missing{iFile}(k, 1) = missing{iFile}(k - 1, 1); + missing{iFile}(k - 1, :) = []; end - else - missing{iFile} = []; + end end - % 3.6 Get marker data - if any(strcmp(model.timeunits, {'marker', 'markers'})) - [sts, ~, ndata] = pspm_load_data(model.datafile{iFile}, options.marker_chan_num); - if sts < 1 - warning('ID:invalid_input', 'Could not load data'); - return; - elseif ~strcmp(ndata{1}.header.chantype, 'marker') - warning('ID:invalid_option', ... - ['Channel %i is no marker channel. ',... - 'The first marker channel in the file is used instead'],... - options.marker_chan_num); - [sts_load_data, ~, ~] = pspm_load_data(model.datafile{iFile}, 'marker'); - if sts_load_data == -1 - warning('ID:invalid_input', 'Could not load data'); - return; - end - end + else + missing{iFile} = []; + end + % 3.6 Get marker data + if any(strcmp(model.timeunits, {'marker', 'markers'})) + [sts, ~, ndata] = pspm_load_data(model.datafile{iFile}, options.marker_chan_num); + if sts < 1 + warning('ID:invalid_input', 'Could not load data'); + return; + elseif ~strcmp(ndata{1}.header.chantype, 'marker') + warning('ID:invalid_option', ... + ['Channel %i is no marker channel. ',... + 'The first marker channel in the file is used instead'],... + options.marker_chan_num); + [sts_load_data, ~, ~] = pspm_load_data(model.datafile{iFile}, 'marker'); + if sts_load_data == -1 + warning('ID:invalid_input', 'Could not load data'); + return; + end end - % use first marker channel - events{iFile} = data{1}.data(:); + end + % use first marker channel + events{iFile} = data{1}.data(:); - for iEpoch = 1:size(epochs{iFile}, 1) - if iEpoch > 1, fprintf('\n\t\t\t'); end - fprintf('epoch %01.0f ...', iEpoch); - for k = 1:numel(method) - fprintf('%s ', method{k}); - switch model.timeunits - case 'seconds' - win = round(epochs{iFile}(iEpoch, :) * sr(datatype(k))); - case 'samples' - win = round(epochs{iFile}(iEpoch, :) * sr(datatype(k)) / sr(1)); - case 'markers' - win = round(events(epochs{1}(iEpoch, :)) * sr(datatype(k))); - case 'whole' - win = [1 numel(y{datatype(k)})]; - end - if any(win > numel(y{datatype(k)}) + 1) || any(win < 0) - warning('\nEpoch %2.0f outside of file %s ...', iEpoch, model.modelfile{iFile}); - else - % correct issues with using 'round' - win(1) = max(win(1), 1); - win(2) = min(win(2), numel(y{datatype(k)})); - end - % 3.6.1 collect information - sf.model{k}(iEpoch).modeltype = method{k}; - sf.model{k}(iEpoch).boundaries = squeeze(epochs{iFile}(iEpoch, :)); - sf.model{k}(iEpoch).timeunits = model.timeunits; - sf.model{k}(iEpoch).samples = win; - sf.model{k}(iEpoch).sr = sr(datatype(k)); - % - escr = y{datatype(k)}(win(1):win(end)); - sf.model{k}(iEpoch).data = escr; - % 3.6.2 do the analysis and collect results - model_analysis = struct('scr', escr, 'sr', sr(datatype(k))); - invrs = fhandle{k}(model_analysis, options); - if any(strcmpi(method{k}, {'dcm', 'mp'})) - sf.model{k}(iEpoch).inv = invrs; - sf.stats(iEpoch, k) = invrs.f; - else - sf.model{k}(iEpoch).stats = invrs; - sf.stats(iEpoch, k) = invrs; - end - end - sf.trlnames{iEpoch} = sprintf('Epoch #%d', iEpoch); + for iEpoch = 1:size(epochs{iFile}, 1) + if iEpoch > 1, fprintf('\n\t\t\t'); end + fprintf('epoch %01.0f ...', iEpoch); + for k = 1:numel(method) + fprintf('%s ', method{k}); + switch model.timeunits + case 'seconds' + win = round(epochs{iFile}(iEpoch, :) * sr(datatype(k))); + case 'samples' + win = round(epochs{iFile}(iEpoch, :) * sr(datatype(k)) / sr(1)); + case 'markers' + win = round(events(epochs{1}(iEpoch, :)) * sr(datatype(k))); + case 'whole' + win = [1 numel(y{datatype(k)})]; + end + if any(win > numel(y{datatype(k)}) + 1) || any(win < 0) + warning('\nEpoch %2.0f outside of file %s ...', iEpoch, model.modelfile{iFile}); + else + % correct issues with using 'round' + win(1) = max(win(1), 1); + win(2) = min(win(2), numel(y{datatype(k)})); + end + % 3.6.1 collect information + sf.model{k}(iEpoch).modeltype = method{k}; + sf.model{k}(iEpoch).boundaries = squeeze(epochs{iFile}(iEpoch, :)); + sf.model{k}(iEpoch).timeunits = model.timeunits; + sf.model{k}(iEpoch).samples = win; + sf.model{k}(iEpoch).sr = sr(datatype(k)); + % + escr = y{datatype(k)}(win(1):win(end)); + sf.model{k}(iEpoch).data = escr; + % 3.6.2 do the analysis and collect results + model_analysis = struct('scr', escr, 'sr', sr(datatype(k))); + invrs = fhandle{k}(model_analysis, options); + if any(strcmpi(method{k}, {'dcm', 'mp'})) + sf.model{k}(iEpoch).inv = invrs; + sf.stats(iEpoch, k) = invrs.f; + else + sf.model{k}(iEpoch).stats = invrs; + sf.stats(iEpoch, k) = invrs; + end end - sf.names = method(:); - sf.infos.date = date; - sf.infos.file = model.modelfile{iFile}; - sf.modelfile = model.modelfile{iFile}; - sf.data = y; - if exist('events','var'), sf.events = events; end - sf.input = model; - sf.options = options; - sf.modeltype = 'sf'; - sf.modality = settings.modalities.sf; - save(model.modelfile{iFile}, 'sf'); - outfile = model.modelfile(iFile); - fprintf('\n'); + sf.trlnames{iEpoch} = sprintf('Epoch #%d', iEpoch); + end + sf.names = method(:); + sf.infos.date = date; + sf.infos.file = model.modelfile{iFile}; + sf.modelfile = model.modelfile{iFile}; + sf.data = y; + if exist('events','var'), sf.events = events; end + sf.input = model; + sf.options = options; + sf.modeltype = 'sf'; + sf.modality = settings.modalities.sf; + save(model.modelfile{iFile}, 'sf'); + outfile = model.modelfile(iFile); + fprintf('\n'); end sts = 1; switch nargout - case 1 - varargout{1} = outfile; - case 2 - varargout{1} = sts; - varargout{2} = outfile; + case 1 + varargout{1} = outfile; + case 2 + varargout{1} = sts; + varargout{2} = outfile; end return From 2cd0879de841e5340dd63aaeda2a44e523d08eb5 Mon Sep 17 00:00:00 2001 From: "teddy.chao" Date: Fri, 11 Aug 2023 17:49:48 +0100 Subject: [PATCH 25/26] Update pspm_cfg_sf.m --- src/pspm_cfg/pspm_cfg_sf.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pspm_cfg/pspm_cfg_sf.m b/src/pspm_cfg/pspm_cfg_sf.m index 65d39d546..f6b89ddf6 100644 --- a/src/pspm_cfg/pspm_cfg_sf.m +++ b/src/pspm_cfg/pspm_cfg_sf.m @@ -193,7 +193,7 @@ mrk_chan.name = 'Marker Channel'; mrk_chan.tag = 'mrk_chan'; mrk_chan.strtype = 'i'; -mrk_chan.val = {0}; +mrk_chan.val = {1}; mrk_chan.num = [1 1]; mrk_chan.help = {['Indicate the marker channel. By default the first marker channel is ' ... 'assumed to contain the relevant markers.'], ['Markers are only used if you have ' ... From c2fd1aeaab1b1b63056451f5686129f08faf5217 Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Fri, 11 Aug 2023 21:53:21 +0200 Subject: [PATCH 26/26] fix bug in cfg_sf --- src/pspm_cfg/pspm_cfg_glm.m | 4 ++-- src/pspm_cfg/pspm_cfg_sf.m | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pspm_cfg/pspm_cfg_glm.m b/src/pspm_cfg/pspm_cfg_glm.m index b16fc5128..3c0652a6f 100644 --- a/src/pspm_cfg/pspm_cfg_glm.m +++ b/src/pspm_cfg/pspm_cfg_glm.m @@ -309,9 +309,9 @@ mrk_chan.strtype = 'i'; mrk_chan.val = {0}; mrk_chan.num = [1 1]; -mrk_chan.help = {['Indicate the marker channel. By default the first marker channel is ' ... +mrk_chan.help = {['Indicate the marker channel. By default (value 0) the first marker channel is ' ... 'assumed to contain the relevant markers.'], ['Markers are only used if you have ' ... - 'specified the time units as �markers�.']}; + 'specified the time units as "markers".']}; % Timeunits seconds = cfg_const; diff --git a/src/pspm_cfg/pspm_cfg_sf.m b/src/pspm_cfg/pspm_cfg_sf.m index f6b89ddf6..8eb03eec4 100644 --- a/src/pspm_cfg/pspm_cfg_sf.m +++ b/src/pspm_cfg/pspm_cfg_sf.m @@ -193,9 +193,9 @@ mrk_chan.name = 'Marker Channel'; mrk_chan.tag = 'mrk_chan'; mrk_chan.strtype = 'i'; -mrk_chan.val = {1}; +mrk_chan.val = {0}; mrk_chan.num = [1 1]; -mrk_chan.help = {['Indicate the marker channel. By default the first marker channel is ' ... +mrk_chan.help = {['Indicate the marker channel. By default (value 0) the first marker channel is ' ... 'assumed to contain the relevant markers.'], ['Markers are only used if you have ' ... 'specified the time units as "markers".']};