@@ -52,6 +52,16 @@ class ComputeDVARSInputSpec(BaseInterfaceInputSpec):
52
52
desc = 'output figure size' )
53
53
figformat = traits .Enum ('png' , 'pdf' , 'svg' , usedefault = True ,
54
54
desc = 'output format for figures' )
55
+ intensity_normalization = traits .Float (1000.0 , usedefault = True ,
56
+ desc = 'Divide value in each voxel at each timepoint '
57
+ 'by the median calculated across all voxels'
58
+ 'and timepoints within the mask (if specified)'
59
+ 'and then multiply by the value specified by'
60
+ 'this parameter. By using the default (1000)' \
61
+ 'output DVARS will be expressed in ' \
62
+ 'x10 % BOLD units compatible with Power et al.' \
63
+ '2012. Set this to 0 to disable intensity' \
64
+ 'normalization altogether.' )
55
65
56
66
57
67
@@ -128,7 +138,8 @@ def _gen_fname(self, suffix, ext=None):
128
138
129
139
def _run_interface (self , runtime ):
130
140
dvars = compute_dvars (self .inputs .in_file , self .inputs .in_mask ,
131
- remove_zerovariance = self .inputs .remove_zerovariance )
141
+ remove_zerovariance = self .inputs .remove_zerovariance ,
142
+ intensity_normalization = self .inputs .intensity_normalization )
132
143
133
144
(self ._results ['avg_std' ],
134
145
self ._results ['avg_nstd' ],
@@ -595,7 +606,8 @@ def regress_poly(degree, data, remove_mean=True, axis=-1):
595
606
# Back to original shape
596
607
return regressed_data .reshape (datashape )
597
608
598
- def compute_dvars (in_file , in_mask , remove_zerovariance = False ):
609
+ def compute_dvars (in_file , in_mask , remove_zerovariance = False ,
610
+ intensity_normalization = 1000 ):
599
611
"""
600
612
Compute the :abbr:`DVARS (D referring to temporal
601
613
derivative of timecourses, VARS referring to RMS variance over voxels)`
@@ -636,59 +648,49 @@ def compute_dvars(in_file, in_mask, remove_zerovariance=False):
636
648
raise RuntimeError (
637
649
"Input fMRI dataset should be 4-dimensional" )
638
650
639
- # Robust standard deviation
640
- func_sd = (np .percentile (func , 75 , axis = 3 ) -
641
- np .percentile (func , 25 , axis = 3 )) / 1.349
642
- func_sd [mask <= 0 ] = 0
643
-
644
- if remove_zerovariance :
645
- # Remove zero-variance voxels across time axis
646
- mask = zero_remove (func_sd , mask )
647
-
648
651
idx = np .where (mask > 0 )
649
652
mfunc = func [idx [0 ], idx [1 ], idx [2 ], :]
650
653
651
- # Demean
652
- mfunc = regress_poly (0 , mfunc , remove_mean = True ).astype (np .float32 )
654
+ if intensity_normalization != 0 :
655
+ mfunc = (mfunc / np .median (mfunc )) * intensity_normalization
656
+
657
+ # Robust standard deviation (we are using "lower" interpolation
658
+ # because this is what FSL is doing
659
+ func_sd = (np .percentile (mfunc , 75 , axis = 1 , interpolation = "lower" ) -
660
+ np .percentile (mfunc , 25 , axis = 1 , interpolation = "lower" )) / 1.349
661
+
662
+ if remove_zerovariance :
663
+ mfunc = mfunc [func_sd != 0 , :]
664
+ func_sd = func_sd [func_sd != 0 ]
653
665
654
666
# Compute (non-robust) estimate of lag-1 autocorrelation
655
- ar1 = np .apply_along_axis (AR_est_YW , 1 , mfunc , 1 )[:, 0 ]
667
+ ar1 = np .apply_along_axis (AR_est_YW , 1 ,
668
+ regress_poly (0 , mfunc , remove_mean = True ).astype (
669
+ np .float32 ), 1 )[:, 0 ]
656
670
657
671
# Compute (predicted) standard deviation of temporal difference time series
658
- diff_sdhat = np .squeeze (np .sqrt (((1 - ar1 ) * 2 ).tolist ())) * func_sd [ mask > 0 ]. reshape ( - 1 )
672
+ diff_sdhat = np .squeeze (np .sqrt (((1 - ar1 ) * 2 ).tolist ())) * func_sd
659
673
diff_sd_mean = diff_sdhat .mean ()
660
674
661
675
# Compute temporal difference time series
662
676
func_diff = np .diff (mfunc , axis = 1 )
663
677
664
678
# DVARS (no standardization)
665
- dvars_nstd = func_diff . std (axis = 0 )
679
+ dvars_nstd = np . sqrt ( np . square ( func_diff ). mean (axis = 0 ) )
666
680
667
681
# standardization
668
682
dvars_stdz = dvars_nstd / diff_sd_mean
669
683
670
- with warnings .catch_warnings (): # catch, e.g., divide by zero errors
684
+ with warnings .catch_warnings (): # catch, e.g., divide by zero errors
671
685
warnings .filterwarnings ('error' )
672
686
673
687
# voxelwise standardization
674
- diff_vx_stdz = func_diff / np .array ([diff_sdhat ] * func_diff .shape [- 1 ]).T
675
- dvars_vx_stdz = diff_vx_stdz .std (axis = 0 , ddof = 1 )
688
+ diff_vx_stdz = np .square (
689
+ func_diff / np .array ([diff_sdhat ] * func_diff .shape [- 1 ]).T )
690
+ dvars_vx_stdz = np .sqrt (diff_vx_stdz .mean (axis = 0 ))
676
691
677
692
return (dvars_stdz , dvars_nstd , dvars_vx_stdz )
678
693
679
- def zero_remove (data , mask ):
680
- """
681
- Modify inputted mask to also mask out zero values
682
-
683
- :param numpy.ndarray data: e.g. voxelwise stddev of fMRI dataset, after motion correction
684
- :param numpy.ndarray mask: brain mask (same dimensions as data)
685
- :return: the mask with any additional zero voxels removed (same dimensions as inputs)
686
- :rtype: numpy.ndarray
687
-
688
- """
689
- new_mask = mask .copy ()
690
- new_mask [data == 0 ] = 0
691
- return new_mask
692
694
693
695
def plot_confound (tseries , figsize , name , units = None ,
694
696
series_tr = None , normalize = False ):
0 commit comments