diff --git a/examples/rsfmri_vol_surface_preprocessing.py b/examples/rsfmri_vol_surface_preprocessing.py index 20b150b149..28e5455f0d 100644 --- a/examples/rsfmri_vol_surface_preprocessing.py +++ b/examples/rsfmri_vol_surface_preprocessing.py @@ -65,6 +65,7 @@ # If SPM is not in your MATLAB path you should add it here # mlab.MatlabCommand.set_default_paths('/software/matlab/spm12') +from nipype.algorithms.filters import Bandpass from nipype.algorithms.rapidart import ArtifactDetect from nipype.algorithms.misc import TSNR, CalculateMedian from nipype.interfaces.utility import Rename, Merge, IdentityInterface @@ -133,46 +134,6 @@ def median(in_files): return filename -def bandpass_filter(files, lowpass_freq, highpass_freq, fs): - """Bandpass filter the input files - - Parameters - ---------- - files: list of 4d nifti files - lowpass_freq: cutoff frequency for the low pass filter (in Hz) - highpass_freq: cutoff frequency for the high pass filter (in Hz) - fs: sampling rate (in Hz) - """ - from nipype.utils.filemanip import split_filename, list_to_filename - import numpy as np - import nibabel as nb - from nipype.utils import NUMPY_MMAP - out_files = [] - for filename in filename_to_list(files): - path, name, ext = split_filename(filename) - out_file = os.path.join(os.getcwd(), name + '_bp' + ext) - img = nb.load(filename, mmap=NUMPY_MMAP) - timepoints = img.shape[-1] - F = np.zeros((timepoints)) - lowidx = int(timepoints / 2) + 1 - if lowpass_freq > 0: - lowidx = np.round(lowpass_freq / fs * timepoints) - highidx = 0 - if highpass_freq > 0: - highidx = np.round(highpass_freq / fs * timepoints) - F[highidx:lowidx] = 1 - F = ((F + F[::-1]) > 0).astype(int) - data = img.get_data() - if np.all(F == 1): - filtered_data = data - else: - filtered_data = np.real(np.fft.ifftn(np.fft.fftn(data) * F)) - img_out = nb.Nifti1Image(filtered_data, img.affine, img.header) - img_out.to_filename(out_file) - out_files.append(out_file) - return list_to_filename(out_files) - - def motion_regressors(motion_params, order=0, derivatives=1): """Compute motion regressors upto given order and derivative @@ -733,17 +694,11 @@ def merge_files(in1, in2): wf.connect(createfilter2, 'out_files', filter2, 'design') wf.connect(mask, 'mask_file', filter2, 'mask') - bandpass = Node( - Function( - input_names=['files', 'lowpass_freq', 'highpass_freq', 'fs'], - output_names=['out_files'], - function=bandpass_filter, - imports=imports), - name='bandpass_unsmooth') - bandpass.inputs.fs = 1. / TR - bandpass.inputs.highpass_freq = highpass_freq - bandpass.inputs.lowpass_freq = lowpass_freq - wf.connect(filter2, 'out_res', bandpass, 'files') + bandpass = MapNode(Bandpass( + repetition_time=TR, freq_low=lowpass_freq, freq_hi=highpass_freq), + iterfield=['in_file'], name='bandpass_unsmooth') + + wf.connect(filter2, 'out_res', bandpass, 'in_file') """Smooth the functional data using :class:`nipype.interfaces.spm.Smooth`. """ @@ -751,11 +706,11 @@ def merge_files(in1, in2): smooth = Node(interface=spm.Smooth(), name="smooth") smooth.inputs.fwhm = vol_fwhm - wf.connect(bandpass, 'out_files', smooth, 'in_files') + wf.connect(bandpass, 'out_file', smooth, 'in_files') collector = Node(Merge(2), name='collect_streams') wf.connect(smooth, 'smoothed_files', collector, 'in1') - wf.connect(bandpass, 'out_files', collector, 'in2') + wf.connect(bandpass, 'out_file', collector, 'in2') """ Transform the remaining images. First to anatomical and then to target """ @@ -917,7 +872,7 @@ def get_names(files, suffix): wf.connect(filter1, 'out_pf', datasink, 'resting.qa.compmaps.@mc_pF') wf.connect(filter2, 'out_f', datasink, 'resting.qa.compmaps') wf.connect(filter2, 'out_pf', datasink, 'resting.qa.compmaps.@p') - wf.connect(bandpass, 'out_files', datasink, + wf.connect(bandpass, 'out_file', datasink, 'resting.timeseries.@bandpassed') wf.connect(smooth, 'smoothed_files', datasink, 'resting.timeseries.@smoothed') diff --git a/examples/rsfmri_vol_surface_preprocessing_nipy.py b/examples/rsfmri_vol_surface_preprocessing_nipy.py index d3d9887cc6..4d51e9d951 100644 --- a/examples/rsfmri_vol_surface_preprocessing_nipy.py +++ b/examples/rsfmri_vol_surface_preprocessing_nipy.py @@ -65,6 +65,7 @@ from nipype import Workflow, Node, MapNode +from nipype.algorithms.filters import Bandpass from nipype.algorithms.rapidart import ArtifactDetect from nipype.algorithms.misc import TSNR, CalculateMedian from nipype.algorithms.confounds import ACompCor @@ -142,42 +143,6 @@ def median(in_files): return filename -def bandpass_filter(files, lowpass_freq, highpass_freq, fs): - """Bandpass filter the input files - - Parameters - ---------- - files: list of 4d nifti files - lowpass_freq: cutoff frequency for the low pass filter (in Hz) - highpass_freq: cutoff frequency for the high pass filter (in Hz) - fs: sampling rate (in Hz) - """ - out_files = [] - for filename in filename_to_list(files): - path, name, ext = split_filename(filename) - out_file = os.path.join(os.getcwd(), name + '_bp' + ext) - img = nb.load(filename, mmap=NUMPY_MMAP) - timepoints = img.shape[-1] - F = np.zeros((timepoints)) - lowidx = int(timepoints / 2) + 1 - if lowpass_freq > 0: - lowidx = np.round(float(lowpass_freq) / fs * timepoints) - highidx = 0 - if highpass_freq > 0: - highidx = np.round(float(highpass_freq) / fs * timepoints) - F[highidx:lowidx] = 1 - F = ((F + F[::-1]) > 0).astype(int) - data = img.get_data() - if np.all(F == 1): - filtered_data = data - else: - filtered_data = np.real(np.fft.ifftn(np.fft.fftn(data) * F)) - img_out = nb.Nifti1Image(filtered_data, img.affine, img.header) - img_out.to_filename(out_file) - out_files.append(out_file) - return list_to_filename(out_files) - - def motion_regressors(motion_params, order=0, derivatives=1): """Compute motion regressors upto given order and derivative @@ -700,30 +665,24 @@ def merge_files(in1, in2): wf.connect(createfilter2, 'components_file', filter2, 'design') wf.connect(mask, 'mask_file', filter2, 'mask') - bandpass = Node( - Function( - input_names=['files', 'lowpass_freq', 'highpass_freq', 'fs'], - output_names=['out_files'], - function=bandpass_filter, - imports=imports), - name='bandpass_unsmooth') - bandpass.inputs.fs = 1. / TR - bandpass.inputs.highpass_freq = highpass_freq - bandpass.inputs.lowpass_freq = lowpass_freq - wf.connect(filter2, 'out_res', bandpass, 'files') + bandpass = MapNode(Bandpass( + repetition_time=TR, freq_low=lowpass_freq, freq_hi=highpass_freq), + iterfield=['in_file'], name='bandpass_unsmooth') + + wf.connect(filter2, 'out_res', bandpass, 'in_file') + """Smooth the functional data using :class:`nipype.interfaces.fsl.IsotropicSmooth`. """ - smooth = MapNode( interface=fsl.IsotropicSmooth(), name="smooth", iterfield=["in_file"]) smooth.inputs.fwhm = vol_fwhm - wf.connect(bandpass, 'out_files', smooth, 'in_file') + wf.connect(bandpass, 'out_file', smooth, 'in_file') collector = Node(Merge(2), name='collect_streams') wf.connect(smooth, 'out_file', collector, 'in1') - wf.connect(bandpass, 'out_files', collector, 'in2') + wf.connect(bandpass, 'out_file', collector, 'in2') """ Transform the remaining images. First to anatomical and then to target """ @@ -914,7 +873,7 @@ def get_names(files, suffix): [('avgwf_txt_file', 'resting.qa.tsnr'), ('summary_file', 'resting.qa.tsnr.@summary')])]) - wf.connect(bandpass, 'out_files', datasink, + wf.connect(bandpass, 'out_file', datasink, 'resting.timeseries.@bandpassed') wf.connect(smooth, 'out_file', datasink, 'resting.timeseries.@smoothed') wf.connect(createfilter1, 'out_files', datasink, diff --git a/nipype/algorithms/filters.py b/nipype/algorithms/filters.py new file mode 100644 index 0000000000..c0fa2bf540 --- /dev/null +++ b/nipype/algorithms/filters.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- +# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- +# vi: set ft=python sts=4 ts=4 sw=4 et: +""" +Signal processing tools +""" +from __future__ import (print_function, division, unicode_literals, + absolute_import) + +from ..utils.filemanip import fname_presuffix +from ..interfaces.base import (traits, TraitedSpec, SimpleInterface, + BaseInterfaceInputSpec, File) + + +class BandpassInputSpec(BaseInterfaceInputSpec): + in_file = File(exists=True, mandatory=True, desc='functional data') + freq_low = traits.Range(0.0, min=0.0, usedefault=True, + desc='low frequency cutoff (in Hz); ' + 'the default of 0 sets low pass cutoff to Nyquist') + freq_hi = traits.Range(0.0, min=0.0, usedefault=True, + desc='high frequency cutoff (in Hz); ' + 'the default of 0 sets high pass cutoff to 0') + repetition_time = traits.Either(None, traits.Range( + 0.0, min=0.0, exclude_low=True), desc='repetition_time') + + +class BandpassOutputSpec(TraitedSpec): + out_file = File(exists=True, desc='bandpass filtered functional data') + + +class Bandpass(SimpleInterface): + """ + Bandpass filtering for functional MRI timeseries + """ + input_spec = BandpassInputSpec + output_spec = BandpassOutputSpec + + def _run_interface(self, runtime): + self._results['out_file'] = _bandpass_filter( + in_file=self.inputs.in_file, + tr=self.inputs.repetition_time, + freq_low=self.inputs.freq_low, + freq_hi=self.inputs.freq_hi, + out_file=fname_presuffix( + self.inputs.in_file, + suffix='_filtered', newpath=runtime.cwd), + ) + return runtime + + +def _bandpass_filter(in_file, tr=None, freq_low=0, freq_hi=0, out_file=None): + """ + Bandpass filter the input files + + Parameters + ---------- + files : str + 4D NIfTI file + freq_low : float + cutoff frequency for the low pass filter (in Hz) + the default of 0 sets low pass cutoff to Nyquist + freq_hi : float + cutoff frequency for the high pass filter (in Hz) + the default of 0 sets high pass cutoff to 0 + tr : float + repetition time (in seconds) + out_file : str + output file name + + """ + import numpy as np + import nibabel as nb + + if freq_hi > 0 and freq_low >= freq_hi: + raise ValueError("Low-cutoff frequency can't be greater than the high-cutoff") + + img = nb.load(in_file, mmap=NUMPY_MMAP) + timepoints = img.shape[-1] + F = np.zeros((timepoints)) + + if tr is None: # If TR is not set, find in the image file header + tr = img.header.get_zooms()[3] + + sampling_rate = 1. / tr + + lowidx = timepoints // 2 + 1 # "/" replaced by "//" + if freq_low > 0: + # "np.round(..." replaced by "int(np.round(..." + lowidx = int(np.round(freq_low / sampling_rate * timepoints)) + + highidx = 0 + if freq_hi > 0: + highidx = int(np.round(freq_hi / sampling_rate * timepoints)) # same + + F[highidx:lowidx] = 1 + F = ((F + F[::-1]) > 0).astype(int) + try: + data = img.get_fdata() + except AttributeError: + data = img.get_data() + + if np.all(F): + filtered_data = data + return in_file + + filtered_data = np.real(np.fft.ifftn(np.fft.fftn(data) * F)) + img_out = nb.Nifti1Image(filtered_data, img.affine, img.header) + img_out.to_filename(out_file) + return out_file diff --git a/nipype/algorithms/tests/test_auto_Bandpass.py b/nipype/algorithms/tests/test_auto_Bandpass.py new file mode 100644 index 0000000000..23fe4e253b --- /dev/null +++ b/nipype/algorithms/tests/test_auto_Bandpass.py @@ -0,0 +1,30 @@ +# AUTO-GENERATED by tools/checkspecs.py - DO NOT EDIT +from __future__ import unicode_literals +from ..filters import Bandpass + + +def test_Bandpass_inputs(): + input_map = dict( + freq_hi=dict( + min=0.0, + usedefault=True, + ), + freq_low=dict( + min=0.0, + usedefault=True, + ), + in_file=dict(mandatory=True, ), + repetition_time=dict(), + ) + inputs = Bandpass.input_spec() + + for key, metadata in list(input_map.items()): + for metakey, value in list(metadata.items()): + assert getattr(inputs.traits()[key], metakey) == value +def test_Bandpass_outputs(): + output_map = dict(out_file=dict(), ) + outputs = Bandpass.output_spec() + + for key, metadata in list(output_map.items()): + for metakey, value in list(metadata.items()): + assert getattr(outputs.traits()[key], metakey) == value