Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix issue 499 #513

Merged
merged 13 commits into from
Aug 14, 2023
4 changes: 2 additions & 2 deletions src/pspm_cfg/pspm_cfg_run_sf.m
Original file line number Diff line number Diff line change
Expand Up @@ -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})
model.missing = job.missing.missingepoch_include.missingepoch_file{1};
end
end
options.dispwin = job.dispwin;
Expand Down
22 changes: 15 additions & 7 deletions src/pspm_cfg/pspm_cfg_sf.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 1 addition & 13 deletions src/pspm_dcm.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
32 changes: 28 additions & 4 deletions src/pspm_get_timing.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -82,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;
Expand All @@ -91,7 +92,7 @@


switch model
case {'onsets', 'epochs'}
case {'onsets', 'epochs', 'missing'}
if nargin < 3
warning('ID:invalid_input', 'Time units unspecified'); return;
else
Expand Down Expand Up @@ -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 --
Expand Down Expand Up @@ -380,7 +381,6 @@
if filewarning
warning('File %s is not a valid epochs or onsets file', ...
intiming);

return;
end
else
Expand All @@ -393,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;
Expand All @@ -408,6 +412,26 @@
'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
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);
overlapindx(k - 1) = 1;
end
end
missepochs(logical(overlapindx), :) = [];
end
outtiming = missepochs;

% Event information for DCM
% ------------------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion src/pspm_glm.m
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,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
Expand Down
9 changes: 4 additions & 5 deletions src/pspm_interp1.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
60 changes: 32 additions & 28 deletions src/pspm_sf.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -225,21 +232,11 @@
data{end}.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');
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
Expand All @@ -260,9 +257,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)
Expand All @@ -276,17 +273,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, :));
Expand All @@ -296,23 +299,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;
sf.stats(iEpoch, k) = invrs;
end
end
Expand Down
9 changes: 5 additions & 4 deletions src/pspm_sf_dcm.m
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down