Skip to content

Commit 344838e

Browse files
authored
[ENH] make addConfoundsToDesignMatrix a method of BidsModel (#1294)
* make add confound stratefy a method * update changelog
1 parent af24ad1 commit 344838e

File tree

6 files changed

+231
-251
lines changed

6 files changed

+231
-251
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2424

2525
### Added
2626

27+
* [ENH] make `addConfoundsToDesignMatrix` a method of `BidsModel` #1294 by @Remi-Gau
2728
* [ENH] add Apptainer definition #1254 by @Remi-Gau and @monique2208
2829
* [ENH] allow to copy anat only on raw datasets #1181 by @Remi-Gau
2930
* [ENH] add option to concatenate runs at subject level to facilite running PPI analysis #1133 by @Remi-Gau

docs/source/API.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,6 @@ bids
188188

189189
bids_model
190190
==========
191-
.. autofunction:: src.bids_model.addConfoundsToDesignMatrix
192191
.. autofunction:: src.bids_model.checkContrast
193192
.. autofunction:: src.bids_model.checkGroupBy
194193
.. autofunction:: src.bids_model.createDefaultStatsModel

src/bids_model/BidsModel.m

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,156 @@
346346

347347
end
348348

349+
function obj = addConfoundsToDesignMatrix(obj, varargin)
350+
%
351+
% Add some typical confounds to the design matrix of bids stat model.
352+
%
353+
% This will update the design matrix of the root node of the model.
354+
%
355+
% Similar to the :func:`nilearn.interfaces.fmriprep.load_confounds`
356+
%
357+
% USAGE::
358+
%
359+
% bm = bm.addConfoundsToDesignMatrix('strategy', strategy);
360+
%
361+
%
362+
% :param bm: bids stats model.
363+
% :type bm: :obj:`BidsModel` instance or path
364+
% to a ``_smdl.json`` file
365+
%
366+
% :type strategy: struct
367+
% :param strategy: structure describing the confoudd strategy.
368+
%
369+
% The structure must have the following field:
370+
%
371+
% - ``strategy``: cell array of char
372+
% with the strategies to apply.
373+
%
374+
% The structure may have the following field:
375+
%
376+
% - ``motion``: motion regressors strategy
377+
% - ``scrub``: scrubbing strategy
378+
% - ``wm_csf``: white matter
379+
% and cerebrospinal fluid regressors strategy
380+
% - ``non_steady_state``:
381+
% non steady state regressors strategy
382+
%
383+
% See the nilearn documentation (mentioned above)
384+
% for more information on the possible values
385+
% those strategies can take.
386+
%
387+
% :type updateName: logical
388+
% :param updateName: Append the name of the root node
389+
% with a string describing the counfounds added.
390+
%
391+
% ``rp-{motion}_scrub-{scrub}_tissue-{wm_csf}_nsso-{non_steady_state}``
392+
%
393+
% default = ``false``
394+
%
395+
%
396+
% :rtype: :obj:`BidsModel` instance
397+
% :return: bids stats model with the confounds added.
398+
%
399+
% EXAMPLE:
400+
%
401+
% .. code-block:: matlab
402+
%
403+
%
404+
% strategy.strategies = {'motion', 'wm_csf', 'scrub', 'non_steady_state'};
405+
% strategy.motion = 'full';
406+
% strategy.scrub = true;
407+
% strategy.non_steady_state = true;
408+
%
409+
% bm = bm.addConfoundsToDesignMatrix('strategy', strategy);
410+
%
411+
%
412+
413+
% (C) Copyright 2023 bidspm developers
414+
415+
args = inputParser;
416+
args.CaseSensitive = false;
417+
args.KeepUnmatched = false;
418+
args.FunctionName = 'addConfoundsToDesignMatrix';
419+
420+
addParameter(args, 'strategy', defaultStrategy(), @isstruct);
421+
addParameter(args, 'updateName', false, @islogical);
422+
423+
parse(args, varargin{:});
424+
425+
strategy = args.Results.strategy;
426+
strategy = setFieldsStrategy(strategy);
427+
428+
[~, name] = obj.get_root_node();
429+
[~, idx] = obj.get_nodes('Name', name);
430+
designMatrix = obj.get_design_matrix('Name', name);
431+
432+
strategiesToApply = strategy.strategies;
433+
for i = 1:numel(strategiesToApply)
434+
435+
switch strategiesToApply{i}
436+
437+
case 'motion'
438+
switch strategy.motion{1}
439+
case 'none'
440+
case 'basic'
441+
designMatrix{end + 1} = 'rot_?'; %#ok<*AGROW>
442+
designMatrix{end + 1} = 'trans_?';
443+
case {'power2', 'derivatives' }
444+
notImplemented(mfilename(), ...
445+
sprintf('motion "%s" not implemented.', strategy.motion));
446+
case 'full'
447+
designMatrix{end + 1} = 'rot_*';
448+
designMatrix{end + 1} = 'trans_*';
449+
end
450+
451+
case 'non_steady_state'
452+
if strategy.non_steady_state{1}
453+
designMatrix{end + 1} = 'non_steady_state_outlier*';
454+
end
455+
456+
case 'scrub'
457+
if strategy.scrub{1}
458+
designMatrix{end + 1} = 'motion_outlier*';
459+
end
460+
461+
case 'wm_csf'
462+
switch strategy.wm_csf{1}
463+
case 'none'
464+
case 'basic'
465+
designMatrix{end + 1} = 'csf';
466+
designMatrix{end + 1} = 'white';
467+
case 'full'
468+
designMatrix{end + 1} = 'csf_*';
469+
designMatrix{end + 1} = 'white_*';
470+
otherwise
471+
notImplemented(mfilename(), ...
472+
sprintf('wm_csf "%s" not implemented.', strategiesToApply{i}));
473+
end
474+
475+
case {'global_signal', 'compcorstr', 'n_compcorstr'}
476+
notImplemented(mfilename(), ...
477+
sprintf(['Strategey "%s" not implemented.\n', ...
478+
'Supported strategies are:%s'], ...
479+
strategiesToApply{i}, ...
480+
bids.internal.create_unordered_list(supportedStrategies())));
481+
otherwise
482+
logger('WARNING', sprintf('Unknown strategey: "%s".', ...
483+
strategiesToApply{i}), ...
484+
'filename', mfilename(), ...
485+
'id', 'unknownStrategy');
486+
end
487+
end
488+
489+
designMatrix = cleanDesignMatrix(designMatrix);
490+
491+
obj.Nodes{idx}.Model.X = designMatrix;
492+
493+
if args.Results.updateName
494+
obj.Nodes{idx}.Name = appendSuffixToNodeName(obj.Nodes{idx}.Name, strategy);
495+
end
496+
497+
end
498+
349499
function validateConstrasts(obj)
350500
% validate all contrasts spec in the model
351501

@@ -425,3 +575,71 @@ function bidsModelError(obj, id, msg)
425575

426576
end
427577
end
578+
579+
function name = appendSuffixToNodeName(name, strategy)
580+
if ~isempty(name)
581+
name = [name, '_'];
582+
end
583+
suffix = sprintf('rp-%s_scrub-%i_tissue-%s_nsso-%i', ...
584+
strategy.motion{1}, ...
585+
strategy.scrub{1}, ...
586+
strategy.wm_csf{1}, ...
587+
strategy.non_steady_state{1});
588+
589+
name = [name suffix];
590+
end
591+
592+
function value = supportedStrategies()
593+
value = {'motion', 'non_steady_state', 'wm_csf', 'scrub'};
594+
end
595+
596+
function value = defaultStrategy()
597+
value.strategies = {};
598+
value.motion = 'none';
599+
value.scrub = false;
600+
value.wm_csf = 'none';
601+
value.non_steady_state = false;
602+
end
603+
604+
function designMatrix = cleanDesignMatrix(designMatrix)
605+
% remove empty and duplicate
606+
toClean = cellfun(@(x) isempty(x), designMatrix);
607+
designMatrix(toClean) = [];
608+
609+
if isempty(designMatrix)
610+
return
611+
end
612+
if size(designMatrix, 1) > 1
613+
designMatrix = designMatrix';
614+
end
615+
616+
numeric = cellfun(@(x) isnumeric(x), designMatrix);
617+
tmp = unique(designMatrix(~numeric));
618+
619+
designMatrix = cat(2, tmp, designMatrix(numeric));
620+
end
621+
622+
function strategy = setFieldsStrategy(strategy)
623+
624+
tmp = defaultStrategy();
625+
626+
strategies = fieldnames(defaultStrategy());
627+
for i = 1:numel(strategies)
628+
629+
if ~isfield(strategy, strategies{i})
630+
strategy.(strategies{i}) = tmp.(strategies{i});
631+
end
632+
633+
if ~iscell(strategy.(strategies{i}))
634+
strategy.(strategies{i}) = {strategy.(strategies{i})};
635+
end
636+
637+
if ~isempty(strategy.(strategies{i})) && ...
638+
isnumeric(strategy.(strategies{i}){1}) && ...
639+
isnan(strategy.(strategies{i}){1})
640+
strategy.(strategies{i}){1} = tmp.(strategies{i});
641+
end
642+
643+
end
644+
645+
end

0 commit comments

Comments
 (0)