high_pass_filter.inputs.highpass_sigma = 22.5 # 90s / (2*2(TR)) # ======================================================================================================== # In[19] # Get the mean image Get_Mean_Image = Node(fsl.MeanImage(), name='Get_Mean_Image') Get_Mean_Image.inputs.dimension = 'T' # Add the mean image to the filtered image Add_Mean_Image = Node(fsl.BinaryMaths(), name='Add_Mean_Image') Add_Mean_Image.inputs.operation = 'add' # ======================================================================================================== # In[20]: melodic = Node(fsl.MELODIC(), name='Melodic') melodic.inputs.approach = 'concat' melodic.inputs.no_bet = True melodic.inputs.bg_threshold = 10.0 melodic.inputs.tr_sec = 2.00 melodic.inputs.mm_thresh = 0.5 melodic.inputs.out_all = True melodic.inputs.report = True melodic.iterables = ('dim', [15, 20, 25]) # ======================================================================================================== # In[21]: stimulation_preproc.connect([ (infosource_anat, selectfiles_anat, [('subject_id', 'subject_id')]), (infosource_func, selectfiles_func, [('subject_id', 'subject_id'),
def create_indnet_workflow(hp_cutoff=100, smoothing=5, smm_threshold=0.5, binarise_threshold=0.5, melodic_seed=None, aggr_aroma=False, name="indnet"): indnet = Workflow(name=name) # Input node inputspec = Node(utility.IdentityInterface( fields=['anat_file', 'func_file', 'templates', 'networks']), name='inputspec') # T1 skullstrip anat_bet = Node(fsl.BET(), name="anat_bet") # EPI preprocessing func_realignsmooth = create_featreg_preproc(highpass=False, whichvol='first', name='func_realignsmooth') func_realignsmooth.inputs.inputspec.fwhm = smoothing # Transform EPI to MNI space func_2mni = create_reg_workflow(name='func_2mni') func_2mni.inputs.inputspec.target_image = fsl.Info.standard_image( 'MNI152_T1_2mm.nii.gz') func_2mni.inputs.inputspec.target_image_brain = fsl.Info.standard_image( 'MNI152_T1_2mm_brain.nii.gz') func_2mni.inputs.inputspec.config_file = 'T1_2_MNI152_2mm' # Segmentation of T1 anat_segmentation = Node(fsl.FAST(output_biascorrected=True), name='anat_segmentation') # Transfrom segments to EPI space segments_2func = create_segments_2func_workflow( threshold=binarise_threshold, name='segments_2func') # Transform templates to EPI space templates_2func = create_templates_2func_workflow( threshold=binarise_threshold, name='templates_2func') # Mask network templates with GM gm_mask_templates = MapNode(fsl.ImageMaths(op_string='-mul'), iterfield=['in_file2'], name='gm_mask_templates') # Mask for ICA-AROMA and statistics func_brainmask = Node(fsl.BET(frac=0.3, mask=True, no_output=True, robust=True), name='func_brainmask') # Melodic ICA if melodic_seed != None: func_melodic = Node(fsl.MELODIC(args='--seed={}'.format(melodic_seed), out_stats=True), name='func_melodic') # ICA-AROMA func_aroma = Node(fsl.ICA_AROMA(), name='func_aroma') if aggr_aroma: func_aroma.inputs.denoise_type = 'aggr' else: func_aroma.inputs.denoise_type = 'nonaggr' # Highpass filter ICA results func_highpass = create_highpass_filter(cutoff=hp_cutoff, name='func_highpass') # Calculate mean CSF sgnal csf_meansignal = Node(fsl.ImageMeants(), name='csf_meansignal') # Calculate mean WM signal wm_meansignal = Node(fsl.ImageMeants(), name='wm_meansignal') # Calculate mean non-brain signal nonbrain_meansignal = create_nonbrain_meansignal( name='nonbrain_meansignal') # Calculate first Eigenvariates firsteigenvariates = MapNode(fsl.ImageMeants(show_all=True, eig=True), iterfield=['mask'], name='firsteigenvariates') # Combine first eigenvariates and wm/csf/non-brain signals regressors = Node(utility.Merge(4), name='regressors') # z-transform regressors ztransform = MapNode(Ztransform(), iterfield=['in_file'], name='ztransform') # Create design matrix designmatrix = Node(DesignMatrix(), name='designmatrix') # Create contrasts contrasts = Node(Contrasts(), name='contrasts') # GLM glm = Node(fsl.GLM(), name='glm') glm.inputs.out_z_name = 'z_stats.nii.gz' glm.inputs.demean = True # Split z-maps zmaps = Node(fsl.Split(), name='zmaps') zmaps.inputs.dimension = 't' # Spatial Mixture Modelling smm = MapNode(fsl.SMM(), iterfield=['spatial_data_file'], name='smm') # Transform probability maps to native (anat) space actmaps_2anat = MapNode(fsl.ApplyXFM(), iterfield=['in_file'], name='actmaps_2anat') # Transform probability maps to MNI space actmaps_2mni = MapNode(fsl.ApplyWarp(), iterfield=['in_file'], name='actmaps_2mni') actmaps_2mni.inputs.ref_file = fsl.Info.standard_image( 'MNI152_T1_2mm.nii.gz') # Create network masks in native (func) space network_masks_func = create_network_masks_workflow( name='network_masks_func', smm_threshold=smm_threshold) # Create network masks in native (anat) space network_masks_anat = create_network_masks_workflow( name='network_masks_anat', smm_threshold=smm_threshold) # Create network masks in MNI space network_masks_mni = create_network_masks_workflow( name='network_masks_mni', smm_threshold=smm_threshold) # Output node outputspec = Node(utility.IdentityInterface(fields=[ 'network_masks_func_main', 'network_masks_func_exclusive', 'network_masks_anat_main', 'network_masks_anat_exclusive', 'network_masks_mni_main', 'network_masks_mni_exclusive', 'preprocessed_func_file', 'preprocessed_anat_file', 'motion_parameters', 'func2anat_transform', 'anat2mni_transform' ]), name='outputspec') # Helper functions def get_first_item(x): try: return x[0] except: return x def get_second_item(x): return x[1] def get_third_item(x): return x[2] def get_components(x): return [y['components'] for y in x] # Connect the nodes # anat_bet indnet.connect(inputspec, 'anat_file', anat_bet, 'in_file') # func_realignsmooth indnet.connect(inputspec, 'func_file', func_realignsmooth, 'inputspec.func') # func_2mni indnet.connect(func_realignsmooth, ('outputspec.smoothed_files', get_first_item), func_2mni, 'inputspec.source_files') indnet.connect(inputspec, 'anat_file', func_2mni, 'inputspec.anatomical_image') indnet.connect(func_realignsmooth, 'outputspec.reference', func_2mni, 'inputspec.mean_image') # anat_segmentation indnet.connect(anat_bet, 'out_file', anat_segmentation, 'in_files') # segments_2func indnet.connect(anat_segmentation, 'partial_volume_files', segments_2func, 'inputspec.segments') indnet.connect(func_2mni, 'outputspec.func2anat_transform', segments_2func, 'inputspec.premat') indnet.connect(func_realignsmooth, 'outputspec.mean', segments_2func, 'inputspec.func_file') # templates_2func indnet.connect(func_realignsmooth, 'outputspec.mean', templates_2func, 'inputspec.func_file') indnet.connect(func_2mni, 'outputspec.func2anat_transform', templates_2func, 'inputspec.premat') indnet.connect(func_2mni, 'outputspec.anat2target_transform', templates_2func, 'inputspec.warp') indnet.connect(inputspec, 'templates', templates_2func, 'inputspec.templates') # gm_mask_templates indnet.connect(segments_2func, ('outputspec.segments_2func_files', get_second_item), gm_mask_templates, 'in_file') indnet.connect(templates_2func, 'outputspec.templates_2func_files', gm_mask_templates, 'in_file2') # func_brainmask indnet.connect(func_realignsmooth, 'outputspec.mean', func_brainmask, 'in_file') # func_melodic if melodic_seed != None: indnet.connect(func_realignsmooth, ('outputspec.smoothed_files', get_first_item), func_melodic, 'in_files') indnet.connect(func_brainmask, 'mask_file', func_melodic, 'mask') # func_aroma indnet.connect(func_realignsmooth, ('outputspec.smoothed_files', get_first_item), func_aroma, 'in_file') indnet.connect(func_2mni, 'outputspec.func2anat_transform', func_aroma, 'mat_file') indnet.connect(func_2mni, 'outputspec.anat2target_transform', func_aroma, 'fnirt_warp_file') indnet.connect(func_realignsmooth, ('outputspec.motion_parameters', get_first_item), func_aroma, 'motion_parameters') indnet.connect(func_brainmask, 'mask_file', func_aroma, 'mask') if melodic_seed != None: indnet.connect(func_melodic, 'out_dir', func_aroma, 'melodic_dir') # func_highpass if aggr_aroma: indnet.connect(func_aroma, 'aggr_denoised_file', func_highpass, 'inputspec.in_file') else: indnet.connect(func_aroma, 'nonaggr_denoised_file', func_highpass, 'inputspec.in_file') # csf_meansignal indnet.connect(segments_2func, ('outputspec.segments_2func_files', get_first_item), csf_meansignal, 'mask') indnet.connect(func_highpass, 'outputspec.filtered_file', csf_meansignal, 'in_file') # wm_meansignal indnet.connect(segments_2func, ('outputspec.segments_2func_files', get_third_item), wm_meansignal, 'mask') indnet.connect(func_highpass, 'outputspec.filtered_file', wm_meansignal, 'in_file') # nonbrain_meansignal indnet.connect(inputspec, 'func_file', nonbrain_meansignal, 'inputspec.func_file') # firsteigenvariates indnet.connect(gm_mask_templates, 'out_file', firsteigenvariates, 'mask') indnet.connect(func_highpass, 'outputspec.filtered_file', firsteigenvariates, 'in_file') # regressors indnet.connect(firsteigenvariates, 'out_file', regressors, 'in1') indnet.connect(wm_meansignal, 'out_file', regressors, 'in2') indnet.connect(csf_meansignal, 'out_file', regressors, 'in3') indnet.connect(nonbrain_meansignal, 'outputspec.nonbrain_regressor', regressors, 'in4') # ztransform indnet.connect(regressors, 'out', ztransform, 'in_file') # designmatrix indnet.connect(ztransform, 'out_file', designmatrix, 'in_files') # contrasts indnet.connect(inputspec, ('networks', get_components), contrasts, 'in_list') indnet.connect(designmatrix, 'out_file', contrasts, 'design') # glm indnet.connect(designmatrix, 'out_file', glm, 'design') indnet.connect(contrasts, 'out_file', glm, 'contrasts') indnet.connect(func_brainmask, 'mask_file', glm, 'mask') indnet.connect(func_highpass, 'outputspec.filtered_file', glm, 'in_file') # zmaps indnet.connect(glm, 'out_z', zmaps, 'in_file') # smm indnet.connect(zmaps, 'out_files', smm, 'spatial_data_file') indnet.connect(func_brainmask, 'mask_file', smm, 'mask') # actmaps_2anat indnet.connect(smm, 'activation_p_map', actmaps_2anat, 'in_file') indnet.connect(func_2mni, 'outputspec.func2anat_transform', actmaps_2anat, 'in_matrix_file') indnet.connect(anat_bet, 'out_file', actmaps_2anat, 'reference') # actmaps_2mni indnet.connect(smm, 'activation_p_map', actmaps_2mni, 'in_file') indnet.connect(templates_2func, 'outputspec.func_2mni_warp', actmaps_2mni, 'field_file') # network_masks_func indnet.connect(smm, 'activation_p_map', network_masks_func, 'inputspec.actmaps') indnet.connect(inputspec, 'networks', network_masks_func, 'inputspec.networks') # network_masks_anat indnet.connect(actmaps_2anat, 'out_file', network_masks_anat, 'inputspec.actmaps') indnet.connect(inputspec, 'networks', network_masks_anat, 'inputspec.networks') # network_masks_mni indnet.connect(actmaps_2mni, 'out_file', network_masks_mni, 'inputspec.actmaps') indnet.connect(inputspec, 'networks', network_masks_mni, 'inputspec.networks') # output node indnet.connect(network_masks_func, 'outputspec.main_masks', outputspec, 'network_masks_func_main') indnet.connect(network_masks_func, 'outputspec.exclusive_masks', outputspec, 'network_masks_func_exclusive') indnet.connect(network_masks_anat, 'outputspec.main_masks', outputspec, 'network_masks_anat_main') indnet.connect(network_masks_anat, 'outputspec.exclusive_masks', outputspec, 'network_masks_anat_exclusive') indnet.connect(network_masks_mni, 'outputspec.main_masks', outputspec, 'network_masks_mni_main') indnet.connect(network_masks_mni, 'outputspec.exclusive_masks', outputspec, 'network_masks_mni_exclusive') indnet.connect(func_highpass, 'outputspec.filtered_file', outputspec, 'preprocessed_func_file') indnet.connect(anat_segmentation, 'restored_image', outputspec, 'preprocessed_anat_file') indnet.connect(func_realignsmooth, ('outputspec.motion_parameters', get_first_item), outputspec, 'motion_parameters') indnet.connect(func_2mni, 'outputspec.func2anat_transform', outputspec, 'func2anat_transform') indnet.connect(func_2mni, 'outputspec.anat2target_transform', outputspec, 'anat2mni_transform') return indnet
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=-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(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 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) t1_2_mni_forward_transform 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=[ 'itk_bold_to_t1', 't1_2_mni_forward_transform', 'name_source', 'skip_vols', '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=str( get_template('MNI152Lin') / 'tpl-MNI152Lin_space-MNI_res-02_T1w.nii.gz'), use_compression=False, use_fieldwarp=use_fieldwarp, name='bold_mni_trans_wf') bold_mni_trans_wf.__desc__ = None 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(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')]), (inputnode, rm_non_steady_state, [('skip_vols', 'skip_vols')]), (bold_mni_trans_wf, rm_non_steady_state, [('outputnode.bold_mni', 'bold_file')]), (bold_mni_trans_wf, calc_median_val, [('outputnode.bold_mask_mni', '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')]), (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'), ('outputnode.bold_mask_mni', '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')]), # TODO change melodic report to reflect noise and non-noise components (ica_aroma, add_non_steady_state, [('nonaggr_denoised_file', 'bold_cut_file')]), (bold_mni_trans_wf, add_non_steady_state, [('outputnode.bold_mni', '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 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, ): """ 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/poldracklab/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 fmriprep.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) 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 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 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). 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 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')]), ]) return workflow
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 = pe.Workflow(name=name) 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' ) 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 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