#Wraps command **fslstats** NodeHash_2fd0bda0 = pe.MapNode(interface = fsl.ImageStats(), name = 'NodeName_2fd0bda0', iterfield = ['in_file', 'mask_file']) NodeHash_2fd0bda0.inputs.op_string = '-p 50' #Wraps command **fslmaths** NodeHash_ffd7a90 = pe.MapNode(interface = fsl.ApplyMask(), name = 'NodeName_ffd7a90', iterfield = ['in_file', 'mask_file']) #Wraps command **fslmaths** NodeHash_255ee520 = pe.MapNode(interface = fsl.MeanImage(), name = 'NodeName_255ee520', iterfield = ['in_file']) #Custom interface wrapping function Getusan NodeHash_12d6d9d0 = pe.MapNode(interface = firstlevelhelpers.Getusan, name = 'NodeName_12d6d9d0', iterfield = ['brightness_thresh', 'in_file']) #Wraps command **susan** NodeHash_f58d230 = pe.MapNode(interface = fsl.SUSAN(), name = 'NodeName_f58d230', iterfield = ['brightness_threshold', 'in_file', 'usans']) NodeHash_f58d230.inputs.dimension = 3 NodeHash_f58d230.inputs.fwhm = 5 NodeHash_f58d230.inputs.use_median = 1 #Wraps command **fslmaths** NodeHash_10dc3c10 = pe.MapNode(interface = fsl.ApplyMask(), name = 'NodeName_10dc3c10', iterfield = ['in_file', 'mask_file']) #Custom interface wrapping function Getinormscale NodeHash_1c9d2280 = pe.MapNode(interface = firstlevelhelpers.Getinormscale, name = 'NodeName_1c9d2280', iterfield = ['medianval']) #Wraps command **fslmaths** NodeHash_2ee27f90 = pe.MapNode(interface = fsl.BinaryMaths(), name = 'NodeName_2ee27f90', iterfield = ['in_file', 'operand_value']) NodeHash_2ee27f90.inputs.operation = 'mul' #Wraps command **fslmaths**
def denoise(subject, sessions, data_dir, wd, sink, TR): #initiate min func preproc workflow wf = pe.Workflow(name='DENOISE_aCompCor') wf.base_dir = wd wf.config['execution']['crashdump_dir'] = wf.base_dir + "/crash_files" ## set fsl output type to nii.gz fsl.FSLCommand.set_default_output_type('NIFTI_GZ') # I/O nodes inputnode = pe.Node(util.IdentityInterface(fields=['subjid']), name='inputnode') inputnode.inputs.subjid = subject ds = pe.Node(nio.DataSink(base_directory=sink, parameterization=False), name='sink') #infosource to interate over sessions: COND, EXT1, EXT2 sessions_infosource = pe.Node(util.IdentityInterface(fields=['session']), name='session') sessions_infosource.iterables = [('session', sessions)] #select files templates = { 'prefiltered': 'MPP/{subject}/{session}/prefiltered_func_data.nii.gz', 'prefiltered_detrend': 'MPP/{subject}/{session}/prefiltered_func_data_detrend.nii.gz', 'prefiltered_detrend_Tmean': 'MPP/{subject}/{session}/QC/prefiltered_func_data_detrend_Tmean.nii.gz', 'prefiltered_mask': 'MPP/{subject}/{session}/prefiltered_func_data_mask.nii.gz', 'WM_msk': 'MASKS/{subject}/aparc_asec.WMmask_ero2EPI.nii.gz', 'CSF_msk': 'MASKS/{subject}/aparc_asec.CSFmask_ero0EPI.nii.gz', 'motion_par': 'MPP/{subject}/{session}/MOCO/func_data_stc_moco.par' } selectfiles = pe.Node(nio.SelectFiles(templates, base_directory=data_dir), name='selectfiles') wf.connect(inputnode, 'subjid', selectfiles, 'subject') wf.connect(sessions_infosource, 'session', selectfiles, 'session') wf.connect(sessions_infosource, 'session', ds, 'container') ########################################################################## ######################## START ###################################### ########################################################################## ########################################################################### ######################## No. 1 ###################################### #the script outputs only std DVARS DVARS = pe.Node(util.Function( input_names=['in_file', 'in_mask', 'out_std_name'], output_names=['out_std', 'out_nstd', 'out_vx_std'], function=compute_dvars), name='DVARS') DVARS.inputs.out_std_name = 'stdDVARS_pre.txt' wf.connect(selectfiles, 'prefiltered_detrend', DVARS, 'in_file') wf.connect(selectfiles, 'prefiltered_mask', DVARS, 'in_mask') wf.connect(DVARS, 'out_std', ds, 'QC.@DVARS') ########################################################################### ######################## No. 2 ###################################### # DEMAN and DETREND the data, which are used to get nuisance regressors def run_demean_detrend(in_file): import nibabel as nb import numpy as np import os from scipy.signal import detrend img = nb.load(in_file) imgseries = img.get_data().astype(np.float32) imgseries_new = detrend(imgseries, type='linear') new = nb.nifti1.Nifti1Image(imgseries_new, header=img.get_header(), affine=img.get_affine()) out_file = os.path.join(os.getcwd(), 'prefiltered_func_data_demean_detrend.nii.gz') new.to_filename(out_file) del imgseries, imgseries_new, new return out_file demean_detrend = pe.Node(util.Function(input_names=['in_file'], output_names=['out_file'], function=run_demean_detrend), name='demean_detrend') wf.connect(selectfiles, 'prefiltered', demean_detrend, 'in_file') wf.connect(demean_detrend, 'out_file', ds, 'TEMP.@demean_detrend') ########################################################################### ######################## No. 3A ###################################### #PREPARE WM_CSF MASK WM_CSF_msk = pe.Node(fsl.BinaryMaths(operation='add'), name='wm_csf_msk') wf.connect(selectfiles, 'WM_msk', WM_CSF_msk, 'in_file') wf.connect(selectfiles, 'CSF_msk', WM_CSF_msk, 'operand_file') #take the coverage of the masks from functional data (essentially multiply by the mask from functional data) func_msk = pe.Node(fsl.BinaryMaths(operation='mul', out_file='WM_CSFmsk.nii.gz'), name='func_masking') wf.connect(WM_CSF_msk, 'out_file', func_msk, 'in_file') wf.connect(selectfiles, 'prefiltered_mask', func_msk, 'operand_file') wf.connect(func_msk, 'out_file', ds, 'TEMP.@masks') ########################################################################### ######################## No. 3B ###################################### #PREPARE MOTION REGRESSSORS FRISTON 24 AND TRENDS friston24 = pe.Node(util.Function(input_names=['in_file'], output_names=['out_file'], function=calc_friston_twenty_four), name='friston24') wf.connect(selectfiles, 'motion_par', friston24, 'in_file') wf.connect(friston24, 'out_file', ds, 'TEMP.@friston24') # linear and quadratic trends trends = pe.Node(util.Function(input_names=['nr_vols'], output_names=['out_file'], function=calc_trends), name='trends') def get_nr_vols(in_file): import nibabel as nb img = nb.load(in_file) return img.shape[3] wf.connect(demean_detrend, ('out_file', get_nr_vols), trends, 'nr_vols') wf.connect(trends, 'out_file', ds, 'TEMP.@trends') ########################################################################### ######################## No. 3C ###################################### #aCOMP_COR aCompCor = pe.Node(util.Function(input_names=['in_file', 'in_mask'], output_names=['out_file'], function=calc_compcor), name='aCompCor') wf.connect(demean_detrend, 'out_file', aCompCor, 'in_file') wf.connect(func_msk, 'out_file', aCompCor, 'in_mask') wf.connect(aCompCor, 'out_file', ds, 'TEMP.@aCompCor') ########################################################################### ######################## No. 4 ###################################### #PREP the nuisance model #A is with Global Signal, and B is CompCor def mergetxt(filelist, fname): import pandas as pd import os for n, f in enumerate(filelist): if n == 0: data = pd.read_csv(f, header=None, sep='\t') else: data_new = pd.read_csv(f, header=None, sep='\t') data = pd.concat([data, data_new], axis=1) out_file = os.path.join(os.getcwd(), 'nuisance' + fname + '.mat') data.to_csv(out_file, index=False, header=None, sep='\t') return out_file merge_nuisance = pe.Node(util.Merge(3), infields=['in1', 'in2', 'in3'], name='merge_nuisance') wf.connect(aCompCor, 'out_file', merge_nuisance, 'in1') wf.connect(friston24, 'out_file', merge_nuisance, 'in2') wf.connect(trends, 'out_file', merge_nuisance, 'in3') nuisance_txt = pe.Node(util.Function(input_names=['filelist', 'fname'], output_names=['out_file'], function=mergetxt), name='nuisance_txt') nuisance_txt.inputs.fname = '_model' wf.connect(merge_nuisance, 'out', nuisance_txt, 'filelist') wf.connect(nuisance_txt, 'out_file', ds, 'TEMP.@nuisance_txt') ########################################################################### ######################## No. 5 ###################################### #run nuisance regression on prefiltered raw data regression = pe.Node(fsl.GLM(demean=True), name='regression') regression.inputs.out_res_name = 'residuals.nii.gz' regression.inputs.out_f_name = 'residuals_fstats.nii.gz' regression.inputs.out_pf_name = 'residuals_pstats.nii.gz' regression.inputs.out_z_name = 'residuals_zstats.nii.gz' wf.connect(nuisance_txt, 'out_file', regression, 'design') wf.connect(selectfiles, 'prefiltered', regression, 'in_file') wf.connect(selectfiles, 'prefiltered_mask', regression, 'mask') wf.connect(regression, 'out_f', ds, 'REGRESSION.@out_f_name') wf.connect(regression, 'out_pf', ds, 'REGRESSION.@out_pf_name') wf.connect(regression, 'out_z', ds, 'REGRESSION.@out_z_name') ######################## FIX HEADER TR AFTER FSL_GLM ################# fixhd = pe.Node(fsl.utils.CopyGeom(), name='fixhd') wf.connect(regression, 'out_res', fixhd, 'dest_file') wf.connect(selectfiles, 'prefiltered', fixhd, 'in_file') wf.connect(fixhd, 'out_file', ds, 'REGRESSION.@res_out') ########################################################################### ######################## No. 6 ###################################### #apply HP FILTER of 0.01Hz #100/1.96/2 = 25.51 hp_filter = pe.Node(fsl.maths.TemporalFilter( highpass_sigma=25.51, out_file='residuals_hp01.nii.gz'), name='highpass') wf.connect(fixhd, 'out_file', hp_filter, 'in_file') wf.connect(hp_filter, 'out_file', ds, 'TEMP.@hp') #add the mean back for smoothing addmean = pe.Node(fsl.BinaryMaths( operation='add', out_file='filtered_func_data_hp01.nii.gz'), name='addmean') wf.connect(hp_filter, 'out_file', addmean, 'in_file') wf.connect(selectfiles, 'prefiltered_detrend_Tmean', addmean, 'operand_file') wf.connect(addmean, 'out_file', ds, '@out') ########################################################################### ######################## No. 7 ###################################### ## COMPUTE POST DVARS DVARSpost = pe.Node(util.Function( input_names=['in_file', 'in_mask', 'out_std_name'], output_names=['out_std', 'out_nstd', 'out_vx_std'], function=compute_dvars), name='DVARSpost') DVARSpost.inputs.out_std_name = 'stdDVARS_post.txt' wf.connect(addmean, 'out_file', DVARSpost, 'in_file') wf.connect(selectfiles, 'prefiltered_mask', DVARSpost, 'in_mask') wf.connect(DVARSpost, 'out_std', ds, 'QC.@DVARSpost') ########################################################################### ######################## No. 8 ###################################### #SMOOTHING of 6fwhm merge_datasets = pe.Node(util.Merge(2), infields=['in1', 'in2'], name='merge_datasets') wf.connect(addmean, 'out_file', merge_datasets, 'in1') wf.connect(selectfiles, 'prefiltered_detrend', merge_datasets, 'in2') median = pe.MapNode(fsl.utils.ImageStats(op_string='-k %s -p 50'), name='median', iterfield=['in_file']) wf.connect(merge_datasets, 'out', median, 'in_file') wf.connect(selectfiles, 'prefiltered_mask', median, 'mask_file') smooth = pe.MapNode( fsl.SUSAN(fwhm=6.0), name='smooth', iterfield=['in_file', 'brightness_threshold', 'usans', 'out_file']) smooth.inputs.out_file = [ 'filtered_func_data_hp01_sm6fwhm.nii.gz', 'prefiltered_func_data_detrend_sm6fwhm.nii.gz' ] merge_usans = pe.MapNode(util.Merge(2), infields=['in1', 'in2'], name='merge_usans', iterfield=['in2']) wf.connect(selectfiles, 'prefiltered_detrend_Tmean', merge_usans, 'in1') wf.connect(median, 'out_stat', merge_usans, 'in2') def getbtthresh(medianvals): return [0.75 * val for val in medianvals] def getusans(x): return [[tuple([val[0], 0.75 * val[1]])] for val in x] wf.connect(merge_datasets, 'out', smooth, 'in_file') wf.connect(median, ('out_stat', getbtthresh), smooth, 'brightness_threshold') wf.connect(merge_usans, ('out', getusans), smooth, 'usans') wf.connect(smooth, 'smoothed_file', ds, '@smoothout') ########################################################################### ######################## RUN ###################################### wf.write_graph(dotfilename='wf.dot', graph2use='colored', format='pdf', simple_form=True) wf.run(plugin='MultiProc', plugin_args={'n_procs': 2}) #wf.run() return
def init_ica_aroma_wf(metadata, mem_gb, omp_nthreads, name='ica_aroma_wf', susan_fwhm=6.0, err_on_aroma_warn=False, aroma_melodic_dim=-200, use_fieldwarp=True): """ This workflow wraps `ICA-AROMA`_ to identify and remove motion-related independent components from a BOLD time series. The following steps are performed: #. Remove non-steady state volumes from the bold series. #. Smooth data using FSL `susan`, with a kernel width FWHM=6.0mm. #. Run FSL `melodic` outside of ICA-AROMA to generate the report #. Run ICA-AROMA #. Aggregate identified motion components (aggressive) to TSV #. Return ``classified_motion_ICs`` and ``melodic_mix`` for user to complete non-aggressive denoising in T1w space #. Calculate ICA-AROMA-identified noise components (columns named ``AROMAAggrCompXX``) Additionally, non-aggressive denoising is performed on the BOLD series resampled into MNI space. There is a current discussion on whether other confounds should be extracted before or after denoising `here <http://nbviewer.jupyter.org/github/poldracklab/\ fmriprep-notebooks/blob/922e436429b879271fa13e76767a6e73443e74d9/issue-817_\ aroma_confounds.ipynb>`__. .. workflow:: :graph2use: orig :simple_form: yes from fmriprep.workflows.bold.confounds import init_ica_aroma_wf wf = init_ica_aroma_wf(metadata={'RepetitionTime': 1.0}, mem_gb=3, omp_nthreads=1) **Parameters** standard_spaces : str Spatial normalization template used as target when that registration step was previously calculated with :py:func:`~fmriprep.workflows.bold.registration.init_bold_reg_wf`. The template must be one of the MNI templates (fMRIPrep uses ``MNI152NLin2009cAsym`` by default). metadata : dict BIDS metadata for BOLD file mem_gb : float Size of BOLD file in GB omp_nthreads : int Maximum number of threads an individual process may use name : str Name of workflow (default: ``bold_tpl_trans_wf``) susan_fwhm : float Kernel width (FWHM in mm) for the smoothing step with FSL ``susan`` (default: 6.0mm) use_fieldwarp : bool Include SDC warp in single-shot transform from BOLD to MNI err_on_aroma_warn : bool Do not fail on ICA-AROMA errors aroma_melodic_dim: int Set the dimensionality of the MELODIC ICA decomposition. Negative numbers set a maximum on automatic dimensionality estimation. Positive numbers set an exact number of components to extract. (default: -200, i.e., estimate <=200 components) **Inputs** itk_bold_to_t1 Affine transform from ``ref_bold_brain`` to T1 space (ITK format) anat2std_xfm ANTs-compatible affine-and-warp transform file name_source BOLD series NIfTI file Used to recover original information lost during processing skip_vols number of non steady state volumes bold_split Individual 3D BOLD volumes, not motion corrected bold_mask BOLD series mask in template space hmc_xforms List of affine transforms aligning each volume to ``ref_image`` in ITK format fieldwarp a :abbr:`DFM (displacements field map)` in ITK format movpar_file SPM-formatted motion parameters file **Outputs** aroma_confounds TSV of confounds identified as noise by ICA-AROMA aroma_noise_ics CSV of noise components identified by ICA-AROMA melodic_mix FSL MELODIC mixing matrix nonaggr_denoised_file BOLD series with non-aggressive ICA-AROMA denoising applied .. _ICA-AROMA: https://github.com/maartenmennes/ICA-AROMA """ workflow = Workflow(name=name) workflow.__postdesc__ = """\ Automatic removal of motion artifacts using independent component analysis [ICA-AROMA, @aroma] was performed on the *preprocessed BOLD on MNI space* time-series after removal of non-steady state volumes and spatial smoothing with an isotropic, Gaussian kernel of 6mm FWHM (full-width half-maximum). Corresponding "non-aggresively" denoised runs were produced after such smoothing. Additionally, the "aggressive" noise-regressors were collected and placed in the corresponding confounds file. """ inputnode = pe.Node(niu.IdentityInterface(fields=[ 'bold_std', 'bold_mask_std', 'movpar_file', 'name_source', 'skip_vols', 'templates', ]), name='inputnode') outputnode = pe.Node(niu.IdentityInterface(fields=[ 'aroma_confounds', 'aroma_noise_ics', 'melodic_mix', 'nonaggr_denoised_file' ]), name='outputnode') select_std = pe.Node(KeySelect(fields=['bold_mask_std', 'bold_std']), name='select_std', run_without_submitting=True) select_std.inputs.key = 'MNI152NLin6Asym' rm_non_steady_state = pe.Node(niu.Function(function=_remove_volumes, output_names=['bold_cut']), name='rm_nonsteady') calc_median_val = pe.Node(fsl.ImageStats(op_string='-k %s -p 50'), name='calc_median_val') calc_bold_mean = pe.Node(fsl.MeanImage(), name='calc_bold_mean') def _getusans_func(image, thresh): return [tuple([image, thresh])] getusans = pe.Node(niu.Function(function=_getusans_func, output_names=['usans']), name='getusans', mem_gb=0.01) smooth = pe.Node(fsl.SUSAN(fwhm=susan_fwhm), name='smooth') # melodic node melodic = pe.Node(fsl.MELODIC(no_bet=True, tr_sec=float(metadata['RepetitionTime']), mm_thresh=0.5, out_stats=True, dim=aroma_melodic_dim), name="melodic") # ica_aroma node ica_aroma = pe.Node(ICA_AROMARPT(denoise_type='nonaggr', generate_report=True, TR=metadata['RepetitionTime']), name='ica_aroma') add_non_steady_state = pe.Node(niu.Function(function=_add_volumes, output_names=['bold_add']), name='add_nonsteady') # extract the confound ICs from the results ica_aroma_confound_extraction = pe.Node( ICAConfounds(err_on_aroma_warn=err_on_aroma_warn), name='ica_aroma_confound_extraction') ds_report_ica_aroma = pe.Node(DerivativesDataSink(desc='aroma', keep_dtype=True), name='ds_report_ica_aroma', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) def _getbtthresh(medianval): return 0.75 * medianval # connect the nodes workflow.connect([ (inputnode, select_std, [('templates', 'keys'), ('bold_std', 'bold_std'), ('bold_mask_std', 'bold_mask_std')]), (inputnode, ica_aroma, [('movpar_file', 'motion_parameters')]), (inputnode, rm_non_steady_state, [('skip_vols', 'skip_vols')]), (select_std, rm_non_steady_state, [('bold_std', 'bold_file')]), (select_std, calc_median_val, [('bold_mask_std', 'mask_file')]), (rm_non_steady_state, calc_median_val, [('bold_cut', 'in_file')]), (rm_non_steady_state, calc_bold_mean, [('bold_cut', 'in_file')]), (calc_bold_mean, getusans, [('out_file', 'image')]), (calc_median_val, getusans, [('out_stat', 'thresh')]), # Connect input nodes to complete smoothing (rm_non_steady_state, smooth, [('bold_cut', 'in_file')]), (getusans, smooth, [('usans', 'usans')]), (calc_median_val, smooth, [(('out_stat', _getbtthresh), 'brightness_threshold')]), # connect smooth to melodic (smooth, melodic, [('smoothed_file', 'in_files')]), (select_std, melodic, [('bold_mask_std', 'mask')]), # connect nodes to ICA-AROMA (smooth, ica_aroma, [('smoothed_file', 'in_file')]), (select_std, ica_aroma, [('bold_mask_std', 'report_mask'), ('bold_mask_std', 'mask')]), (melodic, ica_aroma, [('out_dir', 'melodic_dir')]), # generate tsvs from ICA-AROMA (ica_aroma, ica_aroma_confound_extraction, [('out_dir', 'in_directory') ]), (inputnode, ica_aroma_confound_extraction, [('skip_vols', 'skip_vols') ]), # output for processing and reporting (ica_aroma_confound_extraction, outputnode, [('aroma_confounds', 'aroma_confounds'), ('aroma_noise_ics', 'aroma_noise_ics'), ('melodic_mix', 'melodic_mix')]), (ica_aroma, add_non_steady_state, [('nonaggr_denoised_file', 'bold_cut_file')]), (select_std, add_non_steady_state, [('bold_std', 'bold_file')]), (inputnode, add_non_steady_state, [('skip_vols', 'skip_vols')]), (add_non_steady_state, outputnode, [('bold_add', 'nonaggr_denoised_file')]), (ica_aroma, ds_report_ica_aroma, [('out_report', 'in_file')]), ]) return workflow
def create_susan_smooth(name="susan_smooth", separate_masks=True): """Create a SUSAN smoothing workflow Parameters ---------- :: name : name of workflow (default: susan_smooth) separate_masks : separate masks for each run Inputs:: inputnode.in_files : functional runs (filename or list of filenames) inputnode.fwhm : fwhm for smoothing with SUSAN (float or list of floats) inputnode.mask_file : mask used for estimating SUSAN thresholds (but not for smoothing) Outputs:: outputnode.smoothed_files : functional runs (filename or list of filenames) Example ------- >>> smooth = create_susan_smooth() >>> smooth.inputs.inputnode.in_files = 'f3.nii' >>> smooth.inputs.inputnode.fwhm = 5 >>> smooth.inputs.inputnode.mask_file = 'mask.nii' >>> smooth.run() # doctest: +SKIP """ # replaces the functionality of a "for loop" def cartesian_product(fwhms, in_files, usans, btthresh): from nipype.utils.filemanip import ensure_list # ensure all inputs are lists in_files = ensure_list(in_files) fwhms = [fwhms] if isinstance(fwhms, (int, float)) else fwhms # create cartesian product lists (s_<name> = single element of list) cart_in_file = [ s_in_file for s_in_file in in_files for s_fwhm in fwhms ] cart_fwhm = [s_fwhm for s_in_file in in_files for s_fwhm in fwhms] cart_usans = [s_usans for s_usans in usans for s_fwhm in fwhms] cart_btthresh = [ s_btthresh for s_btthresh in btthresh for s_fwhm in fwhms ] return cart_in_file, cart_fwhm, cart_usans, cart_btthresh susan_smooth = pe.Workflow(name=name) """ Set up a node to define all inputs required for the preprocessing workflow """ inputnode = pe.Node(interface=util.IdentityInterface( fields=['in_files', 'fwhm', 'mask_file']), name='inputnode') """ Smooth each run using SUSAN with the brightness threshold set to 75% of the median value for each run and a mask consituting the mean functional """ multi_inputs = pe.Node(util.Function(function=cartesian_product, output_names=[ 'cart_in_file', 'cart_fwhm', 'cart_usans', 'cart_btthresh' ]), name='multi_inputs') smooth = pe.MapNode( interface=fsl.SUSAN(), iterfield=['in_file', 'brightness_threshold', 'usans', 'fwhm'], name='smooth') """ Determine the median value of the functional runs using the mask """ if separate_masks: median = pe.MapNode(interface=fsl.ImageStats(op_string='-k %s -p 50'), iterfield=['in_file', 'mask_file'], name='median') else: median = pe.MapNode(interface=fsl.ImageStats(op_string='-k %s -p 50'), iterfield=['in_file'], name='median') susan_smooth.connect(inputnode, 'in_files', median, 'in_file') susan_smooth.connect(inputnode, 'mask_file', median, 'mask_file') """ Mask the motion corrected functional runs with the dilated mask """ if separate_masks: mask = pe.MapNode(interface=fsl.ImageMaths(suffix='_mask', op_string='-mas'), iterfield=['in_file', 'in_file2'], name='mask') else: mask = pe.MapNode(interface=fsl.ImageMaths(suffix='_mask', op_string='-mas'), iterfield=['in_file'], name='mask') susan_smooth.connect(inputnode, 'in_files', mask, 'in_file') susan_smooth.connect(inputnode, 'mask_file', mask, 'in_file2') """ Determine the mean image from each functional run """ meanfunc = pe.MapNode(interface=fsl.ImageMaths(op_string='-Tmean', suffix='_mean'), iterfield=['in_file'], name='meanfunc2') susan_smooth.connect(mask, 'out_file', meanfunc, 'in_file') """ Merge the median values with the mean functional images into a coupled list """ merge = pe.Node(interface=util.Merge(2, axis='hstack'), name='merge') susan_smooth.connect(meanfunc, 'out_file', merge, 'in1') susan_smooth.connect(median, 'out_stat', merge, 'in2') """ Define a function to get the brightness threshold for SUSAN """ susan_smooth.connect([ (inputnode, multi_inputs, [('in_files', 'in_files'), ('fwhm', 'fwhms')]), (median, multi_inputs, [(('out_stat', getbtthresh), 'btthresh')]), (merge, multi_inputs, [(('out', getusans), 'usans')]), (multi_inputs, smooth, [('cart_in_file', 'in_file'), ('cart_fwhm', 'fwhm'), ('cart_btthresh', 'brightness_threshold'), ('cart_usans', 'usans')]), ]) outputnode = pe.Node( interface=util.IdentityInterface(fields=['smoothed_files']), name='outputnode') susan_smooth.connect(smooth, 'smoothed_file', outputnode, 'smoothed_files') return susan_smooth
preproc_wf.connect(maskfunc, 'out_file', smooth_median, 'in_file') preproc_wf.connect(fs_threshold2, ('binary_file', pickfirst), smooth_median, 'mask_file') smooth_meanfunc = pe.MapNode(fsl.ImageMaths(op_string='-Tmean', suffix='_mean'), iterfield=['in_file'], name='susan_smooth_meanfunc') preproc_wf.connect(maskfunc, 'out_file', smooth_meanfunc, 'in_file') smooth_merge = pe.Node(util.Merge(2, axis='hstack'), name='susan_smooth_merge') preproc_wf.connect(smooth_meanfunc, 'out_file', smooth_merge, 'in1') preproc_wf.connect(smooth_median, 'out_stat', smooth_merge, 'in2') susan_smooth = pe.MapNode( fsl.SUSAN(), iterfield=['in_file', 'brightness_threshold', 'usans'], name='susan_smooth') susan_smooth.inputs.fwhm = 6. preproc_wf.connect(maskfunc, 'out_file', susan_smooth, 'in_file') preproc_wf.connect(smooth_median, ('out_stat', getbtthresh), susan_smooth, 'brightness_threshold') preproc_wf.connect(smooth_merge, ('out', getusans), susan_smooth, 'usans') # Mask the smoothed data with the dilated mask maskfunc2 = pe.MapNode(fsl.ImageMaths(suffix='_mask', op_string='-mas'), iterfield=['in_file'], name='susan_mask') preproc_wf.connect(susan_smooth, 'smoothed_file', maskfunc2, 'in_file') preproc_wf.connect(fs_threshold2, ('binary_file', pickfirst), maskfunc2, 'in_file2')
('sub-{subject_id}' + '_task-' + taskName + '_events.tsv')) } # Create SelectFiles node sf = Node(SelectFiles(templates, sort_filelist=True), name='sf') sf.iterables = [('subject_id', subject_list)] ########### # # FMRI PREPROCESSING NODES # ########### # smoothing with SUSAN susan = Node( fsl.SUSAN(brightness_threshold=2000.0, fwhm=6.0), # smoothing filter width (6mm, isotropic) name='susan') # masking the fMRI with a brain mask applymask = Node(fsl.ApplyMask(), name='applymask') ########### # # NODE TO CALL EVENT INFO FUNCTION # ########### taskevents = Node(interface=Function( input_names=['fileEvent'], output_names=['subject_info', 'contrast_list'], function=TaskEvents), name='taskevents')
def create_susan_smooth(name="susan_smooth", separate_masks=True): """Create a SUSAN smoothing workflow Parameters ---------- :: name : name of workflow (default: susan_smooth) separate_masks : separate masks for each run Inputs:: inputnode.in_files : functional runs (filename or list of filenames) inputnode.fwhm : fwhm for smoothing with SUSAN inputnode.mask_file : mask used for estimating SUSAN thresholds (but not for smoothing) Outputs:: outputnode.smoothed_files : functional runs (filename or list of filenames) Example ------- >>> smooth = create_susan_smooth() >>> smooth.inputs.inputnode.in_files = 'f3.nii' >>> smooth.inputs.inputnode.fwhm = 5 >>> smooth.inputs.inputnode.mask_file = 'mask.nii' >>> smooth.run() # doctest: +SKIP """ susan_smooth = pe.Workflow(name=name) """ Set up a node to define all inputs required for the preprocessing workflow """ inputnode = pe.Node(interface=util.IdentityInterface( fields=['in_files', 'fwhm', 'mask_file']), name='inputnode') """ Smooth each run using SUSAN with the brightness threshold set to 75% of the median value for each run and a mask consituting the mean functional """ smooth = pe.MapNode(interface=fsl.SUSAN(), iterfield=['in_file', 'brightness_threshold', 'usans'], name='smooth') """ Determine the median value of the functional runs using the mask """ if separate_masks: median = pe.MapNode(interface=fsl.ImageStats(op_string='-k %s -p 50'), iterfield=['in_file', 'mask_file'], name='median') else: median = pe.MapNode(interface=fsl.ImageStats(op_string='-k %s -p 50'), iterfield=['in_file'], name='median') susan_smooth.connect(inputnode, 'in_files', median, 'in_file') susan_smooth.connect(inputnode, 'mask_file', median, 'mask_file') """ Mask the motion corrected functional runs with the dilated mask """ if separate_masks: mask = pe.MapNode(interface=fsl.ImageMaths(suffix='_mask', op_string='-mas'), iterfield=['in_file', 'in_file2'], name='mask') else: mask = pe.MapNode(interface=fsl.ImageMaths(suffix='_mask', op_string='-mas'), iterfield=['in_file'], name='mask') susan_smooth.connect(inputnode, 'in_files', mask, 'in_file') susan_smooth.connect(inputnode, 'mask_file', mask, 'in_file2') """ Determine the mean image from each functional run """ meanfunc = pe.MapNode(interface=fsl.ImageMaths(op_string='-Tmean', suffix='_mean'), iterfield=['in_file'], name='meanfunc2') susan_smooth.connect(mask, 'out_file', meanfunc, 'in_file') """ Merge the median values with the mean functional images into a coupled list """ merge = pe.Node(interface=util.Merge(2, axis='hstack'), name='merge') susan_smooth.connect(meanfunc, 'out_file', merge, 'in1') susan_smooth.connect(median, 'out_stat', merge, 'in2') """ Define a function to get the brightness threshold for SUSAN """ susan_smooth.connect(inputnode, 'fwhm', smooth, 'fwhm') susan_smooth.connect(inputnode, 'in_files', smooth, 'in_file') susan_smooth.connect(median, ('out_stat', getbtthresh), smooth, 'brightness_threshold') susan_smooth.connect(merge, ('out', getusans), smooth, 'usans') outputnode = pe.Node( interface=util.IdentityInterface(fields=['smoothed_files']), name='outputnode') susan_smooth.connect(smooth, 'smoothed_file', outputnode, 'smoothed_files') return susan_smooth
name='NodeName_19cad2d0', iterfield=['in_file', 'mask_file']) #Wraps command **fslmaths** NodeHash_1ba62de0 = pe.MapNode(interface=fsl.MeanImage(), name='NodeName_1ba62de0', iterfield=['in_file']) #Custom interface wrapping function Getusan NodeHash_1b45a190 = pe.MapNode(interface=firstlevelhelpers.Getusan, name='NodeName_1b45a190', iterfield=['brightness_thresh', 'in_file']) #Wraps command **susan** NodeHash_1be88ee0 = pe.MapNode( interface=fsl.SUSAN(), name='NodeName_1be88ee0', iterfield=['brightness_threshold', 'in_file', 'usans']) NodeHash_1be88ee0.inputs.dimension = 3 NodeHash_1be88ee0.inputs.fwhm = 5 NodeHash_1be88ee0.inputs.use_median = 1 #Wraps command **fslmaths** NodeHash_1becb6b0 = pe.MapNode(interface=fsl.ApplyMask(), name='NodeName_1becb6b0', iterfield=['in_file', 'mask_file']) #Custom interface wrapping function Getinormscale NodeHash_1dce7160 = pe.MapNode(interface=firstlevelhelpers.Getinormscale, name='NodeName_1dce7160', iterfield=['medianval'])
""" Merge the median values with the mean functional images into a coupled list """ mergenode = pe.Node(interface=util.Merge(2, axis='hstack'), name='merge') preproc.connect(meanfunc2, 'out_file', mergenode, 'in1') preproc.connect(medianval, 'out_stat', mergenode, 'in2') """ Smooth each run using SUSAN with the brightness threshold set to 75% of the median value for each run and a mask constituting the mean functional """ smooth = pe.MapNode(interface=fsl.SUSAN(), iterfield=['in_file', 'brightness_threshold', 'usans'], name='smooth') """ Define a function to get the brightness threshold for SUSAN """ def getbtthresh(medianvals): return [0.75 * val for val in medianvals] def getusans(x): return [[tuple([val[0], 0.75 * val[1]])] for val in x]
def init_ica_aroma_wf( mem_gb, metadata, omp_nthreads, aroma_melodic_dim=-200, err_on_aroma_warn=False, name="ica_aroma_wf", susan_fwhm=6.0, use_fieldwarp=True, ): """ Build a workflow that runs `ICA-AROMA`_. This workflow wraps `ICA-AROMA`_ to identify and remove motion-related independent components from a BOLD time series. The following steps are performed: #. Remove non-steady state volumes from the bold series. #. Smooth data using FSL `susan`, with a kernel width FWHM=6.0mm. #. Run FSL `melodic` outside of ICA-AROMA to generate the report #. Run ICA-AROMA #. Aggregate identified motion components (aggressive) to TSV #. Return ``classified_motion_ICs`` and ``melodic_mix`` for user to complete non-aggressive denoising in T1w space #. Calculate ICA-AROMA-identified noise components (columns named ``AROMAAggrCompXX``) Additionally, non-aggressive denoising is performed on the BOLD series resampled into MNI space. There is a current discussion on whether other confounds should be extracted before or after denoising `here <http://nbviewer.jupyter.org/github/nipreps/fmriprep-notebooks/blob/922e436429b879271fa13e76767a6e73443e74d9/issue-817_aroma_confounds.ipynb>`__. .. _ICA-AROMA: https://github.com/maartenmennes/ICA-AROMA Workflow Graph .. workflow:: :graph2use: orig :simple_form: yes from fprodents.workflows.bold.confounds import init_ica_aroma_wf wf = init_ica_aroma_wf( mem_gb=3, metadata={'RepetitionTime': 1.0}, omp_nthreads=1) Parameters ---------- metadata : :obj:`dict` BIDS metadata for BOLD file mem_gb : :obj:`float` Size of BOLD file in GB omp_nthreads : :obj:`int` Maximum number of threads an individual process may use name : :obj:`str` Name of workflow (default: ``bold_tpl_trans_wf``) susan_fwhm : :obj:`float` Kernel width (FWHM in mm) for the smoothing step with FSL ``susan`` (default: 6.0mm) use_fieldwarp : :obj:`bool` Include SDC warp in single-shot transform from BOLD to MNI err_on_aroma_warn : :obj:`bool` Do not fail on ICA-AROMA errors aroma_melodic_dim : :obj:`int` Set the dimensionality of the MELODIC ICA decomposition. Negative numbers set a maximum on automatic dimensionality estimation. Positive numbers set an exact number of components to extract. (default: -200, i.e., estimate <=200 components) Inputs ------ itk_bold_to_t1 Affine transform from ``ref_bold_brain`` to T1 space (ITK format) anat2std_xfm ANTs-compatible affine-and-warp transform file name_source BOLD series NIfTI file Used to recover original information lost during processing skip_vols number of non steady state volumes bold_split Individual 3D BOLD volumes, not motion corrected bold_mask BOLD series mask in template space hmc_xforms List of affine transforms aligning each volume to ``ref_image`` in ITK format fieldwarp a :abbr:`DFM (displacements field map)` in ITK format movpar_file SPM-formatted motion parameters file Outputs ------- aroma_confounds TSV of confounds identified as noise by ICA-AROMA aroma_noise_ics CSV of noise components identified by ICA-AROMA melodic_mix FSL MELODIC mixing matrix nonaggr_denoised_file BOLD series with non-aggressive ICA-AROMA denoising applied """ from niworkflows.engine.workflows import LiterateWorkflow as Workflow from niworkflows.interfaces.segmentation import ICA_AROMARPT from niworkflows.interfaces.utility import KeySelect, TSV2JSON workflow = Workflow(name=name) workflow.__postdesc__ = """\ Automatic removal of motion artifacts using independent component analysis [ICA-AROMA, @aroma] was performed on the *preprocessed BOLD on MNI space* time-series after removal of non-steady state volumes and spatial smoothing with an isotropic, Gaussian kernel of 6mm FWHM (full-width half-maximum). Corresponding "non-aggresively" denoised runs were produced after such smoothing. Additionally, the "aggressive" noise-regressors were collected and placed in the corresponding confounds file. """ inputnode = pe.Node( niu.IdentityInterface(fields=[ "bold_std", "bold_mask_std", "movpar_file", "name_source", "skip_vols", "spatial_reference", ]), name="inputnode", ) outputnode = pe.Node( niu.IdentityInterface(fields=[ "aroma_confounds", "aroma_noise_ics", "melodic_mix", "nonaggr_denoised_file", "aroma_metadata", ]), name="outputnode", ) # extract out to BOLD base select_std = pe.Node( KeySelect(fields=["bold_mask_std", "bold_std"]), name="select_std", run_without_submitting=True, ) select_std.inputs.key = "MNI152NLin6Asym_res-2" rm_non_steady_state = pe.Node( niu.Function(function=_remove_volumes, output_names=["bold_cut"]), name="rm_nonsteady", ) calc_median_val = pe.Node(fsl.ImageStats(op_string="-k %s -p 50"), name="calc_median_val") calc_bold_mean = pe.Node(fsl.MeanImage(), name="calc_bold_mean") def _getusans_func(image, thresh): return [tuple([image, thresh])] getusans = pe.Node( niu.Function(function=_getusans_func, output_names=["usans"]), name="getusans", mem_gb=0.01, ) smooth = pe.Node(fsl.SUSAN(fwhm=susan_fwhm), name="smooth") # melodic node melodic = pe.Node( fsl.MELODIC( no_bet=True, tr_sec=float(metadata["RepetitionTime"]), mm_thresh=0.5, out_stats=True, dim=aroma_melodic_dim, ), name="melodic", ) # ica_aroma node ica_aroma = pe.Node( ICA_AROMARPT( denoise_type="nonaggr", generate_report=True, TR=metadata["RepetitionTime"], args="-np", ), name="ica_aroma", ) add_non_steady_state = pe.Node( niu.Function(function=_add_volumes, output_names=["bold_add"]), name="add_nonsteady", ) # extract the confound ICs from the results ica_aroma_confound_extraction = pe.Node( ICAConfounds(err_on_aroma_warn=err_on_aroma_warn), name="ica_aroma_confound_extraction", ) ica_aroma_metadata_fmt = pe.Node( TSV2JSON( index_column="IC", output=None, enforce_case=True, additional_metadata={ "Method": { "Name": "ICA-AROMA", "Version": getenv("AROMA_VERSION", "n/a"), } }, ), name="ica_aroma_metadata_fmt", ) ds_report_ica_aroma = pe.Node( DerivativesDataSink(desc="aroma", datatype="figures", dismiss_entities=("echo", )), name="ds_report_ica_aroma", run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB, ) def _getbtthresh(medianval): return 0.75 * medianval # connect the nodes # fmt:off workflow.connect([ (inputnode, select_std, [('spatial_reference', 'keys'), ('bold_std', 'bold_std'), ('bold_mask_std', 'bold_mask_std')]), (inputnode, ica_aroma, [('movpar_file', 'motion_parameters')]), (inputnode, rm_non_steady_state, [('skip_vols', 'skip_vols')]), (select_std, rm_non_steady_state, [('bold_std', 'bold_file')]), (select_std, calc_median_val, [('bold_mask_std', 'mask_file')]), (rm_non_steady_state, calc_median_val, [('bold_cut', 'in_file')]), (rm_non_steady_state, calc_bold_mean, [('bold_cut', 'in_file')]), (calc_bold_mean, getusans, [('out_file', 'image')]), (calc_median_val, getusans, [('out_stat', 'thresh')]), # Connect input nodes to complete smoothing (rm_non_steady_state, smooth, [('bold_cut', 'in_file')]), (getusans, smooth, [('usans', 'usans')]), (calc_median_val, smooth, [(('out_stat', _getbtthresh), 'brightness_threshold')]), # connect smooth to melodic (smooth, melodic, [('smoothed_file', 'in_files')]), (select_std, melodic, [('bold_mask_std', 'mask')]), # connect nodes to ICA-AROMA (smooth, ica_aroma, [('smoothed_file', 'in_file')]), (select_std, ica_aroma, [('bold_mask_std', 'report_mask'), ('bold_mask_std', 'mask')]), (melodic, ica_aroma, [('out_dir', 'melodic_dir')]), # generate tsvs from ICA-AROMA (ica_aroma, ica_aroma_confound_extraction, [('out_dir', 'in_directory') ]), (inputnode, ica_aroma_confound_extraction, [('skip_vols', 'skip_vols') ]), (ica_aroma_confound_extraction, ica_aroma_metadata_fmt, [('aroma_metadata', 'in_file')]), # output for processing and reporting (ica_aroma_confound_extraction, outputnode, [('aroma_confounds', 'aroma_confounds'), ('aroma_noise_ics', 'aroma_noise_ics'), ('melodic_mix', 'melodic_mix')]), (ica_aroma_metadata_fmt, outputnode, [('output', 'aroma_metadata')]), (ica_aroma, add_non_steady_state, [('nonaggr_denoised_file', 'bold_cut_file')]), (select_std, add_non_steady_state, [('bold_std', 'bold_file')]), (inputnode, add_non_steady_state, [('skip_vols', 'skip_vols')]), (add_non_steady_state, outputnode, [('bold_add', 'nonaggr_denoised_file')]), (ica_aroma, ds_report_ica_aroma, [('out_report', 'in_file')]), ]) # fmt:on return workflow
#runinfo.inputs.thr = thr # set threshold of scrubbing ## adding node for the saveScrub functions svScrub = pe.Node(util.Function( input_names = ['regressors_file', 'thr'], output_names = ['perFile'], function = saveScrub), name = 'svScrub' ) svScrub.inputs.thr = thr # %% skip = pe.Node(interface=fsl.ExtractROI(), name = 'skip') skip.inputs.t_min = removeTR skip.inputs.t_size = lastTR # %% susan = pe.Node(interface=fsl.SUSAN(), name = 'susan') #create_susan_smooth() susan.inputs.fwhm = fwhm susan.inputs.brightness_threshold = 1000.0 # %% modelfit = pe.Workflow(name='fsl_fit', base_dir= output_dir) """ Use :class:`nipype.algorithms.modelgen.SpecifyModel` to generate design information. """ modelspec = pe.Node(interface=model.SpecifyModel(), name="modelspec") modelspec.inputs.input_units = 'scans' modelspec.inputs.time_repetition = tr modelspec.inputs.high_pass_filter_cutoff= 120 """
def init_ica_aroma_wf( dt, aroma_melodic_dim=-200, err_on_aroma_warn=False, susan_fwhm=6.0, name='ica_aroma_wf', ): """ Build a workflow that runs `ICA-AROMA`_. This workflow wraps `ICA-AROMA`_ to identify and remove motion-related independent components from a BOLD time series. The following steps are performed: #. Remove non-steady state volumes from the bold series. #. Smooth data using FSL `susan`, with a kernel width FWHM=6.0mm. #. Run FSL `melodic` outside of ICA-AROMA to generate the report #. Run ICA-AROMA #. Aggregate identified motion components (aggressive) to TSV #. Return ``classified_motion_ICs`` and ``melodic_mix`` for user to complete non-aggressive denoising in T1w space #. Calculate ICA-AROMA-identified noise components (columns named ``AROMAAggrCompXX``) There is a current discussion on whether other confounds should be extracted before or after denoising `here <http://nbviewer.jupyter.org/github/nipreps/fmriprep-notebooks/blob/922e436429b879271fa13e76767a6e73443e74d9/issue-817_aroma_confounds.ipynb>`__. .. _ICA-AROMA: https://github.com/maartenmennes/ICA-AROMA Workflow Graph .. workflow:: :graph2use: orig :simple_form: yes from ecp.workflows.confounds import init_ica_aroma_wf wf = init_ica_aroma_wf( dt=1.0) Parameters ---------- dt : :obj:`float` bold repetition time aroma_melodic_dim : :obj:`int` Set the dimensionality of the MELODIC ICA decomposition. Negative numbers set a maximum on automatic dimensionality estimation. Positive numbers set an exact number of components to extract. (default: -200, i.e., estimate <=200 components) err_on_aroma_warn : :obj:`bool` Do not fail on ICA-AROMA errors susan_fwhm : :obj:`float` Kernel width (FWHM in mm) for the smoothing step with FSL ``susan`` (default: 6.0mm) name : :obj:`str` Name of workflow (default: ``ica_aroma_wf``) Inputs ------ bold_std BOLD series NIfTI file in MNI152NLin6Asym space bold_mask_std BOLD mask for MNI152NLin6Asym space movpar_file movement parameter file skip_vols number of non steady state volumes Outputs ------- aroma_confounds TSV of confounds identified as noise by ICA-AROMA aroma_noise_ics CSV of noise components identified by ICA-AROMA melodic_mix FSL MELODIC mixing matrix aroma_metatdata metadata out_report aroma out report """ from niworkflows.engine.workflows import LiterateWorkflow as Workflow from niworkflows.interfaces.segmentation import ICA_AROMARPT from niworkflows.interfaces.utility import KeySelect from niworkflows.interfaces.utils import TSV2JSON workflow = Workflow(name=name) workflow.__postdesc__ = """\ Automatic removal of motion artifacts using independent component analysis [ICA-AROMA, @aroma] was performed on the *preprocessed BOLD on MNI space* time-series after removal of non-steady state volumes and spatial smoothing with an isotropic, Gaussian kernel of 6mm FWHM (full-width half-maximum). The "aggressive" noise-regressors were collected and placed in the corresponding confounds file. """ inputnode = pe.Node(niu.IdentityInterface(fields=[ 'bold_std', 'bold_mask_std', 'movpar_file', 'skip_vols', ]), name='inputnode') outputnode = pe.Node(niu.IdentityInterface(fields=[ 'aroma_confounds', 'aroma_noise_ics', 'melodic_mix', 'aroma_metadata', 'out_report' ]), name='outputnode') # extract out to BOLD base rm_non_steady_state = pe.Node(Trim(), name='rm_nonsteady') trim_movement = pe.Node(TrimMovement(), name='trim_movement') calc_median_val = pe.Node(fsl.ImageStats(op_string='-k %s -p 50'), name='calc_median_val') calc_bold_mean = pe.Node(fsl.MeanImage(), name='calc_bold_mean') def _getusans_func(image, thresh): return [tuple([image, thresh])] getusans = pe.Node(niu.Function(function=_getusans_func, output_names=['usans']), name='getusans', mem_gb=0.01) smooth = pe.Node(fsl.SUSAN(fwhm=susan_fwhm), name='smooth') # melodic node melodic = pe.Node(fsl.MELODIC(no_bet=True, tr_sec=dt, mm_thresh=0.5, out_stats=True, dim=aroma_melodic_dim), name="melodic") # ica_aroma node ica_aroma = pe.Node(ICA_AROMARPT(denoise_type='no', generate_report=True, TR=dt, args='-np'), name='ica_aroma') # extract the confound ICs from the results ica_aroma_confound_extraction = pe.Node( ICAConfounds(err_on_aroma_warn=err_on_aroma_warn), name='ica_aroma_confound_extraction') ica_aroma_metadata_fmt = pe.Node(TSV2JSON(index_column='IC', output=None, enforce_case=True, additional_metadata={ 'Method': { 'Name': 'ICA-AROMA', 'Version': getenv( 'AROMA_VERSION', 'n/a') } }), name='ica_aroma_metadata_fmt') def _getbtthresh(medianval): return 0.75 * medianval # connect the nodes workflow.connect([ (inputnode, ica_aroma, [('movpar_file', 'motion_parameters')]), (inputnode, rm_non_steady_state, [('skip_vols', 'begin_index')]), (inputnode, rm_non_steady_state, [('bold_std', 'in_file')]), (inputnode, calc_median_val, [('bold_mask_std', 'mask_file')]), (inputnode, trim_movement, [('movpar_file', 'movpar_file')]), (inputnode, trim_movement, [('skip_vols', 'skip_vols')]), (rm_non_steady_state, calc_median_val, [('out_file', 'in_file')]), (rm_non_steady_state, calc_bold_mean, [('out_file', 'in_file')]), (calc_bold_mean, getusans, [('out_file', 'image')]), (calc_median_val, getusans, [('out_stat', 'thresh')]), # Connect input nodes to complete smoothing (rm_non_steady_state, smooth, [('out_file', 'in_file')]), (getusans, smooth, [('usans', 'usans')]), (calc_median_val, smooth, [(('out_stat', _getbtthresh), 'brightness_threshold')]), # connect smooth to melodic (smooth, melodic, [('smoothed_file', 'in_files')]), (inputnode, melodic, [('bold_mask_std', 'mask')]), # connect nodes to ICA-AROMA (smooth, ica_aroma, [('smoothed_file', 'in_file')]), (inputnode, ica_aroma, [('bold_mask_std', 'report_mask'), ('bold_mask_std', 'mask')]), (melodic, ica_aroma, [('out_dir', 'melodic_dir')]), # generate tsvs from ICA-AROMA (ica_aroma, ica_aroma_confound_extraction, [('out_dir', 'in_directory') ]), (inputnode, ica_aroma_confound_extraction, [('skip_vols', 'skip_vols') ]), (ica_aroma_confound_extraction, ica_aroma_metadata_fmt, [('aroma_metadata', 'in_file')]), # output for processing and reporting (ica_aroma_confound_extraction, outputnode, [('aroma_confounds', 'aroma_confounds'), ('aroma_noise_ics', 'aroma_noise_ics'), ('melodic_mix', 'melodic_mix')]), (ica_aroma_metadata_fmt, outputnode, [('output', 'aroma_metadata')]), (ica_aroma, outputnode, [('out_report', 'out_report')]), ]) return workflow
# Calculate the mean functional smooth_meanfunc = pe.MapNode(fsl.ImageMaths(op_string='-Tmean', suffix='_mean'), iterfield=['in_file'], name='smooth_meanfunc') psb6351_wf.connect(maskfunc, 'out_file', smooth_meanfunc, 'in_file') smooth_merge = pe.Node(util.Merge(2, axis='hstack'), name='smooth_merge') psb6351_wf.connect(smooth_meanfunc, 'out_file', smooth_merge, 'in1') psb6351_wf.connect(smooth_median, 'out_stat', smooth_merge, 'in2') # Below is the code for smoothing using the susan algorithm from FSL that # limits smoothing based on different tissue classes smooth = pe.MapNode(fsl.SUSAN(), iterfield=['in_file', 'brightness_threshold', 'usans', 'fwhm'], name='smooth') smooth.inputs.fwhm=[2.0, 4.0, 6.0, 8.0, 10.0, 12.0] psb6351_wf.connect(maskfunc, 'out_file', smooth, 'in_file') psb6351_wf.connect(smooth_median, ('out_stat', getbtthresh), smooth, 'brightness_threshold') psb6351_wf.connect(smooth_merge, ('out', getusans), smooth, 'usans') # Below is the node that collects all the data and saves # the outputs that I am interested in. Here in this node # I use the substitutions input combined with the earlier # function to get rid of nesting datasink = pe.Node(nio.DataSink(), name="datasink") datasink.inputs.base_directory = os.path.join(base_dir, 'derivatives/preproc') datasink.inputs.container = f'sub-{sids[0]}' psb6351_wf.connect(tshifter, 'out_file', datasink, 'sltime_corr')
name='maskfunc2') #Determine the mean image from each functional run meanfunc2 = pe.MapNode(interface=fsl.ImageMaths(op_string='-Tmean', suffix='_mean'), iterfield=['in_file'], name='meanfunc2') #Merge the median values with the mean functional images into a coupled list mergenode = pe.Node(interface=util.Merge(2, axis='hstack'), name='merge') #Smooth each run using SUSAN #brightness threshold set to xx% of the median value for each run by function 'getbtthresh' #and a mask constituting the mean functional smooth = pe.MapNode(interface=fsl.SUSAN(fwhm = 5.), iterfield=['in_file', 'brightness_threshold','usans'], name='smooth' ) #Mask the smoothed data with the dilated mask maskfunc3 = pe.MapNode(interface=fsl.ImageMaths(suffix='_mask', op_string='-mas'), iterfield=['in_file'], name='maskfunc3') #Scale each volume of the run so that the median value of the run is set to 10000 intnorm = pe.MapNode(interface=fsl.ImageMaths(suffix='_intnorm'), iterfield=['in_file','op_string'], name='intnorm')
def init_ica_aroma_wf(template, metadata, mem_gb, omp_nthreads, name='ica_aroma_wf', susan_fwhm=6.0, ignore_aroma_err=False, aroma_melodic_dim=None, use_fieldwarp=True): """ This workflow wraps `ICA-AROMA`_ to identify and remove motion-related independent components from a BOLD time series. The following steps are performed: #. Smooth data using FSL `susan`, with a kernel width FWHM=6.0mm. #. Run FSL `melodic` outside of ICA-AROMA to generate the report #. Run ICA-AROMA #. Aggregate identified motion components (aggressive) to TSV #. Return ``classified_motion_ICs`` and ``melodic_mix`` for user to complete non-aggressive denoising in T1w space #. Calculate ICA-AROMA-identified noise components (columns named ``AROMAAggrCompXX``) Additionally, non-aggressive denoising is performed on the BOLD series resampled into MNI space. There is a current discussion on whether other confounds should be extracted before or after denoising `here <http://nbviewer.jupyter.org/github/poldracklab/\ fmriprep-notebooks/blob/922e436429b879271fa13e76767a6e73443e74d9/issue-817_\ aroma_confounds.ipynb>`__. .. workflow:: :graph2use: orig :simple_form: yes from fmriprep.workflows.bold.confounds import init_ica_aroma_wf wf = init_ica_aroma_wf(template='MNI152NLin2009cAsym', metadata={'RepetitionTime': 1.0}, mem_gb=3, omp_nthreads=1) **Parameters** template : str Spatial normalization template used as target when that registration step was previously calculated with :py:func:`~fmriprep.workflows.bold.registration.init_bold_reg_wf`. The template must be one of the MNI templates (fMRIPrep uses ``MNI152NLin2009cAsym`` by default). metadata : dict BIDS metadata for BOLD file mem_gb : float Size of BOLD file in GB omp_nthreads : int Maximum number of threads an individual process may use name : str Name of workflow (default: ``bold_mni_trans_wf``) susan_fwhm : float Kernel width (FWHM in mm) for the smoothing step with FSL ``susan`` (default: 6.0mm) use_fieldwarp : bool Include SDC warp in single-shot transform from BOLD to MNI ignore_aroma_err : bool Do not fail on ICA-AROMA errors aroma_melodic_dim: int or None Set the dimensionality of the Melodic ICA decomposition If None, MELODIC automatically estimates dimensionality. **Inputs** bold_mni BOLD series, resampled to template space movpar_file SPM-formatted motion parameters file bold_mask_mni BOLD series mask in template space **Outputs** aroma_confounds TSV of confounds identified as noise by ICA-AROMA aroma_noise_ics CSV of noise components identified by ICA-AROMA melodic_mix FSL MELODIC mixing matrix nonaggr_denoised_file BOLD series with non-aggressive ICA-AROMA denoising applied .. _ICA-AROMA: https://github.com/rhr-pruim/ICA-AROMA """ workflow = Workflow(name=name) workflow.__postdesc__ = """\ Automatic removal of motion artifacts using independent component analysis [ICA-AROMA, @aroma] was performed on the *preprocessed BOLD on MNI space* time-series after a spatial smoothing with an isotropic, Gaussian kernel of 6mm FWHM (full-width half-maximum). Corresponding "non-aggresively" denoised runs were produced after such smoothing. Additionally, the "aggressive" noise-regressors were collected and placed in the corresponding confounds file. """ inputnode = pe.Node(niu.IdentityInterface(fields=[ 'itk_bold_to_t1', 't1_2_mni_forward_transform', 'name_source', 'bold_split', 'bold_mask', 'hmc_xforms', 'fieldwarp', 'movpar_file' ]), name='inputnode') outputnode = pe.Node(niu.IdentityInterface(fields=[ 'aroma_confounds', 'aroma_noise_ics', 'melodic_mix', 'nonaggr_denoised_file' ]), name='outputnode') bold_mni_trans_wf = init_bold_mni_trans_wf(template=template, mem_gb=mem_gb, omp_nthreads=omp_nthreads, template_out_grid=os.path.join( get_mni_icbm152_linear(), '2mm_T1.nii.gz'), use_compression=False, use_fieldwarp=use_fieldwarp, name='bold_mni_trans_wf') bold_mni_trans_wf.__desc__ = None calc_median_val = pe.Node(fsl.ImageStats(op_string='-k %s -p 50'), name='calc_median_val') calc_bold_mean = pe.Node(fsl.MeanImage(), name='calc_bold_mean') def _getusans_func(image, thresh): return [tuple([image, thresh])] getusans = pe.Node(niu.Function(function=_getusans_func, output_names=['usans']), name='getusans', mem_gb=0.01) smooth = pe.Node(fsl.SUSAN(fwhm=susan_fwhm), name='smooth') # melodic node melodic = pe.Node(fsl.MELODIC(no_bet=True, tr_sec=float(metadata['RepetitionTime']), mm_thresh=0.5, out_stats=True), name="melodic") if aroma_melodic_dim is not None: melodic.inputs.dim = aroma_melodic_dim # ica_aroma node ica_aroma = pe.Node(ICA_AROMARPT(denoise_type='nonaggr', generate_report=True, TR=metadata['RepetitionTime']), name='ica_aroma') # extract the confound ICs from the results ica_aroma_confound_extraction = pe.Node( ICAConfounds(ignore_aroma_err=ignore_aroma_err), name='ica_aroma_confound_extraction') ds_report_ica_aroma = pe.Node(DerivativesDataSink(suffix='ica_aroma'), name='ds_report_ica_aroma', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) def _getbtthresh(medianval): return 0.75 * medianval # connect the nodes workflow.connect([ (inputnode, bold_mni_trans_wf, [('name_source', 'inputnode.name_source'), ('bold_split', 'inputnode.bold_split'), ('bold_mask', 'inputnode.bold_mask'), ('hmc_xforms', 'inputnode.hmc_xforms'), ('itk_bold_to_t1', 'inputnode.itk_bold_to_t1'), ('t1_2_mni_forward_transform', 'inputnode.t1_2_mni_forward_transform'), ('fieldwarp', 'inputnode.fieldwarp')]), (inputnode, ica_aroma, [('movpar_file', 'motion_parameters')]), (bold_mni_trans_wf, calc_median_val, [('outputnode.bold_mni', 'in_file'), ('outputnode.bold_mask_mni', 'mask_file')]), (bold_mni_trans_wf, calc_bold_mean, [('outputnode.bold_mni', 'in_file') ]), (calc_bold_mean, getusans, [('out_file', 'image')]), (calc_median_val, getusans, [('out_stat', 'thresh')]), # Connect input nodes to complete smoothing (bold_mni_trans_wf, smooth, [('outputnode.bold_mni', 'in_file')]), (getusans, smooth, [('usans', 'usans')]), (calc_median_val, smooth, [(('out_stat', _getbtthresh), 'brightness_threshold')]), # connect smooth to melodic (smooth, melodic, [('smoothed_file', 'in_files')]), (bold_mni_trans_wf, melodic, [('outputnode.bold_mask_mni', 'mask')]), # connect nodes to ICA-AROMA (smooth, ica_aroma, [('smoothed_file', 'in_file')]), (bold_mni_trans_wf, ica_aroma, [('outputnode.bold_mask_mni', 'report_mask')]), (melodic, ica_aroma, [('out_dir', 'melodic_dir')]), # generate tsvs from ICA-AROMA (ica_aroma, ica_aroma_confound_extraction, [('out_dir', 'in_directory') ]), # output for processing and reporting (ica_aroma_confound_extraction, outputnode, [('aroma_confounds', 'aroma_confounds'), ('aroma_noise_ics', 'aroma_noise_ics'), ('melodic_mix', 'melodic_mix')]), # TODO change melodic report to reflect noise and non-noise components (ica_aroma, outputnode, [('nonaggr_denoised_file', 'nonaggr_denoised_file')]), (ica_aroma, ds_report_ica_aroma, [('out_report', 'in_file')]), ]) return workflow
def create_workflow(func_runs, subject_id, subjects_dir, fwhm, slice_times, highpass_frequency, lowpass_frequency, TR, sink_directory, use_fsl_bp, num_components, whichvol, name='wmaze'): wf = pe.Workflow(name=name) datasource = pe.Node(nio.DataGrabber(infields=['subject_id', 'run'], outfields=['func']), name='datasource') datasource.inputs.subject_id = subject_id datasource.inputs.run = func_runs datasource.inputs.template = '/home/data/madlab/data/mri/wmaze/%s/rsfmri/rest_run%03d/rest.nii.gz' datasource.inputs.sort_filelist = True # Rename files in case they are named identically name_unique = pe.MapNode(util.Rename(format_string='wmaze_rest_%(run)02d'), iterfield=['in_file', 'run'], name='rename') name_unique.inputs.keep_ext = True name_unique.inputs.run = func_runs wf.connect(datasource, 'func', name_unique, 'in_file') # Define the outputs for the preprocessing workflow output_fields = [ 'reference', 'motion_parameters', 'motion_parameters_plusDerivs', 'motionandoutlier_noise_file', 'noise_components', 'realigned_files', 'motion_plots', 'mask_file', 'smoothed_files', 'reg_file', 'reg_cost', 'reg_fsl_file', 'artnorm_files', 'artoutlier_files', 'artdisplacement_files', 'tsnr_file' ] outputnode = pe.Node(util.IdentityInterface(fields=output_fields), name='outputspec') # Convert functional images to float representation img2float = pe.MapNode(fsl.ImageMaths(out_data_type='float', op_string='', suffix='_dtype'), iterfield=['in_file'], name='img2float') wf.connect(name_unique, 'out_file', img2float, 'in_file') # Run AFNI's despike. This is always run, however, whether this is fed to # realign depends on the input configuration despiker = pe.MapNode(afni.Despike(outputtype='NIFTI_GZ'), iterfield=['in_file'], name='despike') num_threads = 4 despiker.inputs.environ = {'OMP_NUM_THREADS': '%d' % num_threads} despiker.plugin_args = {'bsub_args': '-n %d' % num_threads} despiker.plugin_args = {'bsub_args': '-R "span[hosts=1]"'} wf.connect(img2float, 'out_file', despiker, 'in_file') # Extract the first volume of the first run as the reference extractref = pe.Node(fsl.ExtractROI(t_size=1), iterfield=['in_file'], name="extractref") wf.connect(despiker, ('out_file', pickfirst), extractref, 'in_file') wf.connect(despiker, ('out_file', pickvol, 0, whichvol), extractref, 't_min') wf.connect(extractref, 'roi_file', outputnode, 'reference') if slice_times is not None: # Simultaneous motion and slice timing correction with Nipy algorithm motion_correct = pe.Node(nipy.SpaceTimeRealigner(), name='motion_correct') motion_correct.inputs.tr = TR motion_correct.inputs.slice_times = slice_times motion_correct.inputs.slice_info = 2 motion_correct.plugin_args = { 'bsub_args': '-n %s' % os.environ['OMP_NUM_THREADS'] } motion_correct.plugin_args = {'bsub_args': '-R "span[hosts=1]"'} wf.connect(despiker, 'out_file', motion_correct, 'in_file') wf.connect(motion_correct, 'par_file', outputnode, 'motion_parameters') wf.connect(motion_correct, 'out_file', outputnode, 'realigned_files') else: # Motion correct functional runs to the reference (1st volume of 1st run) motion_correct = pe.MapNode(fsl.MCFLIRT(save_mats=True, save_plots=True, interpolation='sinc'), name='motion_correct', iterfield=['in_file']) wf.connect(despiker, 'out_file', motion_correct, 'in_file') wf.connect(extractref, 'roi_file', motion_correct, 'ref_file') wf.connect(motion_correct, 'par_file', outputnode, 'motion_parameters') wf.connect(motion_correct, 'out_file', outputnode, 'realigned_files') # Compute TSNR on realigned data regressing polynomials upto order 2 tsnr = pe.MapNode(TSNR(regress_poly=2), iterfield=['in_file'], name='tsnr') wf.connect(motion_correct, 'out_file', tsnr, 'in_file') wf.connect(tsnr, 'tsnr_file', outputnode, 'tsnr_file') # Plot the estimated motion parameters plot_motion = pe.MapNode(fsl.PlotMotionParams(in_source='fsl'), name='plot_motion', iterfield=['in_file']) plot_motion.iterables = ('plot_type', ['rotations', 'translations']) wf.connect(motion_correct, 'par_file', plot_motion, 'in_file') wf.connect(plot_motion, 'out_file', outputnode, 'motion_plots') # Register a source file to fs space and create a brain mask in source space fssource = pe.Node(nio.FreeSurferSource(), name='fssource') fssource.inputs.subject_id = subject_id fssource.inputs.subjects_dir = subjects_dir # Extract aparc+aseg brain mask and binarize fs_threshold = pe.Node(fs.Binarize(min=0.5, out_type='nii'), name='fs_threshold') wf.connect(fssource, ('aparc_aseg', get_aparc_aseg), fs_threshold, 'in_file') # Calculate the transformation matrix from EPI space to FreeSurfer space # using the BBRegister command fs_register = pe.MapNode(fs.BBRegister(init='fsl'), iterfield=['source_file'], name='fs_register') fs_register.inputs.contrast_type = 't2' fs_register.inputs.out_fsl_file = True fs_register.inputs.subject_id = subject_id fs_register.inputs.subjects_dir = subjects_dir wf.connect(extractref, 'roi_file', fs_register, 'source_file') wf.connect(fs_register, 'out_reg_file', outputnode, 'reg_file') wf.connect(fs_register, 'min_cost_file', outputnode, 'reg_cost') wf.connect(fs_register, 'out_fsl_file', outputnode, 'reg_fsl_file') # Extract wm+csf, brain masks by eroding freesurfer lables wmcsf = pe.MapNode(fs.Binarize(), iterfield=['match', 'binary_file', 'erode'], name='wmcsfmask') #wmcsf.inputs.wm_ven_csf = True wmcsf.inputs.match = [[2, 41], [4, 5, 14, 15, 24, 31, 43, 44, 63]] wmcsf.inputs.binary_file = ['wm.nii.gz', 'csf.nii.gz'] wmcsf.inputs.erode = [2, 2] #int(np.ceil(slice_thickness)) wf.connect(fssource, ('aparc_aseg', get_aparc_aseg), wmcsf, 'in_file') # Now transform the wm and csf masks to 1st volume of 1st run wmcsftransform = pe.MapNode(fs.ApplyVolTransform(inverse=True, interp='nearest'), iterfield=['target_file'], name='wmcsftransform') wmcsftransform.inputs.subjects_dir = subjects_dir wf.connect(extractref, 'roi_file', wmcsftransform, 'source_file') wf.connect(fs_register, ('out_reg_file', pickfirst), wmcsftransform, 'reg_file') wf.connect(wmcsf, 'binary_file', wmcsftransform, 'target_file') # Transform the binarized aparc+aseg file to the 1st volume of 1st run space fs_voltransform = pe.MapNode(fs.ApplyVolTransform(inverse=True), iterfield=['source_file', 'reg_file'], name='fs_transform') fs_voltransform.inputs.subjects_dir = subjects_dir wf.connect(extractref, 'roi_file', fs_voltransform, 'source_file') wf.connect(fs_register, 'out_reg_file', fs_voltransform, 'reg_file') wf.connect(fs_threshold, 'binary_file', fs_voltransform, 'target_file') # Dilate the binarized mask by 1 voxel that is now in the EPI space fs_threshold2 = pe.MapNode(fs.Binarize(min=0.5, out_type='nii'), iterfield=['in_file'], name='fs_threshold2') fs_threshold2.inputs.dilate = 1 wf.connect(fs_voltransform, 'transformed_file', fs_threshold2, 'in_file') wf.connect(fs_threshold2, ('binary_file', pickfirst), outputnode, 'mask_file') # Use RapidART to detect motion/intensity outliers art = pe.MapNode(ra.ArtifactDetect(use_differences=[True, False], use_norm=True, zintensity_threshold=3, norm_threshold=1, bound_by_brainmask=True, mask_type="file"), iterfield=["realignment_parameters", "realigned_files"], name="art") if slice_times is not None: art.inputs.parameter_source = "NiPy" else: art.inputs.parameter_source = "FSL" wf.connect(motion_correct, 'par_file', art, 'realignment_parameters') wf.connect(motion_correct, 'out_file', art, 'realigned_files') wf.connect(fs_threshold2, ('binary_file', pickfirst), art, 'mask_file') wf.connect(art, 'norm_files', outputnode, 'artnorm_files') wf.connect(art, 'outlier_files', outputnode, 'artoutlier_files') wf.connect(art, 'displacement_files', outputnode, 'artdisplacement_files') # Compute motion regressors (save file with 1st and 2nd derivatives) motreg = pe.Node(util.Function( input_names=['motion_params', 'order', 'derivatives'], output_names=['out_files'], function=motion_regressors, imports=imports), name='getmotionregress') wf.connect(motion_correct, 'par_file', motreg, 'motion_params') wf.connect(motreg, 'out_files', outputnode, 'motion_parameters_plusDerivs') # Create a filter text file to remove motion (+ derivatives), art confounds, # and 1st, 2nd, and 3rd order legendre polynomials. createfilter1 = pe.Node(util.Function( input_names=['motion_params', 'comp_norm', 'outliers', 'detrend_poly'], output_names=['out_files'], function=build_filter1, imports=imports), name='makemotionbasedfilter') createfilter1.inputs.detrend_poly = 3 wf.connect(motreg, 'out_files', createfilter1, 'motion_params') wf.connect(art, 'norm_files', createfilter1, 'comp_norm') wf.connect(art, 'outlier_files', createfilter1, 'outliers') wf.connect(createfilter1, 'out_files', outputnode, 'motionandoutlier_noise_file') filter1 = pe.MapNode(fsl.GLM(out_f_name='F_mcart.nii.gz', out_pf_name='pF_mcart.nii.gz', demean=True), iterfield=['in_file', 'design', 'out_res_name'], name='filtermotion') wf.connect(motion_correct, 'out_file', filter1, 'in_file') wf.connect(motion_correct, ('out_file', rename, '_filtermotart'), filter1, 'out_res_name') wf.connect(createfilter1, 'out_files', filter1, 'design') # Create a filter to remove noise components based on white matter and CSF createfilter2 = pe.MapNode( util.Function(input_names=[ 'realigned_file', 'mask_file', 'num_components', 'extra_regressors' ], output_names=['out_files'], function=extract_noise_components, imports=imports), iterfield=['realigned_file', 'extra_regressors'], name='makecompcorrfilter') createfilter2.inputs.num_components = num_components wf.connect(createfilter1, 'out_files', createfilter2, 'extra_regressors') wf.connect(motion_correct, 'out_file', createfilter2, 'realigned_file') wf.connect(wmcsftransform, 'transformed_file', createfilter2, 'mask_file') wf.connect(createfilter2, 'out_files', outputnode, 'noise_components') filter2 = pe.MapNode(fsl.GLM(out_f_name='F.nii.gz', out_pf_name='pF.nii.gz', demean=True), iterfield=['in_file', 'design', 'out_res_name'], name='filter_noise_nosmooth') wf.connect(filter1, 'out_res', filter2, 'in_file') wf.connect(filter1, ('out_res', rename, '_cleaned'), filter2, 'out_res_name') wf.connect(createfilter2, 'out_files', filter2, 'design') wf.connect(fs_threshold2, ('binary_file', pickfirst), filter2, 'mask') # Band-pass filter the timeseries if use_fsl_bp == 'True': determine_bp_sigmas = pe.Node(util.Function( input_names=['tr', 'highpass_freq', 'lowpass_freq'], output_names=['out_sigmas'], function=calc_fslbp_sigmas), name='determine_bp_sigmas') determine_bp_sigmas.inputs.tr = float(TR) determine_bp_sigmas.inputs.highpass_freq = float(highpass_frequency) determine_bp_sigmas.inputs.lowpass_freq = float(lowpass_frequency) bandpass = pe.MapNode(fsl.ImageMaths(suffix='_tempfilt'), iterfield=["in_file"], name="bandpass") wf.connect(determine_bp_sigmas, ('out_sigmas', highpass_operand), bandpass, 'op_string') wf.connect(filter2, 'out_res', bandpass, 'in_file') wf.connect(bandpass, 'out_file', outputnode, 'bandpassed_files') else: bandpass = pe.Node(util.Function( input_names=['files', 'lowpass_freq', 'highpass_freq', 'fs'], output_names=['out_files'], function=bandpass_filter, imports=imports), name='bandpass') bandpass.inputs.fs = 1. / TR if highpass_frequency < 0: bandpass.inputs.highpass_freq = -1 else: bandpass.inputs.highpass_freq = highpass_frequency if lowpass_frequency < 0: bandpass.inputs.lowpass_freq = -1 else: bandpass.inputs.lowpass_freq = lowpass_frequency wf.connect(filter2, 'out_res', bandpass, 'files') wf.connect(bandpass, 'out_files', outputnode, 'bandpassed_files') # Smooth each run using SUSAn with the brightness threshold set to 75% # of the median value for each run and a mask constituting the mean functional smooth_median = pe.MapNode(fsl.ImageStats(op_string='-k %s -p 50'), iterfield=['in_file'], name='smooth_median') wf.connect(motion_correct, 'out_file', smooth_median, 'in_file') wf.connect(fs_threshold2, ('binary_file', pickfirst), smooth_median, 'mask_file') smooth_meanfunc = pe.MapNode(fsl.ImageMaths(op_string='-Tmean', suffix='_mean'), iterfield=['in_file'], name='smooth_meanfunc') wf.connect(motion_correct, 'out_file', smooth_meanfunc, 'in_file') smooth_merge = pe.Node(util.Merge(2, axis='hstack'), name='smooth_merge') wf.connect(smooth_meanfunc, 'out_file', smooth_merge, 'in1') wf.connect(smooth_median, 'out_stat', smooth_merge, 'in2') smooth = pe.MapNode(fsl.SUSAN(), iterfield=['in_file', 'brightness_threshold', 'usans'], name='smooth') smooth.inputs.fwhm = fwhm wf.connect(bandpass, 'out_file', smooth, 'in_file') wf.connect(smooth_median, ('out_stat', getbtthresh), smooth, 'brightness_threshold') wf.connect(smooth_merge, ('out', getusans), smooth, 'usans') wf.connect(smooth, 'smoothed_file', outputnode, 'smoothed_files') # Save the relevant data into an output directory datasink = pe.Node(nio.DataSink(), name="datasink") datasink.inputs.base_directory = sink_directory datasink.inputs.container = subject_id wf.connect(outputnode, 'reference', datasink, 'wmaze_rest.ref') wf.connect(outputnode, 'motion_parameters', datasink, 'wmaze_rest.motion') wf.connect(outputnode, 'realigned_files', datasink, 'wmaze_rest.func.realigned') wf.connect(outputnode, 'motion_plots', datasink, 'wmaze_rest.motion.@plots') wf.connect(outputnode, 'mask_file', datasink, 'wmaze_rest.ref.@mask') wf.connect(outputnode, 'smoothed_files', datasink, 'wmaze_rest.func.smoothed_bandpassed') wf.connect(outputnode, 'reg_file', datasink, 'wmaze_rest.bbreg.@reg') wf.connect(outputnode, 'reg_cost', datasink, 'wmaze_rest.bbreg.@cost') wf.connect(outputnode, 'reg_fsl_file', datasink, 'wmaze_rest.bbreg.@regfsl') wf.connect(outputnode, 'artnorm_files', datasink, 'wmaze_rest.art.@norm_files') wf.connect(outputnode, 'artoutlier_files', datasink, 'wmaze_rest.art.@outlier_files') wf.connect(outputnode, 'artdisplacement_files', datasink, 'wmaze_rest.art.@displacement_files') wf.connect(outputnode, 'motion_parameters_plusDerivs', datasink, 'wmaze_rest.noise.@motionplusDerivs') wf.connect(outputnode, 'motionandoutlier_noise_file', datasink, 'wmaze_rest.noise.@motionplusoutliers') wf.connect(outputnode, 'noise_components', datasink, 'wmaze_rest.compcor') wf.connect(outputnode, 'tsnr_file', datasink, 'wmaze_rest.tsnr') return wf