20
20
import nibabel as nb
21
21
import numpy as np
22
22
23
+ from .. import logging
23
24
from ..external .due import due , Doi , BibTeX
24
25
from ..interfaces .base import (traits , TraitedSpec , BaseInterface ,
25
- BaseInterfaceInputSpec , File )
26
+ BaseInterfaceInputSpec , File , isdefined )
27
+ IFLOG = logging .getLogger ('interface' )
26
28
27
29
28
30
class ComputeDVARSInputSpec (BaseInterfaceInputSpec ):
@@ -36,12 +38,27 @@ class ComputeDVARSInputSpec(BaseInterfaceInputSpec):
36
38
desc = 'save voxel-wise standardized DVARS' )
37
39
save_all = traits .Bool (False , usedefault = True , desc = 'output all DVARS' )
38
40
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
+
39
50
40
51
class ComputeDVARSOutputSpec (TraitedSpec ):
41
52
out_std = File (exists = True , desc = 'output text file' )
42
53
out_nstd = File (exists = True , desc = 'output text file' )
43
54
out_vxstd = File (exists = True , desc = 'output text file' )
44
55
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' )
45
62
46
63
47
64
class ComputeDVARS (BaseInterface ):
@@ -51,7 +68,7 @@ class ComputeDVARS(BaseInterface):
51
68
input_spec = ComputeDVARSInputSpec
52
69
output_spec = ComputeDVARSOutputSpec
53
70
references_ = [{
54
- 'entry' : BibTex ("""\
71
+ 'entry' : BibTeX ("""\
55
72
@techreport{nichols_notes_2013,
56
73
address = {Coventry, UK},
57
74
title = {Notes on {Creating} a {Standardized} {Version} of {DVARS}},
@@ -64,7 +81,7 @@ class ComputeDVARS(BaseInterface):
64
81
}""" ),
65
82
'tags' : ['method' ]
66
83
}, {
67
- 'entry' : BibTex ("""\
84
+ 'entry' : BibTeX ("""\
68
85
@article{power_spurious_2012,
69
86
title = {Spurious but systematic correlations in functional connectivity {MRI} networks \
70
87
arise from subject motion},
@@ -100,37 +117,67 @@ def _gen_fname(self, suffix, ext=None):
100
117
if ext .startswith ('.' ):
101
118
ext = ext [1 :]
102
119
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 ))
111
121
112
122
def _run_interface (self , runtime ):
113
123
dvars = compute_dvars (self .inputs .in_file , self .inputs .in_mask )
114
124
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
+
115
133
if self .inputs .save_std :
116
134
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 ' )
118
136
self ._results ['out_std' ] = out_file
119
137
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
+
120
148
if self .inputs .save_nstd :
121
149
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 ' )
123
151
self ._results ['out_nstd' ] = out_file
124
152
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
+
125
162
if self .inputs .save_vxstd :
126
163
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 ' )
128
165
self ._results ['out_vxstd' ] = out_file
129
166
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
+
130
177
if self .inputs .save_all :
131
178
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\t non-std DVARS\t vx-wise std DVARS' )
179
+ np .savetxt (out_file , np .vstack (dvars ). T , fmt = b'%0.8f ' , delimiter = b'\t ' ,
180
+ header = 'std DVARS\t non-std DVARS\t vx-wise std DVARS' )
134
181
self ._results ['out_all' ] = out_file
135
182
136
183
return runtime
@@ -210,7 +257,7 @@ def compute_dvars(in_file, in_mask):
210
257
211
258
# voxelwise standardization
212
259
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 )
214
261
215
262
return (dvars_stdz , dvars_nstd , dvars_vx_stdz )
216
263
@@ -233,3 +280,44 @@ def zero_variance(func, mask):
233
280
newmask = np .zeros_like (mask , dtype = np .uint8 )
234
281
newmask [idx ] = tv_mask
235
282
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