Skip to content

Commit

Permalink
[ENH] make addConfoundsToDesignMatrix a method of BidsModel (#1294)
Browse files Browse the repository at this point in the history
* make add confound stratefy a method

* update changelog
  • Loading branch information
Remi-Gau authored Jul 24, 2024
1 parent af24ad1 commit 344838e
Show file tree
Hide file tree
Showing 6 changed files with 231 additions and 251 deletions.
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(), ...
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(), ...
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
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 @@ function bidsModelError(obj, id, msg)

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';
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

0 comments on commit 344838e

Please sign in to comment.