Skip to content

Commit 6b00443

Browse files
authored
[ENH] add --return-residuals (#304)
* update FAQ to say old fmriprep outputs no longer work * add return_residuals to docs * add --return-residuals option and connect residuals to output * add test for --return-residuals * update other tests with return_residuals parameter
1 parent 45e1620 commit 6b00443

File tree

7 files changed

+64
-27
lines changed

7 files changed

+64
-27
lines changed

docs/FAQ.rst

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ Frequently Asked Questions
77

88
Does NiBetaSeries work with output from old fMRIPrep (< v1.2.0)?
99
----------------------------------------------------------------
10-
Yes, NiBetaSeries will currently work with older version of fMRIPrep.
11-
However, this will not continue to be the case.
10+
No, NiBetaSeries will not work with older version of fMRIPrep.
1211
To rename your files, you can use renameOldFMRIPREP_.
1312

1413
.. _renameOldFMRIPREP: https://github.com/HBClab/renameOldFMRIPREP

docs/workflows.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Participant Workflow
2626
norm_betas=False,
2727
output_dir='.',
2828
preproc_img_list=[''],
29+
return_residuals=False,
2930
selected_confounds=[''],
3031
signal_scaling=0,
3132
smoothing_kernel=None)

src/nibetaseries/cli/run.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -61,18 +61,22 @@ def get_parser():
6161
parser.add_argument('-v', '--version', action='version',
6262
version=verstr)
6363

64-
# Atlas Arguments (Required Options)
65-
atlas_args = parser.add_argument_group('Required Atlas Arguments')
66-
atlas_args.add_argument('-a', '--atlas-img', action='store',
67-
required=('-l' in sys.argv or '--atlas-lut' in sys.argv),
68-
help='input atlas nifti where each voxel within a "region" '
69-
'is labeled with the same integer and there is a unique '
70-
'integer associated with each region of interest.')
71-
atlas_args.add_argument('-l', '--atlas-lut', action='store',
72-
required=('-a' in sys.argv or '--atlas-img' in sys.argv),
73-
help='atlas look up table (tsv) formatted with the columns: '
74-
'index, regions which correspond to the regions in the '
75-
'nifti file specified by --atlas-img.')
64+
# Workflow Arguments
65+
wf_args = parser.add_argument_group('Workflow Arguments')
66+
wf_args.add_argument('-a', '--atlas-img', action='store',
67+
required=('-l' in sys.argv or '--atlas-lut' in sys.argv),
68+
help='input atlas nifti where each voxel within a "region" '
69+
'is labeled with the same integer and there is a unique '
70+
'integer associated with each region of interest.')
71+
wf_args.add_argument('-l', '--atlas-lut', action='store',
72+
required=('-a' in sys.argv or '--atlas-img' in sys.argv),
73+
help='atlas look up table (tsv) formatted with the columns: '
74+
'index, regions which correspond to the regions in the '
75+
'nifti file specified by --atlas-img.')
76+
wf_args.add_argument('--return-residuals', action='store_true', default=False,
77+
help='setting this option returns the residuals from the model'
78+
'while straightforward for LSA, for any other methods, take'
79+
'the residuals with a grain of salt.')
7680

7781
# preprocessing options
7882
proc_opts = parser.add_argument_group('Options for processing')
@@ -265,6 +269,7 @@ def main():
265269
high_pass=opts.high_pass,
266270
norm_betas=opts.normalize_betas,
267271
output_dir=output_dir,
272+
return_residuals=opts.return_residuals,
268273
run_label=opts.run_label,
269274
signal_scaling=signal_scaling,
270275
selected_confounds=opts.confounds,

src/nibetaseries/cli/tests/test_run.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,18 @@ def test_conditional_arguments(monkeypatch):
3737

3838

3939
@pytest.mark.parametrize(
40-
"use_atlas,estimator,fir_delays,hrf_model,part_label,use_signal_scaling,norm_betas",
40+
("use_atlas,estimator,fir_delays,hrf_model,part_label,"
41+
"use_signal_scaling,norm_betas,return_residuals"),
4142
[
42-
(True, 'lsa', None, 'spm', '01', True, True),
43-
(False, 'lss', None, 'spm', 'sub-01', False, False),
44-
(True, 'lss', [0, 1, 2, 3, 4], 'fir', None, False, True)
43+
(True, 'lsa', None, 'spm', '01', True, True, True),
44+
(False, 'lss', None, 'spm', 'sub-01', False, False, False),
45+
(True, 'lss', [0, 1, 2, 3, 4], 'fir', None, False, True, True),
4546
])
4647
def test_nibs(
4748
bids_dir, deriv_dir, sub_fmriprep, sub_metadata, bold_file, preproc_file,
4849
sub_events, confounds_file, brainmask_file, atlas_file, atlas_lut,
4950
estimator, fir_delays, hrf_model, monkeypatch, part_label, use_atlas,
50-
use_signal_scaling, norm_betas):
51+
use_signal_scaling, norm_betas, return_residuals):
5152
import sys
5253
bids_dir = str(bids_dir)
5354
out_dir = os.path.join(bids_dir, 'derivatives')
@@ -79,6 +80,8 @@ def test_nibs(
7980
args.extend([str(d) for d in fir_delays])
8081
if part_label:
8182
args.extend(["--participant-label", part_label])
83+
if return_residuals:
84+
args.append('--return-residuals')
8285

8386
monkeypatch.setattr(sys, 'argv', args)
8487
assert main() is None

src/nibetaseries/workflows/base.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232
def init_nibetaseries_participant_wf(
3333
estimator, atlas_img, atlas_lut, bids_dir,
3434
database_path, derivatives_pipeline_dir, exclude_description_label,
35-
fir_delays, hrf_model, high_pass, norm_betas, output_dir, run_label,
36-
selected_confounds, session_label, signal_scaling, smoothing_kernel,
35+
fir_delays, hrf_model, high_pass, norm_betas, output_dir, return_residuals,
36+
run_label, selected_confounds, session_label, signal_scaling, smoothing_kernel,
3737
space_label, subject_list, task_label, description_label, work_dir,
3838
):
3939

@@ -67,6 +67,9 @@ def init_nibetaseries_participant_wf(
6767
If True, beta estimates are divided by the square root of their variance
6868
output_dir : str
6969
Directory where derivatives are saved
70+
return_residuals : bool
71+
Output the residuals from the betaseries model into the
72+
derivatives directory
7073
run_label : str or None
7174
Include bold series containing this run label
7275
selected_confounds : list
@@ -177,6 +180,7 @@ def init_nibetaseries_participant_wf(
177180
norm_betas=norm_betas,
178181
output_dir=output_dir,
179182
preproc_img_list=preproc_img_list,
183+
return_residuals=return_residuals,
180184
selected_confounds=selected_confounds,
181185
signal_scaling=signal_scaling,
182186
smoothing_kernel=smoothing_kernel,
@@ -197,8 +201,8 @@ def init_nibetaseries_participant_wf(
197201
def init_single_subject_wf(
198202
estimator, atlas_img, atlas_lut, bold_metadata_list, brainmask_list,
199203
confound_tsv_list, events_tsv_list, fir_delays, hrf_model, high_pass,
200-
name, norm_betas, output_dir, preproc_img_list, selected_confounds,
201-
signal_scaling, smoothing_kernel,
204+
name, norm_betas, output_dir, preproc_img_list, return_residuals,
205+
selected_confounds, signal_scaling, smoothing_kernel,
202206
):
203207
"""
204208
This workflow completes the generation of the betaseries files
@@ -257,6 +261,9 @@ def init_single_subject_wf(
257261
Directory where derivatives are saved
258262
preproc_img_list : list
259263
list of preprocessed bold files
264+
return_residuals : bool
265+
Output the residuals from the betaseries model into the
266+
derivatives directory
260267
selected_confounds : list or None
261268
the list of confounds to be included in regression
262269
signal_scaling : False or 0
@@ -311,7 +318,8 @@ def init_single_subject_wf(
311318

312319
output_node = pe.Node(niu.IdentityInterface(fields=['correlation_matrix',
313320
'correlation_fig',
314-
'betaseries_file']),
321+
'betaseries_file',
322+
'residual_file']),
315323
name='output_node')
316324

317325
# initialize the betaseries workflow
@@ -389,6 +397,20 @@ def init_single_subject_wf(
389397
(output_node, ds_correlation_fig, [('correlation_fig', 'in_file')]),
390398
])
391399

400+
if return_residuals:
401+
ds_residual_file = pe.MapNode(DerivativesDataSink(base_directory=output_dir),
402+
iterfield=['in_file'],
403+
name='ds_residual_file')
404+
405+
workflow.connect([
406+
(betaseries_wf, output_node,
407+
[('output_node.residual_file', 'residual_file')]),
408+
(output_node, ds_residual_file,
409+
[('residual_file', 'in_file')]),
410+
(input_node, ds_residual_file,
411+
[('preproc_img', 'source_file')]),
412+
])
413+
392414
return workflow
393415

394416

src/nibetaseries/workflows/model.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,10 @@ def init_betaseries_wf(name="betaseries_wf",
8585
betaseries_files
8686
One file per trial type, with each file being
8787
as long as the number of events for that trial type.
88-
(assuming the number of trials for any trial type is above 2)
88+
residual_file
89+
The residual time series after running beta series.
90+
For LSA this is straight forward, but be cautious when
91+
interpreting residuals from LSS.
8992
9093
"""
9194
workflow = Workflow(name=name)
@@ -129,7 +132,8 @@ def init_betaseries_wf(name="betaseries_wf",
129132
high_pass=high_pass),
130133
name='betaseries_node')
131134

132-
output_node = pe.Node(niu.IdentityInterface(fields=['betaseries_files']),
135+
output_node = pe.Node(niu.IdentityInterface(fields=['betaseries_files',
136+
'residual_file']),
133137
name='output_node')
134138

135139
# main workflow
@@ -139,7 +143,8 @@ def init_betaseries_wf(name="betaseries_wf",
139143
('bold_mask_file', 'mask_file'),
140144
('bold_metadata', 'bold_metadata'),
141145
('confounds_file', 'confounds_file')]),
142-
(betaseries_node, output_node, [('beta_maps', 'betaseries_files')]),
146+
(betaseries_node, output_node, [('beta_maps', 'betaseries_files'),
147+
('residual', 'residual_file')]),
143148
])
144149

145150
return workflow

src/nibetaseries/workflows/tests/test_base.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ def test_valid_init_nibetaseries_participant_wf(
4545
high_pass=0.008,
4646
norm_betas=norm_betas,
4747
output_dir=output_dir,
48+
return_residuals=False,
4849
run_label=None,
4950
selected_confounds=['white_matter', 'csf'],
5051
session_label=None,
@@ -91,6 +92,7 @@ def test_filters_init_nibetaseries_participant_wf(
9192
high_pass=0.008,
9293
norm_betas=False,
9394
output_dir=output_dir,
95+
return_residuals=False,
9496
run_label=run_label,
9597
selected_confounds=['white_matter', 'csf'],
9698
session_label=session_label,

0 commit comments

Comments
 (0)