@@ -323,7 +323,7 @@ class CompCorInputSpec(BaseInterfaceInputSpec):
323
323
desc = ('Position of mask in `mask_files` to use - '
324
324
'first is the default.' ))
325
325
components_file = traits .Str ('components_file.txt' , usedefault = True ,
326
- desc = 'Filename to store physiological components' )
326
+ desc = 'Filename to store physiological components' )
327
327
num_components = traits .Int (6 , usedefault = True ) # 6 for BOLD, 4 for ASL
328
328
use_regress_poly = traits .Bool (True , usedefault = True ,
329
329
desc = ('use polynomial regression '
@@ -333,11 +333,19 @@ class CompCorInputSpec(BaseInterfaceInputSpec):
333
333
header_prefix = traits .Str (desc = ('the desired header for the output tsv '
334
334
'file (one column). If undefined, will '
335
335
'default to "CompCor"' ))
336
+ high_pass_filter = traits .Bool (
337
+ False , usedefault = True ,
338
+ desc = 'Use cosine basis to remove low-frequency trends pre-component '
339
+ 'extraction' )
340
+ save_hpf_basis = traits .Either (
341
+ traits .Bool , File , requires = ['high_pass_filter' ],
342
+ desc = 'Save high pass filter basis as text file' )
336
343
337
344
338
345
class CompCorOutputSpec (TraitedSpec ):
339
346
components_file = File (exists = True ,
340
347
desc = 'text file containing the noise components' )
348
+ hpf_basis_file = File (desc = 'text file containing high-pass filter basis' )
341
349
342
350
343
351
class CompCor (BaseInterface ):
@@ -403,13 +411,18 @@ def _run_interface(self, runtime):
403
411
404
412
mask_images = self ._process_masks (mask_images , imgseries .get_data ())
405
413
406
- components = compute_noise_components (imgseries . get_data (),
407
- mask_images , degree ,
408
- self .inputs .num_components )
414
+ components , hpf_basis = compute_noise_components (
415
+ imgseries . get_data (), mask_images , degree ,
416
+ self .inputs .num_components , self . inputs . high_pass_filter )
409
417
410
418
components_file = os .path .join (os .getcwd (), self .inputs .components_file )
411
419
np .savetxt (components_file , components , fmt = b"%.10f" , delimiter = '\t ' ,
412
420
header = self ._make_headers (components .shape [1 ]), comments = '' )
421
+
422
+ if self .inputs .save_hpf_basis :
423
+ hpf_basis_file = self ._list_outputs ()['hpf_basis_file' ]
424
+ np .savetxt (hpf_basis_file , hpf_basis , fmt = b'%.10f' , delimiter = '\t ' )
425
+
413
426
return runtime
414
427
415
428
def _process_masks (self , mask_images , timeseries = None ):
@@ -418,6 +431,13 @@ def _process_masks(self, mask_images, timeseries=None):
418
431
def _list_outputs (self ):
419
432
outputs = self ._outputs ().get ()
420
433
outputs ['components_file' ] = os .path .abspath (self .inputs .components_file )
434
+
435
+ save_hpf_basis = self .inputs .save_hpf_basis
436
+ if save_hpf_basis :
437
+ if isinstance (save_hpf_basis , bool ):
438
+ save_hpf_basis = os .path .abspath ('hpf_basis.txt' )
439
+ outputs ['save_hpf_basis' ] = save_hpf_basis
440
+
421
441
return outputs
422
442
423
443
def _make_headers (self , num_col ):
@@ -794,6 +814,27 @@ def is_outlier(points, thresh=3.5):
794
814
return timepoints_to_discard
795
815
796
816
817
+ def cosine_filter (data , timestep , remove_mean = False , axis = - 1 ):
818
+ datashape = data .shape
819
+ timepoints = datashape [axis ]
820
+
821
+ data = data .reshape ((- 1 , timepoints ))
822
+
823
+ design_matrix = dmtx_light (timestep * np .arange (timepoints ))
824
+
825
+ X = np .hstack (np .ones ((timepoints , 1 )), design_matrix )
826
+ betas = np .linalg .lstsq (X , data )[0 ]
827
+
828
+ if not remove_mean :
829
+ X = X [:, 1 :]
830
+ betas = betas [1 :]
831
+
832
+ residuals = data - X .dot (betas )
833
+
834
+ return residuals , design_matrix
835
+
836
+
837
+
797
838
def regress_poly (degree , data , remove_mean = True , axis = - 1 ):
798
839
"""
799
840
Returns data with degree polynomial regressed out.
@@ -886,20 +927,23 @@ def combine_mask_files(mask_files, mask_method=None, mask_index=None):
886
927
return [img ]
887
928
888
929
889
- def compute_noise_components (imgseries , mask_images , degree , num_components ):
930
+ def compute_noise_components (imgseries , mask_images , degree , num_components ,
931
+ high_pass_filter = False ):
890
932
"""Compute the noise components from the imgseries for each mask
891
933
892
934
imgseries: a nibabel img
893
935
mask_images: a list of nibabel images
894
936
degree: order of polynomial used to remove trends from the timeseries
895
937
num_components: number of noise components to return
938
+ high_pass_filter:
896
939
897
940
returns:
898
941
899
942
components: a numpy array
900
943
901
944
"""
902
945
components = None
946
+ hpf_basis = None
903
947
for img in mask_images :
904
948
mask = img .get_data ().astype (np .bool )
905
949
if imgseries .shape [:3 ] != mask .shape :
@@ -913,10 +957,16 @@ def compute_noise_components(imgseries, mask_images, degree, num_components):
913
957
# Zero-out any bad values
914
958
voxel_timecourses [np .isnan (np .sum (voxel_timecourses , axis = 1 )), :] = 0
915
959
960
+ if high_pass_filter :
961
+ # If degree == 0, remove mean in same pass
962
+ voxel_timecourses , hpf_basis = cosine_filter (
963
+ voxel_timecourses , 2.5 , remove_mean = (degree == 0 ))
964
+
916
965
# from paper:
917
966
# "The constant and linear trends of the columns in the matrix M were
918
967
# removed [prior to ...]"
919
- voxel_timecourses = regress_poly (degree , voxel_timecourses )
968
+ if degree > 0 or not high_pass_filter :
969
+ voxel_timecourses = regress_poly (degree , voxel_timecourses )
920
970
921
971
# "Voxel time series from the noise ROI (either anatomical or tSTD) were
922
972
# placed in a matrix M of size Nxm, with time along the row dimension
@@ -936,7 +986,7 @@ def compute_noise_components(imgseries, mask_images, degree, num_components):
936
986
u [:, :num_components ]))
937
987
if components is None and num_components > 0 :
938
988
raise ValueError ('No components found' )
939
- return components
989
+ return components , hpf_basis
940
990
941
991
942
992
def _compute_tSTD (M , x , axis = 0 ):
0 commit comments