Skip to content

Commit 4d2f10a

Browse files
author
Shoshana Berleant
committed
move compcor to confounds.py
1 parent a746633 commit 4d2f10a

File tree

3 files changed

+149
-172
lines changed

3 files changed

+149
-172
lines changed

nipype/algorithms/compcor.py

Lines changed: 0 additions & 170 deletions
This file was deleted.

nipype/algorithms/confounds.py

Lines changed: 148 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,15 @@
1919

2020
import nibabel as nb
2121
import numpy as np
22+
from scipy import linalg
2223

2324
from .. import logging
2425
from ..external.due import due, Doi, BibTeX
2526
from ..interfaces.base import (traits, TraitedSpec, BaseInterface,
2627
BaseInterfaceInputSpec, File, isdefined)
28+
from .misc import regress_poly
2729
IFLOG = logging.getLogger('interface')
2830

29-
3031
class ComputeDVARSInputSpec(BaseInterfaceInputSpec):
3132
in_file = File(exists=True, mandatory=True, desc='functional data, after HMC')
3233
in_mask = File(exists=True, mandatory=True, desc='a brain mask')
@@ -276,6 +277,152 @@ def _run_interface(self, runtime):
276277
def _list_outputs(self):
277278
return self._results
278279

280+
class CompCorInputSpec(BaseInterfaceInputSpec):
281+
realigned_file = File(exists=True, mandatory=True,
282+
desc='already realigned brain image (4D)')
283+
mask_file = File(exists=True, mandatory=False,
284+
desc='mask file that determines ROI (3D)')
285+
components_file = File('components_file.txt', exists=False,
286+
mandatory=False, usedefault=True,
287+
desc='filename to store physiological components')
288+
num_components = traits.Int(6, usedefault=True) # 6 for BOLD, 4 for ASL
289+
use_regress_poly = traits.Bool(True, usedefault=True,
290+
desc='use polynomial regression'
291+
'pre-component extraction')
292+
regress_poly_degree = traits.Range(low=1, default=1, usedefault=True,
293+
desc='the degree polynomial to use')
294+
295+
class CompCorOutputSpec(TraitedSpec):
296+
components_file = File(exists=True,
297+
desc='text file containing the noise components')
298+
299+
class CompCor(BaseInterface):
300+
'''
301+
Interface with core CompCor computation, used in aCompCor and tCompCor
302+
303+
Example
304+
-------
305+
306+
>>> ccinterface = CompCor()
307+
>>> ccinterface.inputs.realigned_file = 'functional.nii'
308+
>>> ccinterface.inputs.mask_file = 'mask.nii'
309+
>>> ccinterface.inputs.num_components = 1
310+
>>> ccinterface.inputs.use_regress_poly = True
311+
>>> ccinterface.inputs.regress_poly_degree = 2
312+
'''
313+
input_spec = CompCorInputSpec
314+
output_spec = CompCorOutputSpec
315+
316+
def _run_interface(self, runtime):
317+
imgseries = nb.load(self.inputs.realigned_file).get_data()
318+
mask = nb.load(self.inputs.mask_file).get_data()
319+
voxel_timecourses = imgseries[mask > 0]
320+
# Zero-out any bad values
321+
voxel_timecourses[np.isnan(np.sum(voxel_timecourses, axis=1)), :] = 0
322+
323+
# from paper:
324+
# "The constant and linear trends of the columns in the matrix M were
325+
# removed [prior to ...]"
326+
if self.inputs.use_regress_poly:
327+
voxel_timecourses = regress_poly(self.inputs.regress_poly_degree,
328+
voxel_timecourses)
329+
voxel_timecourses = voxel_timecourses - np.mean(voxel_timecourses,
330+
axis=1)[:, np.newaxis]
331+
332+
# "Voxel time series from the noise ROI (either anatomical or tSTD) were
333+
# placed in a matrix M of size Nxm, with time along the row dimension
334+
# and voxels along the column dimension."
335+
M = voxel_timecourses.T
336+
numvols = M.shape[0]
337+
numvoxels = M.shape[1]
338+
339+
# "[... were removed] prior to column-wise variance normalization."
340+
M = M / self._compute_tSTD(M, 1.)
341+
342+
# "The covariance matrix C = MMT was constructed and decomposed into its
343+
# principal components using a singular value decomposition."
344+
u, _, _ = linalg.svd(M, full_matrices=False)
345+
components = u[:, :self.inputs.num_components]
346+
components_file = os.path.join(os.getcwd(), self.inputs.components_file)
347+
np.savetxt(components_file, components, fmt="%.10f")
348+
return runtime
349+
350+
def _list_outputs(self):
351+
outputs = self._outputs().get()
352+
outputs['components_file'] = os.path.abspath(self.inputs.components_file)
353+
return outputs
354+
355+
def _compute_tSTD(self, M, x):
356+
stdM = np.std(M, axis=0)
357+
# set bad values to x
358+
stdM[stdM == 0] = x
359+
stdM[np.isnan(stdM)] = x
360+
return stdM
361+
362+
class TCompCorInputSpec(CompCorInputSpec):
363+
# and all the fields in CompCorInputSpec
364+
percentile_threshold = traits.Range(low=0., high=1., value=.02,
365+
exclude_low=True, exclude_high=True,
366+
usedefault=True, desc='the percentile '
367+
'used to select highest-variance '
368+
'voxels, represented by a number '
369+
'between 0 and 1, exclusive. By '
370+
'default, this value is set to .02. '
371+
'That is, the 2% of voxels '
372+
'with the highest variance are used.')
373+
374+
class TCompCor(CompCor):
375+
'''
376+
Interface for tCompCor. Computes a ROI mask based on variance of voxels.
377+
378+
Example
379+
-------
380+
381+
>>> ccinterface = TCompCor()
382+
>>> ccinterface.inputs.realigned_file = 'functional.nii'
383+
>>> ccinterface.inputs.mask_file = 'mask.nii'
384+
>>> ccinterface.inputs.num_components = 1
385+
>>> ccinterface.inputs.use_regress_poly = True
386+
>>> ccinterface.inputs.regress_poly_degree = 2
387+
>>> ccinterface.inputs.percentile_threshold = .03
388+
'''
389+
390+
input_spec = TCompCorInputSpec
391+
output_spec = CompCorOutputSpec
392+
393+
def _run_interface(self, runtime):
394+
imgseries = nb.load(self.inputs.realigned_file).get_data()
395+
396+
# From the paper:
397+
# "For each voxel time series, the temporal standard deviation is
398+
# defined as the standard deviation of the time series after the removal
399+
# of low-frequency nuisance terms (e.g., linear and quadratic drift)."
400+
imgseries = regress_poly(2, imgseries)
401+
imgseries = imgseries - np.mean(imgseries, axis=1)[:, np.newaxis]
402+
403+
time_voxels = imgseries.T
404+
num_voxels = np.prod(time_voxels.shape[1:])
405+
406+
# "To construct the tSTD noise ROI, we sorted the voxels by their
407+
# temporal standard deviation ..."
408+
tSTD = self._compute_tSTD(time_voxels, 0)
409+
sortSTD = np.sort(tSTD, axis=None) # flattened sorted matrix
410+
411+
# use percentile_threshold to pick voxels
412+
threshold_index = int(num_voxels * (1. - self.inputs.percentile_threshold))
413+
threshold_std = sortSTD[threshold_index]
414+
mask = tSTD >= threshold_std
415+
mask = mask.astype(int)
416+
417+
# save mask
418+
mask_file = 'mask.nii'
419+
nb.nifti1.save(nb.Nifti1Image(mask, np.eye(4)), mask_file)
420+
self.inputs.mask_file = mask_file
421+
422+
super(TCompCor, self)._run_interface(runtime)
423+
return runtime
424+
425+
ACompCor = CompCor
279426

280427
def compute_dvars(in_file, in_mask, remove_zerovariance=False):
281428
"""

nipype/algorithms/tests/test_compcor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# vi: set ft=python sts=4 ts=4 sw=4 et:
33
import nipype
44
from ...testing import assert_equal, assert_true, assert_false, skipif, utils
5-
from ..compcor import CompCor, TCompCor, ACompCor
5+
from ..confounds import CompCor, TCompCor, ACompCor
66

77
import unittest
88
import nibabel as nb

0 commit comments

Comments
 (0)