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

[ENH] make addConfoundsToDesignMatrix a method of BidsModel #1294

Merged
merged 2 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

* [ENH] make `addConfoundsToDesignMatrix` a method of `BidsModel` #1294 by @Remi-Gau
* [ENH] add Apptainer definition #1254 by @Remi-Gau and @monique2208
* [ENH] allow to copy anat only on raw datasets #1181 by @Remi-Gau
* [ENH] add option to concatenate runs at subject level to facilite running PPI analysis #1133 by @Remi-Gau
Expand Down
1 change: 0 additions & 1 deletion docs/source/API.rst
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,6 @@ bids

bids_model
==========
.. autofunction:: src.bids_model.addConfoundsToDesignMatrix
.. autofunction:: src.bids_model.checkContrast
.. autofunction:: src.bids_model.checkGroupBy
.. autofunction:: src.bids_model.createDefaultStatsModel
Expand Down
218 changes: 218 additions & 0 deletions src/bids_model/BidsModel.m
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,156 @@

end

function obj = addConfoundsToDesignMatrix(obj, varargin)
%
% Add some typical confounds to the design matrix of bids stat model.
%
% This will update the design matrix of the root node of the model.
%
% Similar to the :func:`nilearn.interfaces.fmriprep.load_confounds`
%
% USAGE::
%
% bm = bm.addConfoundsToDesignMatrix('strategy', strategy);
%
%
% :param bm: bids stats model.
% :type bm: :obj:`BidsModel` instance or path
% to a ``_smdl.json`` file
%
% :type strategy: struct
% :param strategy: structure describing the confoudd strategy.
%
% The structure must have the following field:
%
% - ``strategy``: cell array of char
% with the strategies to apply.
%
% The structure may have the following field:
%
% - ``motion``: motion regressors strategy
% - ``scrub``: scrubbing strategy
% - ``wm_csf``: white matter
% and cerebrospinal fluid regressors strategy
% - ``non_steady_state``:
% non steady state regressors strategy
%
% See the nilearn documentation (mentioned above)
% for more information on the possible values
% those strategies can take.
%
% :type updateName: logical
% :param updateName: Append the name of the root node
% with a string describing the counfounds added.
%
% ``rp-{motion}_scrub-{scrub}_tissue-{wm_csf}_nsso-{non_steady_state}``
%
% default = ``false``
%
%
% :rtype: :obj:`BidsModel` instance
% :return: bids stats model with the confounds added.
%
% EXAMPLE:
%
% .. code-block:: matlab
%
%
% strategy.strategies = {'motion', 'wm_csf', 'scrub', 'non_steady_state'};
% strategy.motion = 'full';
% strategy.scrub = true;
% strategy.non_steady_state = true;
%
% bm = bm.addConfoundsToDesignMatrix('strategy', strategy);
%
%

% (C) Copyright 2023 bidspm developers

args = inputParser;
args.CaseSensitive = false;
args.KeepUnmatched = false;
args.FunctionName = 'addConfoundsToDesignMatrix';

addParameter(args, 'strategy', defaultStrategy(), @isstruct);
addParameter(args, 'updateName', false, @islogical);

parse(args, varargin{:});

strategy = args.Results.strategy;
strategy = setFieldsStrategy(strategy);

[~, name] = obj.get_root_node();
[~, idx] = obj.get_nodes('Name', name);
designMatrix = obj.get_design_matrix('Name', name);

strategiesToApply = strategy.strategies;
for i = 1:numel(strategiesToApply)

switch strategiesToApply{i}

case 'motion'
switch strategy.motion{1}
case 'none'
case 'basic'
designMatrix{end + 1} = 'rot_?'; %#ok<*AGROW>
designMatrix{end + 1} = 'trans_?';
case {'power2', 'derivatives' }
notImplemented(mfilename(), ...

Check warning on line 444 in src/bids_model/BidsModel.m

View check run for this annotation

Codecov / codecov/patch

src/bids_model/BidsModel.m#L444

Added line #L444 was not covered by tests
sprintf('motion "%s" not implemented.', strategy.motion));
case 'full'
designMatrix{end + 1} = 'rot_*';
designMatrix{end + 1} = 'trans_*';
end

case 'non_steady_state'
if strategy.non_steady_state{1}
designMatrix{end + 1} = 'non_steady_state_outlier*';
end

case 'scrub'
if strategy.scrub{1}
designMatrix{end + 1} = 'motion_outlier*';
end

case 'wm_csf'
switch strategy.wm_csf{1}
case 'none'
case 'basic'
designMatrix{end + 1} = 'csf';
designMatrix{end + 1} = 'white';
case 'full'
designMatrix{end + 1} = 'csf_*';
designMatrix{end + 1} = 'white_*';
otherwise
notImplemented(mfilename(), ...

Check warning on line 471 in src/bids_model/BidsModel.m

View check run for this annotation

Codecov / codecov/patch

src/bids_model/BidsModel.m#L471

Added line #L471 was not covered by tests
sprintf('wm_csf "%s" not implemented.', strategiesToApply{i}));
end

case {'global_signal', 'compcorstr', 'n_compcorstr'}
notImplemented(mfilename(), ...
sprintf(['Strategey "%s" not implemented.\n', ...
'Supported strategies are:%s'], ...
strategiesToApply{i}, ...
bids.internal.create_unordered_list(supportedStrategies())));
otherwise

Check warning on line 481 in src/bids_model/BidsModel.m

View check run for this annotation

Codecov / codecov/patch

src/bids_model/BidsModel.m#L481

Added line #L481 was not covered by tests
logger('WARNING', sprintf('Unknown strategey: "%s".', ...
strategiesToApply{i}), ...
'filename', mfilename(), ...
'id', 'unknownStrategy');
end
end

designMatrix = cleanDesignMatrix(designMatrix);

obj.Nodes{idx}.Model.X = designMatrix;

if args.Results.updateName
obj.Nodes{idx}.Name = appendSuffixToNodeName(obj.Nodes{idx}.Name, strategy);
end

end

function validateConstrasts(obj)
% validate all contrasts spec in the model

Expand Down Expand Up @@ -425,3 +575,71 @@

end
end

function name = appendSuffixToNodeName(name, strategy)
if ~isempty(name)
name = [name, '_'];
end
suffix = sprintf('rp-%s_scrub-%i_tissue-%s_nsso-%i', ...
strategy.motion{1}, ...
strategy.scrub{1}, ...
strategy.wm_csf{1}, ...
strategy.non_steady_state{1});

name = [name suffix];
end

function value = supportedStrategies()
value = {'motion', 'non_steady_state', 'wm_csf', 'scrub'};
end

function value = defaultStrategy()
value.strategies = {};
value.motion = 'none';
value.scrub = false;
value.wm_csf = 'none';
value.non_steady_state = false;
end

function designMatrix = cleanDesignMatrix(designMatrix)
% remove empty and duplicate
toClean = cellfun(@(x) isempty(x), designMatrix);
designMatrix(toClean) = [];

if isempty(designMatrix)
return
end
if size(designMatrix, 1) > 1
designMatrix = designMatrix';

Check warning on line 613 in src/bids_model/BidsModel.m

View check run for this annotation

Codecov / codecov/patch

src/bids_model/BidsModel.m#L613

Added line #L613 was not covered by tests
end

numeric = cellfun(@(x) isnumeric(x), designMatrix);
tmp = unique(designMatrix(~numeric));

designMatrix = cat(2, tmp, designMatrix(numeric));
end

function strategy = setFieldsStrategy(strategy)

tmp = defaultStrategy();

strategies = fieldnames(defaultStrategy());
for i = 1:numel(strategies)

if ~isfield(strategy, strategies{i})
strategy.(strategies{i}) = tmp.(strategies{i});
end

if ~iscell(strategy.(strategies{i}))
strategy.(strategies{i}) = {strategy.(strategies{i})};
end

if ~isempty(strategy.(strategies{i})) && ...
isnumeric(strategy.(strategies{i}){1}) && ...
isnan(strategy.(strategies{i}){1})
strategy.(strategies{i}){1} = tmp.(strategies{i});
end

end

end
Loading
Loading