Skip to content

Commit 0250954

Browse files
committed
add regression test
1 parent fe02413 commit 0250954

File tree

5 files changed

+62
-18
lines changed

5 files changed

+62
-18
lines changed

nipype/algorithms/confounds.py

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
class ComputeDVARSInputSpec(BaseInterfaceInputSpec):
3131
in_file = File(exists=True, mandatory=True, desc='functional data, after HMC')
3232
in_mask = File(exists=True, mandatory=True, desc='a brain mask')
33+
remove_zerovariance = traits.Bool(False, usedefault=True,
34+
desc='remove voxels with zero variance')
3335
save_std = traits.Bool(True, usedefault=True,
3436
desc='save standardized DVARS')
3537
save_nstd = traits.Bool(False, usedefault=True,
@@ -120,7 +122,8 @@ def _gen_fname(self, suffix, ext=None):
120122
return op.abspath('{}_{}.{}'.format(fname, suffix, ext))
121123

122124
def _run_interface(self, runtime):
123-
dvars = compute_dvars(self.inputs.in_file, self.inputs.in_mask)
125+
dvars = compute_dvars(self.inputs.in_file, self.inputs.in_mask,
126+
remove_zerovariance=self.inputs.remove_zerovariance)
124127

125128
self._results['avg_std'] = dvars[0].mean()
126129
self._results['avg_nstd'] = dvars[1].mean()
@@ -180,6 +183,8 @@ def _run_interface(self, runtime):
180183
header='std DVARS\tnon-std DVARS\tvx-wise std DVARS')
181184
self._results['out_all'] = out_file
182185

186+
return runtime
187+
183188
def _list_outputs(self):
184189
return self._results
185190

@@ -250,7 +255,6 @@ def _run_interface(self, runtime):
250255
}
251256
np.savetxt(self.inputs.out_file, fd_res)
252257

253-
254258
if self.inputs.save_plot:
255259
tr = None
256260
if isdefined(self.inputs.series_tr):
@@ -266,13 +270,14 @@ def _run_interface(self, runtime):
266270
format=self.inputs.out_figure[-3:],
267271
bbox_inches='tight')
268272
fig.clf()
273+
269274
return runtime
270275

271276
def _list_outputs(self):
272277
return self._results
273278

274279

275-
def compute_dvars(in_file, in_mask):
280+
def compute_dvars(in_file, in_mask, remove_zerovariance=False):
276281
"""
277282
Compute the :abbr:`DVARS (D referring to temporal
278283
derivative of timecourses, VARS referring to RMS variance over voxels)`
@@ -313,24 +318,32 @@ def compute_dvars(in_file, in_mask):
313318
raise RuntimeError(
314319
"Input fMRI dataset should be 4-dimensional")
315320

316-
# Remove zero-variance voxels across time axis
317-
zv_mask = zero_variance(func, mask)
318-
idx = np.where(zv_mask > 0)
319-
mfunc = func[idx[0], idx[1], idx[2], :]
320-
321321
# Robust standard deviation
322-
func_sd = (np.percentile(mfunc, 75) -
323-
np.percentile(mfunc, 25)) / 1.349
322+
func_sd = (np.percentile(func, 75, axis=3) -
323+
np.percentile(func, 25, axis=3)) / 1.349
324+
func_sd[mask <= 0] = 0
325+
326+
# ar1_img = np.zeros_like(func_sd)
327+
# ar1_img[idx] = diff_SDhat
328+
nb.Nifti1Image(func_sd, nb.load(in_mask).get_affine()).to_filename('func_sd.nii.gz')
329+
330+
331+
if remove_zerovariance:
332+
# Remove zero-variance voxels across time axis
333+
mask = zero_variance(func, mask)
334+
335+
idx = np.where(mask > 0)
336+
mfunc = func[idx[0], idx[1], idx[2], :]
324337

325338
# Demean
326339
mfunc -= mfunc.mean(axis=1).astype(np.float32)[..., np.newaxis]
327340

328-
# AR1
329-
ak_coeffs = np.apply_along_axis(AR_est_YW, 1, mfunc, 1)
341+
# Compute (non-robust) estimate of lag-1 autocorrelation
342+
ar1 = np.apply_along_axis(AR_est_YW, 1, mfunc, 1)[:, 0]
330343

331-
# Predicted standard deviation of temporal derivative
332-
func_sd_pd = np.squeeze(np.sqrt((2. * (1. - ak_coeffs[:, 0])).tolist()) * func_sd)
333-
diff_sd_mean = func_sd_pd[func_sd_pd > 0].mean()
344+
# Compute (predicted) standard deviation of temporal difference time series
345+
diff_SDhat = np.squeeze(np.sqrt(((1 - ar1) * 2).tolist())) * func_sd[mask > 0].reshape(-1)
346+
diff_sd_mean = diff_SDhat.mean()
334347

335348
# Compute temporal difference time series
336349
func_diff = np.diff(mfunc, axis=1)
@@ -342,7 +355,7 @@ def compute_dvars(in_file, in_mask):
342355
dvars_stdz = dvars_nstd / diff_sd_mean
343356

344357
# voxelwise standardization
345-
diff_vx_stdz = func_diff / np.array([func_sd_pd] * func_diff.shape[-1]).T
358+
diff_vx_stdz = func_diff / np.array([diff_SDhat] * func_diff.shape[-1]).T
346359
dvars_vx_stdz = diff_vx_stdz.std(axis=0, ddof=1)
347360

348361
return (dvars_stdz, dvars_nstd, dvars_vx_stdz)

nipype/algorithms/tests/test_confounds.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
#!/usr/bin/env python
22
# -*- coding: utf-8 -*-
3-
3+
import os
44
from nipype.testing import (assert_equal, example_data)
5-
from nipype.algorithms.confounds import FramewiseDisplacement
5+
from nipype.algorithms.confounds import FramewiseDisplacement, ComputeDVARS
66
import numpy as np
77
from tempfile import mkdtemp
88
from shutil import rmtree
@@ -16,3 +16,15 @@ def test_fd():
1616
yield assert_equal, np.allclose(ground_truth, np.loadtxt(res.outputs.out_file)), True
1717
yield assert_equal, np.abs(ground_truth.mean() - res.outputs.fd_average) < 1e-4, True
1818
rmtree(tempdir)
19+
20+
def test_dvars():
21+
tempdir = mkdtemp()
22+
ground_truth = np.loadtxt(example_data('ds003_sub-01_mc.DVARS'))
23+
dvars = ComputeDVARS(in_file=example_data('ds003_sub-01_mc.nii.gz'),
24+
in_mask=example_data('ds003_sub-01_mc_brainmask.nii.gz'),
25+
save_all = True)
26+
os.chdir(tempdir)
27+
res = dvars.run()
28+
29+
dv1 = np.loadtxt(res.outputs.out_std)
30+
yield assert_equal, (np.abs(dv1 - ground_truth).sum()/ len(dv1)) < 0.05, True
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
1.54062
2+
1.31972
3+
0.921541
4+
1.26107
5+
0.99986
6+
0.929237
7+
0.715096
8+
1.05153
9+
1.29109
10+
0.700641
11+
0.844657
12+
0.884972
13+
0.807096
14+
0.881976
15+
0.843652
16+
0.780457
17+
1.05401
18+
1.32161
19+
0.686738
164 KB
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)