Skip to content

Commit 6b734d2

Browse files
committed
add generating plots
1 parent 4372f0c commit 6b734d2

File tree

1 file changed

+105
-17
lines changed

1 file changed

+105
-17
lines changed

nipype/algorithms/confounds.py

Lines changed: 105 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@
2020
import nibabel as nb
2121
import numpy as np
2222

23+
from .. import logging
2324
from ..external.due import due, Doi, BibTeX
2425
from ..interfaces.base import (traits, TraitedSpec, BaseInterface,
25-
BaseInterfaceInputSpec, File)
26+
BaseInterfaceInputSpec, File, isdefined)
27+
IFLOG = logging.getLogger('interface')
2628

2729

2830
class ComputeDVARSInputSpec(BaseInterfaceInputSpec):
@@ -36,12 +38,27 @@ class ComputeDVARSInputSpec(BaseInterfaceInputSpec):
3638
desc='save voxel-wise standardized DVARS')
3739
save_all = traits.Bool(False, usedefault=True, desc='output all DVARS')
3840

41+
series_tr = traits.Float(desc='repetition time in sec.')
42+
save_plot = traits.Bool(False, usedefault=True, desc='write DVARS plot')
43+
figdpi = traits.Int(100, usedefault=True, desc='output dpi for the plot')
44+
figsize = traits.Tuple(traits.Float(11.7), traits.Float(2.3), usedefault=True,
45+
desc='output figure size')
46+
figformat = traits.Enum('png', 'pdf', 'svg', usedefault=True,
47+
desc='output format for figures')
48+
49+
3950

4051
class ComputeDVARSOutputSpec(TraitedSpec):
4152
out_std = File(exists=True, desc='output text file')
4253
out_nstd = File(exists=True, desc='output text file')
4354
out_vxstd = File(exists=True, desc='output text file')
4455
out_all = File(exists=True, desc='output text file')
56+
avg_std = traits.Float()
57+
avg_nstd = traits.Float()
58+
avg_vxstd = traits.Float()
59+
fig_std = File(exists=True, desc='output DVARS plot')
60+
fig_nstd = File(exists=True, desc='output DVARS plot')
61+
fig_vxstd = File(exists=True, desc='output DVARS plot')
4562

4663

4764
class ComputeDVARS(BaseInterface):
@@ -51,7 +68,7 @@ class ComputeDVARS(BaseInterface):
5168
input_spec = ComputeDVARSInputSpec
5269
output_spec = ComputeDVARSOutputSpec
5370
references_ = [{
54-
'entry': BibTex("""\
71+
'entry': BibTeX("""\
5572
@techreport{nichols_notes_2013,
5673
address = {Coventry, UK},
5774
title = {Notes on {Creating} a {Standardized} {Version} of {DVARS}},
@@ -64,7 +81,7 @@ class ComputeDVARS(BaseInterface):
6481
}"""),
6582
'tags': ['method']
6683
}, {
67-
'entry': BibTex("""\
84+
'entry': BibTeX("""\
6885
@article{power_spurious_2012,
6986
title = {Spurious but systematic correlations in functional connectivity {MRI} networks \
7087
arise from subject motion},
@@ -100,37 +117,67 @@ def _gen_fname(self, suffix, ext=None):
100117
if ext.startswith('.'):
101118
ext = ext[1:]
102119

103-
return op.abspath('{}_{},{}'.format(fname, suffix, ext))
104-
105-
def _parse_inputs(self):
106-
if (self.inputs.save_std or self.inputs.save_nstd or
107-
self.inputs.save_vxstd or self.inputs.save_all):
108-
return super(ComputeDVARS, self)._parse_inputs()
109-
else:
110-
raise RuntimeError('At least one of the save_* options must be True')
120+
return op.abspath('{}_{}.{}'.format(fname, suffix, ext))
111121

112122
def _run_interface(self, runtime):
113123
dvars = compute_dvars(self.inputs.in_file, self.inputs.in_mask)
114124

125+
self._results['avg_std'] = dvars[0].mean()
126+
self._results['avg_nstd'] = dvars[1].mean()
127+
self._results['avg_vxstd'] = dvars[2].mean()
128+
129+
tr = None
130+
if isdefined(self.inputs.series_tr):
131+
tr = self.inputs.series_tr
132+
115133
if self.inputs.save_std:
116134
out_file = self._gen_fname('dvars_std', ext='tsv')
117-
np.savetxt(out_file, dvars[0], fmt=b'%.12f')
135+
np.savetxt(out_file, dvars[0], fmt=b'%0.6f')
118136
self._results['out_std'] = out_file
119137

138+
if self.inputs.save_plot:
139+
self._results['fig_std'] = self._gen_fname(
140+
'dvars_std', ext=self.inputs.figformat)
141+
fig = plot_confound(dvars[0], self.inputs.figsize, 'Standardized DVARS',
142+
series_tr=tr)
143+
fig.savefig(self._results['fig_std'], dpi=float(self.inputs.figdpi),
144+
format=self.inputs.figformat,
145+
bbox_inches='tight')
146+
fig.clf()
147+
120148
if self.inputs.save_nstd:
121149
out_file = self._gen_fname('dvars_nstd', ext='tsv')
122-
np.savetxt(out_file, dvars[1], fmt=b'%.12f')
150+
np.savetxt(out_file, dvars[1], fmt=b'%0.6f')
123151
self._results['out_nstd'] = out_file
124152

153+
if self.inputs.save_plot:
154+
self._results['fig_nstd'] = self._gen_fname(
155+
'dvars_nstd', ext=self.inputs.figformat)
156+
fig = plot_confound(dvars[1], self.inputs.figsize, 'DVARS', series_tr=tr)
157+
fig.savefig(self._results['fig_nstd'], dpi=float(self.inputs.figdpi),
158+
format=self.inputs.figformat,
159+
bbox_inches='tight')
160+
fig.clf()
161+
125162
if self.inputs.save_vxstd:
126163
out_file = self._gen_fname('dvars_vxstd', ext='tsv')
127-
np.savetxt(out_file, dvars[2], fmt=b'%.12f')
164+
np.savetxt(out_file, dvars[2], fmt=b'%0.6f')
128165
self._results['out_vxstd'] = out_file
129166

167+
if self.inputs.save_plot:
168+
self._results['fig_vxstd'] = self._gen_fname(
169+
'dvars_vxstd', ext=self.inputs.figformat)
170+
fig = plot_confound(dvars[2], self.inputs.figsize, 'Voxelwise std DVARS',
171+
series_tr=tr)
172+
fig.savefig(self._results['fig_vxstd'], dpi=float(self.inputs.figdpi),
173+
format=self.inputs.figformat,
174+
bbox_inches='tight')
175+
fig.clf()
176+
130177
if self.inputs.save_all:
131178
out_file = self._gen_fname('dvars', ext='tsv')
132-
np.savetxt(out_file, np.vstack(dvars), fmt=b'%.12f', delimiter=b'\t',
133-
header='# std DVARS\tnon-std DVARS\tvx-wise std DVARS')
179+
np.savetxt(out_file, np.vstack(dvars).T, fmt=b'%0.8f', delimiter=b'\t',
180+
header='std DVARS\tnon-std DVARS\tvx-wise std DVARS')
134181
self._results['out_all'] = out_file
135182

136183
return runtime
@@ -210,7 +257,7 @@ def compute_dvars(in_file, in_mask):
210257

211258
# voxelwise standardization
212259
diff_vx_stdz = func_diff / np.array([func_sd_pd] * func_diff.shape[-1]).T
213-
dvars_vx_stdz = diff_vx_stdz.std(1, ddof=1)
260+
dvars_vx_stdz = diff_vx_stdz.std(axis=0, ddof=1)
214261

215262
return (dvars_stdz, dvars_nstd, dvars_vx_stdz)
216263

@@ -233,3 +280,44 @@ def zero_variance(func, mask):
233280
newmask = np.zeros_like(mask, dtype=np.uint8)
234281
newmask[idx] = tv_mask
235282
return newmask
283+
284+
def plot_confound(tseries, figsize, name, units=None,
285+
series_tr=None, normalize=False):
286+
"""
287+
A helper function to plot the framewise displacement
288+
"""
289+
import matplotlib
290+
matplotlib.use('Agg')
291+
import matplotlib.pyplot as plt
292+
from matplotlib.gridspec import GridSpec
293+
from matplotlib.backends.backend_pdf import FigureCanvasPdf as FigureCanvas
294+
import seaborn as sns
295+
296+
fig = plt.Figure(figsize=figsize)
297+
FigureCanvas(fig)
298+
grid = GridSpec(1, 2, width_ratios=[3, 1], wspace=0.025)
299+
grid.update(hspace=1.0, right=0.95, left=0.1, bottom=0.2)
300+
301+
ax = fig.add_subplot(grid[0, :-1])
302+
if normalize and series_tr is not None:
303+
tseries /= series_tr
304+
305+
ax.plot(tseries)
306+
ax.set_xlim((0, len(tseries)))
307+
ylabel = name
308+
if units is not None:
309+
ylabel += (' speed [{}/s]' if normalize else ' [{}]').format(units)
310+
ax.set_ylabel(ylabel)
311+
312+
xlabel = 'Frame #'
313+
if series_tr is not None:
314+
xlabel = 'Frame # ({} sec TR)'.format(series_tr)
315+
ax.set_xlabel(xlabel)
316+
ylim = ax.get_ylim()
317+
318+
ax = fig.add_subplot(grid[0, -1])
319+
sns.distplot(tseries, vertical=True, ax=ax)
320+
ax.set_xlabel('Frames')
321+
ax.set_ylim(ylim)
322+
ax.set_yticklabels([])
323+
return fig

0 commit comments

Comments
 (0)