Skip to content

Commit 4369e64

Browse files
authored
Merge pull request #1443 from effigies/fix/compcor_nans
[MRG] FIX: Output NaN columns for CompCor on failure
2 parents 752c5b5 + 74371b3 commit 4369e64

File tree

5 files changed

+110
-32
lines changed

5 files changed

+110
-32
lines changed

docs/environment.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ dependencies:
2020
- python-dateutil
2121
- pydot>=1.2.3
2222
- cython
23-
- nipype>=1.1.6
23+
- nipype>=1.1.7
2424

2525
- pip:
2626
- sphinx-argparse

fmriprep/__about__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
'indexed_gzip>=0.8.8',
8989
'nibabel>=2.2.1',
9090
'nilearn',
91-
'nipype>=1.1.6',
91+
'nipype>=1.1.7',
9292
'nitime',
9393
'niworkflows>=0.5.2.post5,<0.5.3',
9494
'numpy',

fmriprep/interfaces/patches.py

Lines changed: 83 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,49 +12,104 @@
1212

1313
from numpy.linalg.linalg import LinAlgError
1414
from nipype.algorithms import confounds as nac
15+
from nipype.interfaces.base import File
16+
from nipype.interfaces.mixins import reporting
1517

1618

17-
class RobustACompCor(nac.ACompCor):
18-
"""
19-
Runs aCompCor several times if it suddenly fails with
20-
https://github.com/poldracklab/fmriprep/issues/776
19+
class RetryCompCorInputSpecMixin(reporting.ReportCapableInputSpec):
20+
out_report = File('report.html', usedefault=True, hash_files=False,
21+
desc='filename for warning HTML snippet')
2122

22-
"""
2323

24+
class RetryCompCorMixin(reporting.ReportCapableInterface):
2425
def _run_interface(self, runtime):
26+
warn = self.inputs.failure_mode == 'NaN'
27+
2528
failures = 0
29+
save_exc = None
2630
while True:
31+
success = True
32+
# Identifiy success/failure in both error and NaN mode
2733
try:
28-
runtime = super(RobustACompCor, self)._run_interface(runtime)
34+
runtime = super()._run_interface(runtime)
35+
if warn and self._is_allnans():
36+
success = False
37+
except LinAlgError as exc:
38+
success = False
39+
save_exc = exc
40+
41+
if success:
2942
break
30-
except LinAlgError:
31-
failures += 1
32-
if failures > 10:
33-
raise
34-
start = (failures - 1) * 10
35-
sleep(randint(start + 4, start + 10))
43+
44+
failures += 1
45+
if failures > 10:
46+
if warn:
47+
break
48+
raise save_exc
49+
start = (failures - 1) * 10
50+
sleep(randint(start + 4, start + 10))
3651

3752
return runtime
3853

54+
def _is_allnans(self):
55+
import numpy as np
56+
outputs = self._list_outputs()
57+
components = np.loadtxt(outputs['components_file'], skiprows=1)
58+
return np.isnan(components).all()
59+
60+
def _generate_report(self):
61+
snippet = '<!-- {} completed without error -->'.format(self._header)
62+
if self._is_allnans():
63+
snippet = '''\
64+
<p class="elem-desc">
65+
Warning: {} components could not be estimated, due to a linear algebra error.
66+
While not definitive, this may be an indication of a poor mask.
67+
Please inspect the {} contours above to ensure that they are located
68+
in the white matter/CSF.
69+
</p>
70+
'''.format(self._header, 'magenta' if self._header[0] == 'a' else 'blue')
71+
72+
with open(self._out_report, 'w') as fobj:
73+
fobj.write(snippet)
3974

40-
class RobustTCompCor(nac.TCompCor):
75+
76+
class RobustACompCorInputSpec(RetryCompCorInputSpecMixin, nac.CompCorInputSpec):
77+
pass
78+
79+
80+
class RobustACompCorOutputSpec(reporting.ReportCapableOutputSpec, nac.CompCorOutputSpec):
81+
pass
82+
83+
84+
class RobustACompCor(RetryCompCorMixin, nac.ACompCor):
4185
"""
42-
Runs tCompCor several times if it suddenly fails with
43-
https://github.com/poldracklab/fmriprep/issues/940
86+
Runs aCompCor several times if it suddenly fails with
87+
https://github.com/poldracklab/fmriprep/issues/776
88+
89+
Warns by default, rather than failing, on linear algebra errors.
90+
https://github.com/poldracklab/fmriprep/issues/1433
4491
4592
"""
93+
input_spec = RobustACompCorInputSpec
94+
output_spec = RobustACompCorOutputSpec
4695

47-
def _run_interface(self, runtime):
48-
failures = 0
49-
while True:
50-
try:
51-
runtime = super(RobustTCompCor, self)._run_interface(runtime)
52-
break
53-
except LinAlgError:
54-
failures += 1
55-
if failures > 10:
56-
raise
57-
start = (failures - 1) * 10
58-
sleep(randint(start + 4, start + 10))
5996

60-
return runtime
97+
class RobustTCompCorInputSpec(RetryCompCorInputSpecMixin, nac.TCompCorInputSpec):
98+
pass
99+
100+
101+
class RobustTCompCorOutputSpec(reporting.ReportCapableOutputSpec, nac.TCompCorOutputSpec):
102+
pass
103+
104+
105+
class RobustTCompCor(RetryCompCorMixin, nac.TCompCor):
106+
"""
107+
Runs tCompCor several times if it suddenly fails with
108+
https://github.com/poldracklab/fmriprep/issues/776
109+
110+
Warns by default, rather than failing, on linear algebra errors.
111+
https://github.com/poldracklab/fmriprep/issues/1433
112+
113+
"""
114+
input_spec = RobustTCompCorInputSpec
115+
output_spec = RobustTCompCorOutputSpec

fmriprep/viz/config.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,16 @@
9898
"title": "ROIs in BOLD space",
9999
"description": "Brain mask calculated on the BOLD signal (red contour), along with the masks used for a/tCompCor.<br />The aCompCor mask (magenta contour) is a conservative CSF and white-matter mask for extracting physiological and movement confounds. <br />The fCompCor mask (blue contour) contains the top 5% most variable voxels within a heavily-eroded brain-mask."
100100
},
101+
{
102+
"name": "epi/warn_tcompcor",
103+
"file_pattern": "func/.*_acompcor\\.",
104+
"raw": true
105+
},
106+
{
107+
"name": "epi/warn_tcompcor",
108+
"file_pattern": "func/.*_tcompcor\\.",
109+
"raw": true
110+
},
101111
{
102112
"name": "epi_mean_t1_registration/flirt",
103113
"file_pattern": "func/.*_flirtnobbr",

fmriprep/workflows/bold/confounds.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,12 +183,13 @@ def init_bold_confs_wf(mem_gb, metadata, name="bold_confs_wf"):
183183
# a/t-CompCor
184184
tcompcor = pe.Node(
185185
TCompCor(components_file='tcompcor.tsv', header_prefix='t_comp_cor_', pre_filter='cosine',
186-
save_pre_filter=True, percentile_threshold=.05),
186+
save_pre_filter=True, percentile_threshold=.05, failure_mode='NaN',
187+
generate_report=True),
187188
name="tcompcor", mem_gb=mem_gb)
188189

189190
acompcor = pe.Node(
190191
ACompCor(components_file='acompcor.tsv', header_prefix='a_comp_cor_', pre_filter='cosine',
191-
save_pre_filter=True),
192+
save_pre_filter=True, failure_mode='NaN', generate_report=True),
192193
name="acompcor", mem_gb=mem_gb)
193194

194195
# Set TR if present
@@ -223,6 +224,16 @@ def init_bold_confs_wf(mem_gb, metadata, name="bold_confs_wf"):
223224
name='ds_report_bold_rois', run_without_submitting=True,
224225
mem_gb=DEFAULT_MEMORY_MIN_GB)
225226

227+
ds_report_warn_acompcor = pe.Node(
228+
DerivativesDataSink(suffix='acompcor'),
229+
name='ds_report_warn_acompcor', run_without_submitting=True,
230+
mem_gb=DEFAULT_MEMORY_MIN_GB)
231+
232+
ds_report_warn_tcompcor = pe.Node(
233+
DerivativesDataSink(suffix='tcompcor'),
234+
name='ds_report_warn_tcompcor', run_without_submitting=True,
235+
mem_gb=DEFAULT_MEMORY_MIN_GB)
236+
226237
def _pick_csf(files):
227238
return files[0]
228239

@@ -303,6 +314,8 @@ def _pick_wm(files):
303314
(acc_msk, mrg_compcor, [('out', 'in2')]),
304315
(mrg_compcor, rois_plot, [('out', 'in_rois')]),
305316
(rois_plot, ds_report_bold_rois, [('out_report', 'in_file')]),
317+
(acompcor, ds_report_warn_acompcor, [('out_report', 'in_file')]),
318+
(tcompcor, ds_report_warn_tcompcor, [('out_report', 'in_file')]),
306319
])
307320

308321
return workflow

0 commit comments

Comments
 (0)