Skip to content

Commit

Permalink
Merge branch 'develop' into fix-issue-499
Browse files Browse the repository at this point in the history
  • Loading branch information
dominikbach authored Aug 11, 2023
2 parents ef4b2a4 + f207530 commit 4d9b7fa
Show file tree
Hide file tree
Showing 8 changed files with 343 additions and 351 deletions.
4 changes: 2 additions & 2 deletions src/pspm_cfg/pspm_cfg_glm.m
Original file line number Diff line number Diff line change
Expand Up @@ -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 last 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;
Expand Down
4 changes: 2 additions & 2 deletions src/pspm_cfg/pspm_cfg_sf.m
Original file line number Diff line number Diff line change
Expand Up @@ -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".']};

Expand Down
22 changes: 13 additions & 9 deletions src/pspm_dcm.m
Original file line number Diff line number Diff line change
Expand Up @@ -344,12 +344,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{iSn};

% 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);
Expand All @@ -376,19 +380,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

Expand Down Expand Up @@ -424,17 +428,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
Expand Down
55 changes: 13 additions & 42 deletions src/pspm_glm.m
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -247,43 +248,20 @@
fprintf('Getting data ...');
nFile = numel(model.datafile);
for iFile = 1:nFile
[sts, ~, data] = pspm_load_data(model.datafile{iFile}, model.channel);
if sts < 1
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
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)
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
end
% 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;
end
Expand All @@ -304,7 +282,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)
Expand Down Expand Up @@ -344,7 +322,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
Expand Down Expand Up @@ -697,7 +676,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));
Expand Down Expand Up @@ -865,12 +844,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
2 changes: 1 addition & 1 deletion src/pspm_merge.m
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
29 changes: 18 additions & 11 deletions src/pspm_options.m
Original file line number Diff line number Diff line change
Expand Up @@ -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 --
Expand Down Expand Up @@ -271,7 +280,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', 'first', '*Num*Char' );
if ~isfield(options, 'exclude_missing')
options.exclude_missing = struct('segment_length',-1,'cutoff',0);
else
Expand Down Expand Up @@ -303,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'
Expand Down Expand Up @@ -410,8 +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,'missingthresh', 2, '>', 0 );
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 );
Expand Down Expand Up @@ -463,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 --
Expand Down Expand Up @@ -863,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
Expand Down Expand Up @@ -974,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') && ...
Expand Down
53 changes: 33 additions & 20 deletions src/pspm_sf.m
Original file line number Diff line number Diff line change
Expand Up @@ -197,9 +197,9 @@
return
end
%% 3 Get data
missing = cell(size(model.missing));
for iFile = 1:numel(model.datafile)
% 3.1 User output --
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)
Expand All @@ -208,19 +208,21 @@
% 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;
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{1}.data, model.filter);
[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{1}.header.units, 'uS') && any(strcmpi('dcm', method))
% 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{1}.header.units);
data{end}.header.units);
end
% 3.5 Get missing epochs --
if ~isempty(model.missing{iFile})
Expand All @@ -230,15 +232,26 @@
end
% 3.6 Get marker data --
if any(strcmp(model.timeunits, {'marker', 'markers'}))
if options.marker_chan_num
[nsts, ~, ndata] = pspm_load_data(model.datafile{iFile}, options.marker_chan_num);
if nsts == -1; warning('ID:invalid_input', 'Could not load data'); return; end
else
[nsts, ~, ndata] = pspm_load_data(model.datafile{iFile}, 'marker');
if nsts == -1; warning('ID:invalid_input', 'Could not load data'); return; end
[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
events = ndata{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);
Expand All @@ -252,14 +265,14 @@
case 'markers'
win = round(events(epochs{iFile}(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.1 collect information --
sf.model{k}(iEpoch).modeltype = method{k};
Expand All @@ -268,7 +281,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;
if any(missing{iFile})
model.missing_data = zeros(size(escr));
Expand Down Expand Up @@ -296,7 +309,7 @@
sf.infos.date = date;
sf.infos.file = model.modelfile{iFile};
sf.modelfile = model.modelfile{iFile};
sf.data = Y;
sf.data = y;
if exist('events','var'), sf.events = events; end
sf.input = model;
sf.options = options;
Expand Down
Loading

0 comments on commit 4d9b7fa

Please sign in to comment.