32
32
class ComputeDVARSInputSpec (BaseInterfaceInputSpec ):
33
33
in_file = File (exists = True , mandatory = True , desc = 'functional data, after HMC' )
34
34
in_mask = File (exists = True , mandatory = True , desc = 'a brain mask' )
35
- remove_zerovariance = traits .Bool (False , usedefault = True ,
35
+ remove_zerovariance = traits .Bool (True , usedefault = True ,
36
36
desc = 'remove voxels with zero variance' )
37
37
save_std = traits .Bool (True , usedefault = True ,
38
38
desc = 'save standardized DVARS' )
@@ -255,7 +255,7 @@ def _run_interface(self, runtime):
255
255
'out_file' : op .abspath (self .inputs .out_file ),
256
256
'fd_average' : float (fd_res .mean ())
257
257
}
258
- np .savetxt (self .inputs .out_file , fd_res )
258
+ np .savetxt (self .inputs .out_file , fd_res , header = 'framewise_displacement' )
259
259
260
260
if self .inputs .save_plot :
261
261
tr = None
@@ -291,6 +291,8 @@ class CompCorInputSpec(BaseInterfaceInputSpec):
291
291
'pre-component extraction' )
292
292
regress_poly_degree = traits .Range (low = 1 , default = 1 , usedefault = True ,
293
293
desc = 'the degree polynomial to use' )
294
+ header = traits .Str (desc = 'the desired header for the output tsv file (one column).'
295
+ 'If undefined, will default to "CompCor"' )
294
296
295
297
class CompCorOutputSpec (TraitedSpec ):
296
298
components_file = File (exists = True ,
@@ -329,6 +331,13 @@ class CompCor(BaseInterface):
329
331
def _run_interface (self , runtime ):
330
332
imgseries = nb .load (self .inputs .realigned_file ).get_data ()
331
333
mask = nb .load (self .inputs .mask_file ).get_data ()
334
+
335
+ if imgseries .shape [:3 ] != mask .shape :
336
+ raise ValueError ('Inputs for CompCor, func {} and mask {}, do not have matching '
337
+ 'spatial dimensions ({} and {}, respectively)'
338
+ .format (self .inputs .realigned_file , self .inputs .mask_file ,
339
+ imgseries .shape [:3 ], mask .shape ))
340
+
332
341
voxel_timecourses = imgseries [mask > 0 ]
333
342
# Zero-out any bad values
334
343
voxel_timecourses [np .isnan (np .sum (voxel_timecourses , axis = 1 )), :] = 0
@@ -352,7 +361,10 @@ def _run_interface(self, runtime):
352
361
u , _ , _ = linalg .svd (M , full_matrices = False )
353
362
components = u [:, :self .inputs .num_components ]
354
363
components_file = os .path .join (os .getcwd (), self .inputs .components_file )
355
- np .savetxt (components_file , components , fmt = b"%.10f" )
364
+
365
+ self ._set_header ()
366
+ np .savetxt (components_file , components , fmt = b"%.10f" , delimiter = '\t ' ,
367
+ header = self ._make_headers (components .shape [1 ]))
356
368
return runtime
357
369
358
370
def _list_outputs (self ):
@@ -367,6 +379,26 @@ def _compute_tSTD(self, M, x):
367
379
stdM [np .isnan (stdM )] = x
368
380
return stdM
369
381
382
+ def _set_header (self , header = 'CompCor' ):
383
+ self .inputs .header = self .inputs .header if isdefined (self .inputs .header ) else header
384
+
385
+ def _make_headers (self , num_col ):
386
+ headers = []
387
+ for i in range (num_col ):
388
+ headers .append (self .inputs .header + str (i ))
389
+ return '\t ' .join (headers )
390
+
391
+
392
+ class ACompCor (CompCor ):
393
+ ''' Anatomical compcor; for input/output, see CompCor.
394
+ If the mask provided is an anatomical mask, CompCor == ACompCor '''
395
+
396
+ def __init__ (self , * args , ** kwargs ):
397
+ ''' exactly the same as compcor except the header '''
398
+ super (ACompCor , self ).__init__ (* args , ** kwargs )
399
+ self ._set_header ('aCompCor' )
400
+
401
+
370
402
class TCompCorInputSpec (CompCorInputSpec ):
371
403
# and all the fields in CompCorInputSpec
372
404
percentile_threshold = traits .Range (low = 0. , high = 1. , value = .02 ,
@@ -401,6 +433,11 @@ class TCompCor(CompCor):
401
433
def _run_interface (self , runtime ):
402
434
imgseries = nb .load (self .inputs .realigned_file ).get_data ()
403
435
436
+ if imgseries .ndim != 4 :
437
+ raise ValueError ('tCompCor expected a 4-D nifti file. Input {} has {} dimensions '
438
+ '(shape {})'
439
+ .format (self .inputs .realigned_file , imgseries .ndim , imgseries .shape ))
440
+
404
441
# From the paper:
405
442
# "For each voxel time series, the temporal standard deviation is
406
443
# defined as the standard deviation of the time series after the removal
@@ -419,18 +456,19 @@ def _run_interface(self, runtime):
419
456
threshold_index = int (num_voxels * (1. - self .inputs .percentile_threshold ))
420
457
threshold_std = sortSTD [threshold_index ]
421
458
mask = tSTD >= threshold_std
422
- mask = mask .astype (int )
459
+ mask = mask .astype (int ). T
423
460
424
461
# save mask
425
- mask_file = 'mask.nii'
462
+ mask_file = os . path . abspath ( 'mask.nii' )
426
463
nb .nifti1 .save (nb .Nifti1Image (mask , np .eye (4 )), mask_file )
464
+ IFLOG .debug ('tCompcor computed and saved mask of shape {} to mask_file {}'
465
+ .format (mask .shape , mask_file ))
427
466
self .inputs .mask_file = mask_file
467
+ self ._set_header ('tCompCor' )
428
468
429
469
super (TCompCor , self )._run_interface (runtime )
430
470
return runtime
431
471
432
- ACompCor = CompCor
433
-
434
472
class TSNRInputSpec (BaseInterfaceInputSpec ):
435
473
in_file = InputMultiPath (File (exists = True ), mandatory = True ,
436
474
desc = 'realigned 4D file or a list of 3D files' )
@@ -512,6 +550,8 @@ def regress_poly(degree, data, remove_mean=True, axis=-1):
512
550
If remove_mean is True (default), the data is demeaned (i.e. degree 0).
513
551
If remove_mean is false, the data is not.
514
552
'''
553
+ IFLOG .debug ('Performing polynomial regression on data of shape ' + str (data .shape ))
554
+
515
555
datashape = data .shape
516
556
timepoints = datashape [axis ]
517
557
@@ -570,6 +610,7 @@ def compute_dvars(in_file, in_mask, remove_zerovariance=False):
570
610
import numpy as np
571
611
import nibabel as nb
572
612
from nitime .algorithms import AR_est_YW
613
+ import warnings
573
614
574
615
func = nb .load (in_file ).get_data ().astype (np .float32 )
575
616
mask = nb .load (in_mask ).get_data ().astype (np .uint8 )
@@ -585,7 +626,7 @@ def compute_dvars(in_file, in_mask, remove_zerovariance=False):
585
626
586
627
if remove_zerovariance :
587
628
# Remove zero-variance voxels across time axis
588
- mask = zero_variance ( func , mask )
629
+ mask = zero_remove ( func_sd , mask )
589
630
590
631
idx = np .where (mask > 0 )
591
632
mfunc = func [idx [0 ], idx [1 ], idx [2 ], :]
@@ -609,31 +650,28 @@ def compute_dvars(in_file, in_mask, remove_zerovariance=False):
609
650
# standardization
610
651
dvars_stdz = dvars_nstd / diff_sd_mean
611
652
612
- # voxelwise standardization
613
- diff_vx_stdz = func_diff / np .array ([diff_sdhat ] * func_diff .shape [- 1 ]).T
614
- dvars_vx_stdz = diff_vx_stdz .std (axis = 0 , ddof = 1 )
653
+ with warnings .catch_warnings (): # catch, e.g., divide by zero errors
654
+ warnings .filterwarnings ('error' )
655
+
656
+ # voxelwise standardization
657
+ diff_vx_stdz = func_diff / np .array ([diff_sdhat ] * func_diff .shape [- 1 ]).T
658
+ dvars_vx_stdz = diff_vx_stdz .std (axis = 0 , ddof = 1 )
615
659
616
660
return (dvars_stdz , dvars_nstd , dvars_vx_stdz )
617
661
618
- def zero_variance ( func , mask ):
662
+ def zero_remove ( data , mask ):
619
663
"""
620
- Mask out voxels with zero variance across t-axis
664
+ Modify inputted mask to also mask out zero values
621
665
622
- :param numpy.ndarray func: input fMRI dataset, after motion correction
623
- :param numpy.ndarray mask: 3D brain mask
624
- :return: the 3D mask of voxels with nonzero variance across :math:`t`.
666
+ :param numpy.ndarray data: e.g. voxelwise stddev of fMRI dataset, after motion correction
667
+ :param numpy.ndarray mask: brain mask (same dimensions as data)
668
+ :return: the mask with any additional zero voxels removed (same dimensions as inputs)
625
669
:rtype: numpy.ndarray
626
670
627
671
"""
628
- idx = np .where (mask > 0 )
629
- func = func [idx [0 ], idx [1 ], idx [2 ], :]
630
- tvariance = func .var (axis = 1 )
631
- tv_mask = np .zeros_like (tvariance , dtype = np .uint8 )
632
- tv_mask [tvariance > 0 ] = 1
633
-
634
- newmask = np .zeros_like (mask , dtype = np .uint8 )
635
- newmask [idx ] = tv_mask
636
- return newmask
672
+ new_mask = mask .copy ()
673
+ new_mask [data == 0 ] = 0
674
+ return new_mask
637
675
638
676
def plot_confound (tseries , figsize , name , units = None ,
639
677
series_tr = None , normalize = False ):
0 commit comments