From 5e7f250c66aab90a16a8a496ef325c530fb21253 Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Fri, 11 Aug 2023 22:37:23 +0200 Subject: [PATCH 1/8] fix issue 499 --- src/pspm_dcm.m | 14 +------------- src/pspm_get_timing.m | 21 ++++++++++++++++++++- src/pspm_glm.m | 2 +- src/pspm_sf.m | 15 +-------------- 4 files changed, 23 insertions(+), 29 deletions(-) diff --git a/src/pspm_dcm.m b/src/pspm_dcm.m index 732368585..8050dedc5 100644 --- a/src/pspm_dcm.m +++ b/src/pspm_dcm.m @@ -339,20 +339,8 @@ % load and check existing missing data (if defined) if ~isempty(model.missing{iSn}) - [~, missing{iSn}] = pspm_get_timing('epochs', ... + [~, missing{iSn}] = pspm_get_timing('missing', ... model.missing{iSn}, 'seconds'); - % sort missing epochs - if size(missing{iSn}, 1) > 0 - [~, sortindx] = sort(missing{iSn}(:, 1)); - missing{iSn} = missing{iSn}(sortindx,:); - % check for overlap and merge - for k = 2:size(missing{iSn}, 1) - if missing{iSn}(k, 1) <= missing{iSn}(k - 1, 2) - missing{iSn}(k, 1) = missing{iSn}(k - 1, 1); - missing{iSn}(k - 1, :) = []; - end - end - end else missing{iSn} = []; end diff --git a/src/pspm_get_timing.m b/src/pspm_get_timing.m index 9571ee819..896ff7aee 100644 --- a/src/pspm_get_timing.m +++ b/src/pspm_get_timing.m @@ -7,6 +7,7 @@ % [sts, multi] = pspm_get_timing('onsets', intiming, timeunits) % [sts, epochs] = pspm_get_timing('epochs', epochs, timeunits) % [sts, events] = pspm_get_timing('events', events) +% [sts, epochs] = pspm_get_timing('missing', epochs, timeunits) % for recursive calls also: % [sts, epochs] = pspm_get_timing('file', filename) % ● Arguments @@ -352,7 +353,7 @@ end - % Epoch information for SF and GLM (model.missing) + % Epoch information for SF and recursive call from option "missing" % ------------------------------------------------------------------------ case 'epochs' % get epoch information from file or from input -- @@ -408,6 +409,24 @@ 'time units are ''%s'''], timeunits); return; end + % Missing epoch information for GLM and DCM + % ------------------------------------------------------------------------ + case 'missing' + [sts, missepochs] = pspm_get_timing('epochs', intiming, timeunits); + if sts < 1, return; end + % sort & merge missing epochs + if size(missepochs, 1) > 0 + [~, sortindx] = sort(missepochs(:, 1)); + missepochs = missepochs(sortindx,:); + % check for overlap and merge + for k = 2:size(missepochs, 1) + if missepochs(k, 1) <= missepochs(k - 1, 2) + missepochs(k, 1) = missepochs(k - 1, 1); + missepochs(k - 1, :) = []; + end + end + end + outtiming = missepochs; % Event information for DCM % ------------------------------------------------------------------------ diff --git a/src/pspm_glm.m b/src/pspm_glm.m index 3cf10d500..4728731bb 100644 --- a/src/pspm_glm.m +++ b/src/pspm_glm.m @@ -389,7 +389,7 @@ if isempty(model.missing{iSn}) sts = 1; missing{iSn} = []; else - [sts, missing{iSn}] = pspm_get_timing('epochs', model.missing{iSn}, 'seconds'); + [sts, missing{iSn}] = pspm_get_timing('missing', model.missing{iSn}, 'seconds'); end if sts == -1, warning('ID:invalid_input',... 'Failed to call pspm_get_timing'); return; end diff --git a/src/pspm_sf.m b/src/pspm_sf.m index 48c7d426f..9cf580075 100644 --- a/src/pspm_sf.m +++ b/src/pspm_sf.m @@ -223,21 +223,8 @@ data{1}.header.units); end % 3.5 Get missing epochs -- - % 3.5.1 Load missing epochs -- if ~isempty(model.missing{iFile}) - [~, missing{iFile}] = pspm_get_timing('epochs', model.missing{iFile}, 'seconds'); - % 3.5.2 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{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, :) = []; - end - end - end + [~, missing{iFile}] = pspm_get_timing('missing', model.missing{iFile}, 'seconds'); else missing{iFile} = []; end From ef4b2a43dd1c278e171861db0be1dc50c03c84d4 Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Fri, 11 Aug 2023 23:05:35 +0200 Subject: [PATCH 2/8] bugfix --- src/pspm_get_timing.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pspm_get_timing.m b/src/pspm_get_timing.m index 896ff7aee..9fbce3e8d 100644 --- a/src/pspm_get_timing.m +++ b/src/pspm_get_timing.m @@ -83,7 +83,7 @@ intiming = varargin{2}; end -if ~ismember(model, {'onsets', 'epochs', 'events', 'file'}) +if ~ismember(model, {'onsets', 'epochs', 'missing', 'events', 'file'}) warning('ID:invalid_input', 'Invalid input. I don''t know what to do.'); return; @@ -92,7 +92,7 @@ switch model - case {'onsets', 'epochs'} + case {'onsets', 'epochs', 'missing'} if nargin < 3 warning('ID:invalid_input', 'Time units unspecified'); return; else From d83eefc4132e8f942c48da4afa13e5c94baa5004 Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Sat, 12 Aug 2023 09:02:40 +0200 Subject: [PATCH 3/8] bugfix and additional check --- src/pspm_get_timing.m | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/pspm_get_timing.m b/src/pspm_get_timing.m index 9fbce3e8d..93a59a985 100644 --- a/src/pspm_get_timing.m +++ b/src/pspm_get_timing.m @@ -381,7 +381,6 @@ if filewarning warning('File %s is not a valid epochs or onsets file', ... intiming); - return; end else @@ -394,6 +393,10 @@ if size(outtiming, 2) ~= 2 warning(['Epochs must be specified by a e x 2 vector', ... 'of onset/offsets.']); return; + else + if any(diff(outtiming, [], 2) < 0) + warning('Offsets must be larger than onsets.'); return; + end end else warning('Unknown epoch definition format.'); return; @@ -419,12 +422,14 @@ [~, sortindx] = sort(missepochs(:, 1)); missepochs = missepochs(sortindx,:); % check for overlap and merge + overlapindx = zeros(size(missepochs, 1), 1); for k = 2:size(missepochs, 1) if missepochs(k, 1) <= missepochs(k - 1, 2) missepochs(k, 1) = missepochs(k - 1, 1); - missepochs(k - 1, :) = []; + overlapindx(k - 1) = 1; end end + missepochs(logical(overlapindx), :) = []; end outtiming = missepochs; From bf51ffb50ed727a21a2b4392148251e08dfe49d4 Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Sun, 13 Aug 2023 16:50:26 +0200 Subject: [PATCH 4/8] bugfixes --- src/pspm_interp1.m | 9 ++++----- src/pspm_sf.m | 40 +++++++++++++++++++++++++--------------- src/pspm_sf_dcm.m | 9 +++++---- 3 files changed, 34 insertions(+), 24 deletions(-) diff --git a/src/pspm_interp1.m b/src/pspm_interp1.m index 86b1e5aae..9e9154844 100644 --- a/src/pspm_interp1.m +++ b/src/pspm_interp1.m @@ -69,11 +69,10 @@ end X_body = X(index_non_nan_full(1):index_non_nan_full(end)); % processing body -index = 1:length(X_body); -index_nan = index(isnan(X_body) | index_missing(index_non_nan_full(1):index_non_nan_full(end))) ; -index_non_nan = 1 - index_nan; -if ~isempty(index_nan) - X_body_interp = interp1(index_non_nan,X_body(index_non_nan),index_nan); +index_nan = zeros(size(X_body)); +index_nan(isnan(X_body) | index_missing(index_non_nan_full(1):index_non_nan_full(end))) = 1; +if any(index_nan) + X_body_interp = interp1(find(~index_nan),X_body(~index_nan), (1:numel(X_body))'); else X_body_interp = X_body; end diff --git a/src/pspm_sf.m b/src/pspm_sf.m index beb3cec24..3cf6e4f42 100644 --- a/src/pspm_sf.m +++ b/src/pspm_sf.m @@ -227,6 +227,9 @@ % 3.5 Get missing epochs -- if ~isempty(model.missing{iFile}) [~, missing{iFile}] = pspm_get_timing('missing', model.missing{iFile}, 'seconds'); + model.missing_data = zeros(size(y{2})); + missing_index = pspm_time2index(missing{iFile}, sr(datatype(k))); + model.missing_data((missing_index(:,1)+1):(missing_index(:,2)+1)) = 1; else missing{iFile} = []; end @@ -247,9 +250,9 @@ return; end end + % use first marker channel + events{iFile} = ndata{1}.data(:); end - % use first marker channel - events{iFile} = data{1}.data(:); for iEpoch = 1:size(epochs{iFile}, 1) @@ -263,17 +266,23 @@ case 'samples' win = round(epochs{iFile}(iEpoch, :) * sr(datatype(k)) / sr(1)); case 'markers' - win = round(events(epochs{iFile}(iEpoch, :)) * sr(datatype(k))); + win = round(events{iFile}(epochs{iFile}(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}); + inv_flag = 0; else + inv_flag = 1; % correct issues with using 'round' win(1) = max(win(1), 1); win(2) = min(win(2), numel(y{datatype(k)})); end + if diff(win) < 4 + warning('\nEpoch %2.0f contains insufficient data ...', iEpoch); + inv_flag = 0; + end % 3.6.1 collect information -- sf.model{k}(iEpoch).modeltype = method{k}; sf.model{k}(iEpoch).boundaries = squeeze(epochs{iFile}(iEpoch, :)); @@ -283,23 +292,24 @@ % escr = y{datatype(k)}(win(1):win(end)); sf.model{k}(iEpoch).data = escr; - if any(missing{iFile}) - model.missing_data = zeros(size(escr)); - missing_index = pspm_time2index(missing, sr(datatype(k))); - model.missing_data((missing_index{iFile}(:,1)+1):(missing_index{iFile}(:,2)+1)) = 1; - end + % 3.6.2 do the analysis and collect results -- - if any(missing{iFile}) - model_analysis = struct('scr', escr, 'sr', sr(datatype(k)), 'missing_data', model.missing_data); + if ~isempty(model.missing{iFile}) + model_analysis = struct('scr', escr, 'sr', sr(datatype(k)), 'missing_data', model.missing_data(win(1):win(end))); else model_analysis = struct('scr', escr, 'sr', sr(datatype(k))); end - invrs = fhandle{k}(model_analysis, options); - if any(strcmpi(method{k}, {'dcm', 'mp'})) - sf.model{k}(iEpoch).inv = invrs; + if inv_flag ~= 0 + invrs = fhandle{k}(model_analysis, options); + sf.model{k}(iEpoch).inv = invrs; + else + sf.model{k}(iEpoch).inv = []; + end + if inv_flag == 0 + sf.stats(iEpoch, k) = NaN; + elseif any(strcmpi(method{k}, {'dcm', 'mp'})) sf.stats(iEpoch, k) = invrs.f; - else - sf.model{k}(iEpoch).stats = invrs; + else sf.stats(iEpoch, k) = invrs; end end diff --git a/src/pspm_sf_dcm.m b/src/pspm_sf_dcm.m index 2df113ac7..1d27f2076 100644 --- a/src/pspm_sf_dcm.m +++ b/src/pspm_sf_dcm.m @@ -150,12 +150,13 @@ flag_missing_too_long = 0; if any(diff(miss_epoch, 1, 2)/model.sr > 0) if any(diff(miss_epoch, 1, 2)/model.sr > options.missingthresh) - warning_message = ['Imported data includes too long miss epoches (over ',... - num2str(options.missingthresh), 's), thus estimation has been skipped.']; + warning_message = ['Epoch includes missing data of more than ',... + num2str(options.missingthresh), ' s, thus estimation has been skipped. ', ... + 'Please adjust options.missingthresh to proceed if you wish.']; flag_missing_too_long = 1; else - warning_message = ['Imported data includes miss epoches (over ',... - num2str(options.missingthresh), 's), but the trial has been allowed. ',... + warning_message = ['Epoch includes missing data of less than ',... + num2str(options.missingthresh), ' s, hence estimation is proceeding. ', ... 'Please adjust options.missingthresh to skip if you wish.']; end warning('ID:missing_data', warning_message); From ac8fa2535c53c6f1a534d694dddfeffff8fe03e4 Mon Sep 17 00:00:00 2001 From: "dadi.zhao" Date: Sun, 13 Aug 2023 20:32:11 +0100 Subject: [PATCH 5/8] Update pspm_sf.m --- src/pspm_sf.m | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/pspm_sf.m b/src/pspm_sf.m index 3cf6e4f42..945733a0e 100644 --- a/src/pspm_sf.m +++ b/src/pspm_sf.m @@ -76,6 +76,13 @@ end outfile = []; sts = -1; +switch nargout + case 1 + varargout{1} = outfile; + case 2 + varargout{1} = sts; + varargout{2} = outfile; +end %% 2 Check input % 2.1 Check missing input -- if nargin<1 @@ -203,6 +210,7 @@ fprintf('SF analysis: %s ...', model.datafile{iFile}); % 3.2 Check whether model file exists -- if ~pspm_overwrite(model.modelfile, options) + warning('results are not saved.'); return end % 3.3 get and filter data -- From 60afe5ecfcf2998f3cf303e5de65380065a1946f Mon Sep 17 00:00:00 2001 From: "dadi.zhao" Date: Mon, 14 Aug 2023 02:27:28 +0100 Subject: [PATCH 6/8] update gui --- src/pspm_cfg/pspm_cfg_run_sf.m | 4 ++-- src/pspm_cfg/pspm_cfg_sf.m | 22 +++++++++++++++------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/pspm_cfg/pspm_cfg_run_sf.m b/src/pspm_cfg/pspm_cfg_run_sf.m index 9638d4f9c..e8b14b6b0 100644 --- a/src/pspm_cfg/pspm_cfg_run_sf.m +++ b/src/pspm_cfg/pspm_cfg_run_sf.m @@ -78,8 +78,8 @@ options.fresp = job.fresp; end if ~isempty(job.missing) - if ischar(job.missing.missingdata) - options.missing = job.missing; + if ischar(job.missing.missingepoch_include.missingepoch_file{1}) + options.missing = job.missing.missingepoch_include.missingepoch_file{1}; end end options.dispwin = job.dispwin; diff --git a/src/pspm_cfg/pspm_cfg_sf.m b/src/pspm_cfg/pspm_cfg_sf.m index 8eb03eec4..f3cf5c565 100644 --- a/src/pspm_cfg/pspm_cfg_sf.m +++ b/src/pspm_cfg/pspm_cfg_sf.m @@ -296,25 +296,33 @@ -missingepoch_none = cfg_const; -missingepoch_none.name = 'Not added'; -missingepoch_none.tag = 'missingdata'; -missingepoch_none.val = {0}; -missingepoch_none.help = {'Do not add missing epochs.'}; + missingepoch_file = cfg_files; missingepoch_file.name = 'Missing epoch file'; -missingepoch_file.tag = 'missingdata'; +missingepoch_file.tag = 'missingepoch_file'; missingepoch_file.num = [1 1]; missingepoch_file.filter = '.*\.(mat|MAT)$'; missingepoch_file.help = {['Missing (e.g. artefact) epochs in the data file, where ',... 'data must always be specified in seconds.']}; +missingepoch_none = cfg_const; +missingepoch_none.name = 'Do not add'; +missingepoch_none.tag = 'missingepoch_none'; +missingepoch_none.val = {0}; +missingepoch_none.help = {'Do not add missing epochs.'}; + +missingepoch_include = cfg_branch; +missingepoch_include.name = 'Add'; +missingepoch_include.tag = 'missingepoch_include'; +missingepoch_include.val = {missingepoch_file}; +missingepoch_include.help = {'Add missing epoch file'}; + missing = cfg_choice; missing.name = 'Missing Epoch Settings'; missing.tag = 'missing'; missing.val = {missingepoch_none}; -missing.values = {missingepoch_none, missingepoch_file}; +missing.values = {missingepoch_none, missingepoch_include}; missing.help = {'Specify whether you would like to include missing epochs.'}; % Show figures From 66ed766cc79e47b0e0afea2a6ed52eece03d1062 Mon Sep 17 00:00:00 2001 From: "teddy.chao" Date: Mon, 14 Aug 2023 15:40:39 +0100 Subject: [PATCH 7/8] Update pspm_sf.m --- src/pspm_sf.m | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pspm_sf.m b/src/pspm_sf.m index 945733a0e..eca593009 100644 --- a/src/pspm_sf.m +++ b/src/pspm_sf.m @@ -210,7 +210,6 @@ fprintf('SF analysis: %s ...', model.datafile{iFile}); % 3.2 Check whether model file exists -- if ~pspm_overwrite(model.modelfile, options) - warning('results are not saved.'); return end % 3.3 get and filter data -- @@ -300,7 +299,7 @@ % escr = y{datatype(k)}(win(1):win(end)); sf.model{k}(iEpoch).data = escr; - + % 3.6.2 do the analysis and collect results -- if ~isempty(model.missing{iFile}) model_analysis = struct('scr', escr, 'sr', sr(datatype(k)), 'missing_data', model.missing_data(win(1):win(end))); From de01f4956ae89214c1784616294872e39ec73ff9 Mon Sep 17 00:00:00 2001 From: "dadi.zhao" Date: Mon, 14 Aug 2023 16:33:42 +0100 Subject: [PATCH 8/8] correct missing assigning --- src/pspm_cfg/pspm_cfg_run_sf.m | 2 +- src/pspm_sf.m | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/pspm_cfg/pspm_cfg_run_sf.m b/src/pspm_cfg/pspm_cfg_run_sf.m index e8b14b6b0..0d9727b50 100644 --- a/src/pspm_cfg/pspm_cfg_run_sf.m +++ b/src/pspm_cfg/pspm_cfg_run_sf.m @@ -79,7 +79,7 @@ end if ~isempty(job.missing) if ischar(job.missing.missingepoch_include.missingepoch_file{1}) - options.missing = job.missing.missingepoch_include.missingepoch_file{1}; + model.missing = job.missing.missingepoch_include.missingepoch_file{1}; end end options.dispwin = job.dispwin; diff --git a/src/pspm_sf.m b/src/pspm_sf.m index eca593009..b725f6713 100644 --- a/src/pspm_sf.m +++ b/src/pspm_sf.m @@ -306,17 +306,17 @@ else model_analysis = struct('scr', escr, 'sr', sr(datatype(k))); end - if inv_flag ~= 0 - invrs = fhandle{k}(model_analysis, options); - sf.model{k}(iEpoch).inv = invrs; - else - sf.model{k}(iEpoch).inv = []; - end - if inv_flag == 0 - sf.stats(iEpoch, k) = NaN; - elseif any(strcmpi(method{k}, {'dcm', 'mp'})) + if inv_flag ~= 0 + invrs = fhandle{k}(model_analysis, options); + sf.model{k}(iEpoch).inv = invrs; + else + sf.model{k}(iEpoch).inv = []; + end + if inv_flag == 0 + sf.stats(iEpoch, k) = NaN; + elseif any(strcmpi(method{k}, {'dcm', 'mp'})) sf.stats(iEpoch, k) = invrs.f; - else + else sf.stats(iEpoch, k) = invrs; end end