def test_afni(tmp_path): seed(a=0x5E6128C4) m = 100 n = 5 column_names = [f"column_{i+1}" for i in range(n)] data_frame = pd.DataFrame(np.random.rand(m, n), columns=column_names) data_file = tmp_path / "data.tsv" data_frame.to_csv(data_file, sep="\t", header=True, index=False) to_afni = ToAFNI(in_file=data_file) cwd = tmp_path / "to_afni" cwd.mkdir() result = to_afni.run(cwd=cwd) assert result.outputs is not None oned_file = result.outputs.out_file metadata = result.outputs.metadata from_afni = FromAFNI(in_file=oned_file, metadata=metadata) cwd = tmp_path / "from_afni" cwd.mkdir() result = from_afni.run(cwd=cwd) assert result.outputs is not None test_data_frame = read_spreadsheet(result.outputs.out_file) assert np.allclose(data_frame.values, test_data_frame.values) cwd = tmp_path / "tproject" cwd.mkdir() tproject = afni.TProject( in_file=oned_file, out_file=cwd / "filt.1D", bandpass=(0.01, 0.1), TR=2, polort=1, ) result = tproject.run(cwd=cwd) assert result.outputs is not None from_afni = FromAFNI(in_file=result.outputs.out_file, metadata=metadata) result = from_afni.run(cwd=cwd) assert result.outputs is not None test_data_frame = read_spreadsheet(result.outputs.out_file) assert not np.allclose(data_frame.values, test_data_frame.values)
def create_nuisance_workflow(nuisance_selectors, use_ants, name='nuisance'): """ Workflow for the removal of various signals considered to be noise from resting state fMRI data. The residual signals for linear regression denoising is performed in a single model. Therefore the residual time-series will be orthogonal to all signals. Parameters ---------- :param nuisance_selectors: dictionary describing nuisance regression to be performed :param use_ants: flag indicating whether FNIRT or ANTS is used :param name: Name of the workflow, defaults to 'nuisance' :return: nuisance : nipype.pipeline.engine.Workflow Nuisance workflow. Notes ----- Workflow Inputs --------------- Workflow Inputs:: inputspec.functional_file_path : string (nifti file) Path to realigned and motion corrected functional image (nifti) file. inputspec.functional_brain_mask_file_path : string (nifti file) Whole brain mask corresponding to the functional data. inputspec.anatomical_file_path : string (nifti file) Corresponding preprocessed anatomical. inputspec.wm_mask_file_path : string (nifti file) Corresponding white matter mask. inputspec.csf_mask_file_path : string (nifti file) Corresponding cerebral spinal fluid mask. inputspec.gm_mask_file_path : string (nifti file) Corresponding grey matter mask. inputspec.lat_ventricles_mask_file_path : string (nifti file) Mask of lateral ventricles calculated from the Harvard Oxford Atlas. inputspec.mni_to_anat_linear_xfm_file_path: string (nifti file) FLIRT Linear MNI to Anat transform inputspec.anat_to_mni_initial_xfm_file_path: string (nifti file) ANTS initial transform from anat to MNI inputspec.anat_to_mni_rigid_xfm_file_path: string (nifti file) ANTS rigid (6 parameter, no scaling) transform from anat to MNI inputspec.anat_to_mni_affine_xfm_file_path: string (nifti file) ANTS affine (13 parameter, scales and shears) transform from anat to MNI inputspec.func_to_anat_linear_xfm_file_path: string (nifti file) FLIRT Linear Transform between functional and anatomical spaces inputspec.motion_parameter_file_path : string (text file) Corresponding rigid-body motion parameters. Matrix in the file should be of shape (`T`, `R`), `T` time points and `R` motion parameters. inputspec.fd_j_file_path : string (text file) Framewise displacement calculated from the volume alignment. inputspec.fd_p_file_path : string (text file) Framewise displacement calculated from the motion parameters. inputspec.dvars_file_path : string (text file) DVARS calculated from the functional data. inputspec.selector : Dictionary containing configuration parameters for nuisance regression. To not run a type of nuisance regression, it may be ommited from the dictionary. selector = { aCompCor: { symmary: { method: 'DetrendPC', aCompCor will always extract the principal components from detrended tissues signal, components: number of components to retain, }, tissues: list of tissues to extract regressors. Valid values are: 'WhiteMatter', 'CerebrospinalFluid', extraction_resolution: None | floating point value indicating isotropic resolution (ex. 2 for 2mm x 2mm x 2mm that data should be extracted at, the corresponding tissue mask will be resampled to this resolution. The functional data will also be resampled to this resolution, and the extraction will occur at this new resolution. The goal is to avoid contamination from undesired tissue components when extracting nuisance regressors, erode_mask: True | False, whether or not the mask should be eroded to further avoid a mask overlapping with a different tissue class, include_delayed: True | False, whether or not to include a one-frame delay regressor, default to False, include_squared: True | False, whether or not to include a squared regressor, default to False, include_delayed_squared: True | False, whether or not to include a squared one-frame delay regressor, default to False, }, tCompCor: { symmary: { method: 'PC', tCompCor will always extract the principal components from BOLD signal, components: number of components to retain, }, threshold: floating point number = cutoff as raw variance value, floating point number followed by SD (ex. 1.5SD) = mean + a multiple of the SD, floating point number followed by PCT (ex. 2PCT) = percentile from the top (ex is top 2%), by_slice: True | False, whether or not the threshold criterion should be applied by slice or across the entire volume, makes most sense for thresholds using SD or PCT, include_delayed: True | False, include_squared: True | False, include_delayed_squared: True | False, }, WhiteMatter: { symmary: { method: 'PC', 'DetrendPC', 'Mean', 'NormMean' or 'DetrendNormMean', components: number of components to retain, if PC, }, extraction_resolution: None | floating point value (same as for aCompCor), erode_mask: True | False (same as for aCompCor), include_delayed: True | False (same as for aCompCor), include_squared: True | False (same as for aCompCor), include_delayed_squared: True | False (same as for aCompCor), }, CerebrospinalFluid: { symmary: { method: 'PC', 'DetrendPC', 'Mean', 'NormMean' or 'DetrendNormMean', components: number of components to retain, if PC, }, extraction_resolution: None | floating point value (same as for aCompCor), erode_mask: True | False (same as for aCompCor), include_delayed: True | False (same as for aCompCor), include_squared: True | False (same as for aCompCor), include_delayed_squared: True | False (same as for aCompCor), }, GreyMatter: { symmary: { method: 'PC', 'DetrendPC', 'Mean', 'NormMean' or 'DetrendNormMean', components: number of components to retain, if PC, }, extraction_resolution: None | floating point value (same as for aCompCor), erode_mask: True | False (same as for aCompCor), include_delayed: True | False (same as for aCompCor), include_squared: True | False (same as for aCompCor), include_delayed_squared: True | False (same as for aCompCor), }, GlobalSignal: { symmary: { method: 'PC', 'DetrendPC', 'Mean', 'NormMean' or 'DetrendNormMean', components: number of components to retain, if PC, }, include_delayed: True | False (same as for aCompCor), include_squared: True | False (same as for aCompCor), include_delayed_squared: True | False (same as for aCompCor), }, Motion: None | { include_delayed: True | False (same as for aCompCor), include_squared: True | False (same as for aCompCor), include_delayed_squared: True | False (same as for aCompCor), }, Censor: { method: 'Kill', 'Zero', 'Interpolate', 'SpikeRegression', thresholds: list of dictionary, { type: 'FD_J', 'FD_P', 'DVARS', value: threshold value to be applied to metric }, number_of_previous_trs_to_censor: integer, number of previous TRs to censor (remove or regress, if spike regression) number_of_subsequent_trs_to_censor: integer, number of subsequent TRs to censor (remove or regress, if spike regression) }, PolyOrt: { degree: integer, polynomial degree up to which will be removed, e.g. 2 means constant + linear + quadratic, practically that is probably, the most that will be need especially if band pass filtering }, Bandpass: { bottom_frequency: floating point value, frequency in hertz of the highpass part of the pass band, frequencies below this will be removed, top_frequency: floating point value, frequency in hertz of the lowpass part of the pass band, frequencies above this will be removed } } Workflow Outputs:: outputspec.residual_file_path : string (nifti file) Path of residual file in nifti format outputspec.regressors_file_path : string (TSV file) Path of TSV file of regressors used. Column name indicates the regressors included . Nuisance Procedure: 1. Compute nuisance regressors based on input selections. 2. Calculate residuals with respect to these nuisance regressors in a single model for every voxel. High Level Workflow Graph: .. exec:: from CPAC.nuisance import create_nuisance_workflow wf = create_nuisance_workflow({ 'PolyOrt': {'degree': 2}, 'tCompCor': {'summary': {'method': 'PC', 'components': 5}, 'threshold': '1.5SD', 'by_slice': True}, 'aCompCor': {'summary': {'method': 'PC', 'components': 5}, 'tissues': ['WhiteMatter', 'CerebrospinalFluid'], 'extraction_resolution': 2}, 'WhiteMatter': {'summary': {'method': 'PC', 'components': 5}, 'extraction_resolution': 2}, 'CerebrospinalFluid': {'summary': {'method': 'PC', 'components': 5}, 'extraction_resolution': 2, 'erode_mask': True}, 'GreyMatter': {'summary': {'method': 'PC', 'components': 5}, 'extraction_resolution': 2, 'erode_mask': True}, 'GlobalSignal': {'summary': 'Mean', 'include_delayed': True, 'include_squared': True, 'include_delayed_squared': True}, 'Motion': {'include_delayed': True, 'include_squared': True, 'include_delayed_squared': True}, 'Censor': {'method': 'Interpolate', 'thresholds': [{'type': 'FD_J', 'value': 0.5}, {'type': 'DVARS', 'value': 0.7}]} }, use_ants=False) wf.write_graph( graph2use='orig', dotfilename='./images/nuisance.dot' ) .. image:: ../images/nuisance.png :width: 1000 Detailed Workflow Graph: .. image:: ../images/nuisance_detailed.png :width: 1000 """ nuisance_wf = pe.Workflow(name=name) inputspec = pe.Node(util.IdentityInterface(fields=[ 'selector', 'functional_file_path', 'anatomical_file_path', 'gm_mask_file_path', 'wm_mask_file_path', 'csf_mask_file_path', 'lat_ventricles_mask_file_path', 'functional_brain_mask_file_path', 'func_to_anat_linear_xfm_file_path', 'mni_to_anat_linear_xfm_file_path', 'anat_to_mni_initial_xfm_file_path', 'anat_to_mni_rigid_xfm_file_path', 'anat_to_mni_affine_xfm_file_path', 'motion_parameters_file_path', 'fd_j_file_path', 'fd_p_file_path', 'dvars_file_path', ]), name='inputspec') outputspec = pe.Node(util.IdentityInterface(fields=['residual_file_path', 'regressors_file_path']), name='outputspec') # Resources to create regressors pipeline_resource_pool = { "Anatomical": (inputspec, 'anatomical_file_path'), "Functional": (inputspec, 'functional_file_path'), "GlobalSignal": (inputspec, 'functional_brain_mask_file_path'), "WhiteMatter": (inputspec, 'wm_mask_file_path'), "CerebrospinalFluid": (inputspec, 'csf_mask_file_path'), "GreyMatter": (inputspec, 'gm_mask_file_path'), "Ventricles": (inputspec, 'lat_ventricles_mask_file_path'), "Transformations": { "func_to_anat_linear_xfm": (inputspec, "func_to_anat_linear_xfm_file_path"), "mni_to_anat_linear_xfm": (inputspec, "mni_to_anat_linear_xfm_file_path"), "anat_to_mni_initial_xfm": (inputspec, "anat_to_mni_initial_xfm_file_path"), "anat_to_mni_rigid_xfm": (inputspec, "anat_to_mni_rigid_xfm_file_path"), "anat_to_mni_affine_xfm": (inputspec, "anat_to_mni_affine_xfm_file_path"), } } # Regressor map to simplify construction of the needed regressors regressors = { 'GreyMatter': ['grey_matter_summary_file_path', ()], 'WhiteMatter': ['white_matter_summary_file_path', ()], 'CerebrospinalFluid': ['csf_summary_file_path', ()], 'aCompCor': ['acompcor_file_path', ()], 'tCompCor': ['tcompcor_file_path', ()], 'GlobalSignal': ['global_summary_file_path', ()], 'DVARS': ['dvars_file_path', (inputspec, 'dvars_file_path')], 'FD_J': ['framewise_displacement_j_file_path', (inputspec, 'framewise_displacement_j_file_path')], 'FD_P': ['framewise_displacement_p_file_path', (inputspec, 'framewise_displacement_p_file_path')], 'Motion': ['motion_parameters_file_path', (inputspec, 'motion_parameters_file_path')] } derived = ['tCompCor', 'aCompCor'] tissues = ['GreyMatter', 'WhiteMatter', 'CerebrospinalFluid'] for regressor_type, regressor_resource in regressors.items(): if regressor_type not in nuisance_selectors: continue regressor_selector = nuisance_selectors[regressor_type] # Set summary method for tCompCor and aCompCor if regressor_type in derived: if 'summary' not in regressor_selector: regressor_selector['summary'] = {} if type(regressor_selector['summary']) is not dict: raise ValueError("Regressor {0} requires PC summary method, " "but {1} specified" .format(regressor_type, regressor_selector['summary'])) regressor_selector['summary']['method'] = \ 'DetrendPC' if regressor_type == 'aCompCor' else 'PC' if not regressor_selector['summary'].get('components'): regressor_selector['summary']['components'] = 1 # If regressor is not present, build up the regressor if not regressor_resource[1]: # We don't have the regressor, look for it in the resource pool, # build a corresponding key, this is seperated in to a mask key # and an extraction key, which when concatenated provide the # resource key for the regressor regressor_descriptor = {'tissue': regressor_type} if regressor_type == 'aCompCor': if not regressor_selector.get('tissues'): raise ValueError("Tissue type required for aCompCor, " "but none specified") regressor_descriptor = { 'tissue': regressor_selector['tissues'] } if regressor_type == 'tCompCor': if not regressor_selector.get('threshold'): raise ValueError("Threshold required for tCompCor, " "but none specified.") regressor_descriptor = { 'tissue': 'FunctionalVariance-{}' .format(regressor_selector['threshold']) } if regressor_selector.get('by_slice'): regressor_descriptor['tissue'] += '-BySlice' else: regressor_selector['by_slice'] = False # Add selector into regressor description if regressor_selector.get('extraction_resolution'): regressor_descriptor['resolution'] = \ str(regressor_selector['extraction_resolution']) + "mm" elif regressor_type in tissues: regressor_selector['extraction_resolution'] = "Functional" regressor_descriptor['resolution'] = "Functional" if regressor_selector.get('erode_mask'): regressor_descriptor['erosion'] = 'Eroded' if not regressor_selector.get('summary'): raise ValueError("Summary method required for {0}, " "but none specified".format(regressor_type)) if type(regressor_selector['summary']) is dict: regressor_descriptor['extraction'] = \ regressor_selector['summary']['method'] else: regressor_descriptor['extraction'] = \ regressor_selector['summary'] if regressor_descriptor['extraction'] in ['DetrendPC', 'PC']: if not regressor_selector['summary'].get('components'): raise ValueError("Summary method PC requires components, " "but received none.") regressor_descriptor['extraction'] += \ '_{0}'.format(regressor_selector['summary']['components']) if type(regressor_descriptor['tissue']) is not list: regressor_descriptor['tissue'] = \ [regressor_descriptor['tissue']] if regressor_selector.get('extraction_resolution') and \ regressor_selector["extraction_resolution"] != "Functional": functional_at_resolution_key = "Functional_{0}mm".format( regressor_selector["extraction_resolution"] ) anatomical_at_resolution_key = "Anatomical_{0}mm".format( regressor_selector["extraction_resolution"] ) if anatomical_at_resolution_key not in pipeline_resource_pool: anat_resample = pe.Node( interface=fsl.FLIRT(), name='{}_flirt' .format(anatomical_at_resolution_key) ) anat_resample.inputs.apply_isoxfm = regressor_selector["extraction_resolution"] nuisance_wf.connect(*( pipeline_resource_pool['Anatomical'] + (anat_resample, 'in_file') )) nuisance_wf.connect(*( pipeline_resource_pool['Anatomical'] + (anat_resample, 'reference') )) pipeline_resource_pool[anatomical_at_resolution_key] = \ (anat_resample, 'out_file') if functional_at_resolution_key not in pipeline_resource_pool: func_resample = pe.Node( interface=fsl.FLIRT(), name='{}_flirt' .format(functional_at_resolution_key) ) func_resample.inputs.apply_xfm = True nuisance_wf.connect(*( pipeline_resource_pool['Transformations']['func_to_anat_linear_xfm'] + (func_resample, 'in_matrix_file') )) nuisance_wf.connect(*( pipeline_resource_pool['Functional'] + (func_resample, 'in_file') )) nuisance_wf.connect(*( pipeline_resource_pool[anatomical_at_resolution_key] + (func_resample, 'reference') )) pipeline_resource_pool[functional_at_resolution_key] = \ (func_resample, 'out_file') # Create merger to summarize the functional timeseries regressor_mask_file_resource_keys = [] for tissue in regressor_descriptor['tissue']: # Ignore non tissue masks if tissue not in tissues and \ not tissue.startswith('FunctionalVariance'): regressor_mask_file_resource_keys += [tissue] continue tissue_regressor_descriptor = regressor_descriptor.copy() tissue_regressor_descriptor['tissue'] = tissue # Generate resource masks (pipeline_resource_pool, regressor_mask_file_resource_key) = \ generate_summarize_tissue_mask( nuisance_wf, pipeline_resource_pool, tissue_regressor_descriptor, regressor_selector, use_ants=use_ants ) regressor_mask_file_resource_keys += \ [regressor_mask_file_resource_key] # Keep tissus ordered, to avoid duplicates regressor_mask_file_resource_keys = \ list(sorted(regressor_mask_file_resource_keys)) # Create key for the final regressors regressor_file_resource_key = "_".join([ "-".join(regressor_descriptor[key]) if type(regressor_descriptor[key]) == list else regressor_descriptor[key] for key in ['tissue', 'resolution', 'erosion', 'extraction'] if key in regressor_descriptor ]) if regressor_file_resource_key not in pipeline_resource_pool: # Retrieve summary from voxels at provided mask summarize_timeseries_node = pe.Node( Function( input_names=[ 'functional_path', 'masks_path', 'summary' ], output_names=['components_path'], function=summarize_timeseries, as_module=True, ), name='{}_summarization'.format(regressor_type) ) summarize_timeseries_node.inputs.summary = \ regressor_selector['summary'] # Merge mask paths to extract voxel timeseries merge_masks_paths = pe.Node( util.Merge(len(regressor_mask_file_resource_keys)), name='{}_marge_masks'.format(regressor_type) ) for i, regressor_mask_file_resource_key in \ enumerate(regressor_mask_file_resource_keys): node, node_output = \ pipeline_resource_pool[regressor_mask_file_resource_key] nuisance_wf.connect( node, node_output, merge_masks_paths, "in{}".format(i + 1) ) nuisance_wf.connect( merge_masks_paths, 'out', summarize_timeseries_node, 'masks_path' ) functional_key = 'Functional' if regressor_selector.get('extraction_resolution') and \ regressor_selector["extraction_resolution"] != "Functional": functional_key = 'Functional_{}mm'.format( regressor_selector['extraction_resolution'] ) nuisance_wf.connect(*( pipeline_resource_pool[functional_key] + (summarize_timeseries_node, 'functional_path') )) pipeline_resource_pool[regressor_file_resource_key] = \ (summarize_timeseries_node, 'components_path') # Add it to internal resource pool regressor_resource[1] = \ pipeline_resource_pool[regressor_file_resource_key] # Build regressors and combine them into a single file build_nuisance_regressors = pe.Node(Function( input_names=['functional_file_path', 'selector', 'grey_matter_summary_file_path', 'white_matter_summary_file_path', 'csf_summary_file_path', 'acompcor_file_path', 'tcompcor_file_path', 'global_summary_file_path', 'motion_parameters_file_path', 'censor_file_path'], output_names=['out_file'], function=gather_nuisance, as_module=True ), name="build_nuisance_regressors") nuisance_wf.connect( inputspec, 'functional_file_path', build_nuisance_regressors, 'functional_file_path' ) nuisance_wf.connect( inputspec, 'selector', build_nuisance_regressors, 'selector' ) # Check for any regressors to combine into files has_nuisance_regressors = any( regressor_resource[1] for regressor_key, regressor_resource in regressors.items() ) if has_nuisance_regressors: for regressor_key, (regressor_arg, regressor_node) in regressors.items(): if regressor_key in nuisance_selectors: nuisance_wf.connect( regressor_node[0], regressor_node[1], build_nuisance_regressors, regressor_arg ) if nuisance_selectors.get('Censor'): censor_methods = ['Kill', 'Zero', 'Interpolate', 'SpikeRegression'] censor_selector = nuisance_selectors.get('Censor') if censor_selector.get('method') not in censor_methods: raise ValueError("Improper censoring method specified ({0}), " "should be one of {1}." .format(censor_selector.get('method'), censor_methods)) find_censors = pe.Node(Function( input_names=['fd_j_file_path', 'fd_j_threshold', 'fd_p_file_path', 'fd_p_threshold', 'dvars_file_path', 'dvars_threshold', 'number_of_previous_trs_to_censor', 'number_of_subsequent_trs_to_censor'], output_names=['out_file'], function=find_offending_time_points, as_module=True ), name="find_offending_time_points") if not censor_selector.get('thresholds'): raise ValueError( 'Censoring requested, but thresh_metric not provided.' ) for threshold in censor_selector['thresholds']: if 'type' not in threshold or threshold['type'] not in ['DVARS', 'FD_J', 'FD_P']: raise ValueError( 'Censoring requested, but with invalid threshold type.' ) if 'value' not in threshold: raise ValueError( 'Censoring requested, but threshold not provided.' ) if threshold['type'] == 'FD_J': find_censors.inputs.fd_j_threshold = threshold['value'] nuisance_wf.connect(inputspec, "fd_j_file_path", find_censors, "fd_j_file_path") if threshold['type'] == 'FD_P': find_censors.inputs.fd_p_threshold = threshold['value'] nuisance_wf.connect(inputspec, "fd_p_file_path", find_censors, "fd_p_file_path") if threshold['type'] == 'DVARS': find_censors.inputs.dvars_threshold = threshold['value'] nuisance_wf.connect(inputspec, "dvars_file_path", find_censors, "dvars_file_path") if censor_selector.get('number_of_previous_trs_to_censor') and \ censor_selector['method'] != 'SpikeRegression': find_censors.inputs.number_of_previous_trs_to_censor = \ censor_selector['number_of_previous_trs_to_censor'] else: find_censors.inputs.number_of_previous_trs_to_censor = 0 if censor_selector.get('number_of_subsequent_trs_to_censor') and \ censor_selector['method'] != 'SpikeRegression': find_censors.inputs.number_of_subsequent_trs_to_censor = \ censor_selector['number_of_subsequent_trs_to_censor'] else: find_censors.inputs.number_of_subsequent_trs_to_censor = 0 # Use 3dTproject to perform nuisance variable regression nuisance_regression = pe.Node(interface=afni.TProject(), name='nuisance_regression') nuisance_regression.inputs.out_file = 'residuals.nii.gz' nuisance_regression.inputs.outputtype = 'NIFTI_GZ' nuisance_regression.inputs.norm = False if nuisance_selectors.get('Censor'): if nuisance_selectors['Censor']['method'] == 'SpikeRegression': nuisance_wf.connect(find_censors, 'out_file', build_nuisance_regressors, 'censor_file_path') else: if nuisance_selectors['Censor']['method'] == 'Interpolate': nuisance_regression.inputs.cenmode = 'NTRP' else: nuisance_regression.inputs.cenmode = \ nuisance_selectors['Censor']['method'].upper() nuisance_wf.connect(find_censors, 'out_file', nuisance_regression, 'censor') if nuisance_selectors.get('PolyOrt'): if not nuisance_selectors['PolyOrt'].get('degree'): raise ValueError("Polynomial orthogonalization requested, " "but degree not provided.") nuisance_regression.inputs.polort = \ nuisance_selectors['PolyOrt']['degree'] else: nuisance_regression.inputs.polort = 0 nuisance_wf.connect([ (inputspec, nuisance_regression, [ ('functional_file_path', 'in_file'), ('functional_brain_mask_file_path', 'mask'), ]), ]) if has_nuisance_regressors: nuisance_wf.connect(build_nuisance_regressors, 'out_file', nuisance_regression, 'ort') nuisance_wf.connect(nuisance_regression, 'out_file', outputspec, 'residual_file_path') nuisance_wf.connect(build_nuisance_regressors, 'out_file', outputspec, 'regressors_file_path') return nuisance_wf
def init_bandpass_filter_wf(bandpass_filter=None, name=None, suffix=None, memcalc=MemoryCalculator()): """ """ type, low, high = bandpass_filter if name is None: name = f"{type}_bandpass_filter" if low is not None: name = f"{name}_{int(low * 1000):d}" if low is not None: name = f"{name}_{int(high * 1000):d}" name = f"{name}_wf" if suffix is not None: name = f"{name}_{suffix}" workflow = pe.Workflow(name=name) inputnode = pe.Node( niu.IdentityInterface( fields=["files", "mask", "low", "high", "vals", "repetition_time" ]), name="inputnode", ) outputnode = pe.Node( niu.IdentityInterface(fields=["files", "mask", "vals"]), name="outputnode", ) workflow.connect(inputnode, "mask", outputnode, "mask") workflow.connect(inputnode, "vals", outputnode, "vals") if low is not None: inputnode.inputs.low = low else: inputnode.inputs.low = -1.0 if high is not None: inputnode.inputs.high = high else: inputnode.inputs.high = -1.0 addmeans = pe.MapNode(AddMeans(), iterfield=["in_file", "mean_file"], name="addmeans", mem_gb=memcalc.series_std_gb * 2) workflow.connect(inputnode, "files", addmeans, "mean_file") workflow.connect(addmeans, "out_file", outputnode, "files") if type == "gaussian": calcsigma = pe.Node( niu.Function( input_names=["lp_width", "hp_width", "repetition_time"], output_names=["lp_sigma", "hp_sigma"], function=_calc_sigma, ), name="calcsigma", ) workflow.connect(inputnode, "low", calcsigma, "lp_width") workflow.connect(inputnode, "high", calcsigma, "hp_width") workflow.connect(inputnode, "repetition_time", calcsigma, "repetition_time") temporalfilter = pe.MapNode(TemporalFilter(), iterfield="in_file", name="temporalfilter", mem_gb=memcalc.series_std_gb) workflow.connect(calcsigma, "lp_sigma", temporalfilter, "lowpass_sigma") workflow.connect(calcsigma, "hp_sigma", temporalfilter, "highpass_sigma") workflow.connect(inputnode, "files", temporalfilter, "in_file") workflow.connect(inputnode, "mask", temporalfilter, "mask") workflow.connect(temporalfilter, "out_file", addmeans, "in_file") elif type == "frequency_based": toafni = pe.MapNode(ToAFNI(), iterfield="in_file", name="toafni") workflow.connect(inputnode, "files", toafni, "in_file") makeoutfname = pe.MapNode( niu.Function( input_names=["in_file"], output_names=["out_file"], function=_out_file_name, ), iterfield="in_file", name="tprojectoutfilename", ) workflow.connect(toafni, "out_file", makeoutfname, "in_file") bandpassarg = pe.Node( niu.Function( input_names=["low", "high"], output_names=["out"], function=_bandpass_arg, ), name="bandpassarg", ) # cannot use merge here as we need a tuple workflow.connect(inputnode, "low", bandpassarg, "low") workflow.connect(inputnode, "high", bandpassarg, "high") tproject = pe.MapNode(afni.TProject(polort=1), iterfield=["in_file", "out_file"], name="tproject", mem_gb=memcalc.series_std_gb * 2) workflow.connect(toafni, "out_file", tproject, "in_file") workflow.connect(bandpassarg, "out", tproject, "bandpass") workflow.connect(inputnode, "repetition_time", tproject, "TR") workflow.connect(makeoutfname, "out_file", tproject, "out_file") fromafni = pe.MapNode(FromAFNI(), iterfield=["in_file", "metadata"], name="fromafni") workflow.connect(toafni, "metadata", fromafni, "metadata") workflow.connect(tproject, "out_file", fromafni, "in_file") workflow.connect(fromafni, "out_file", addmeans, "in_file") else: raise ValueError(f"Unknown bandpass_filter type '{type}'") return workflow
def init_bold_filt_wf(variant=None, memcalc=MemoryCalculator()): assert variant is not None name = make_variant_bold_filt_wf_name(variant) workflow = pe.Workflow(name=name) inputnode = pe.Node( niu.IdentityInterface(fields=[ *in_attrs_from_func_preproc_wf, *in_attrs_from_anat_preproc_wf, "metadata" ]), name="inputnode", ) workflow.add_nodes([inputnode]) metadatanode = pe.Node(niu.IdentityInterface(fields=["repetition_time"]), name="metadatanode") workflow.connect([(inputnode, metadatanode, [ (("metadata", get_repetition_time), "repetition_time") ])]) bandpass = None ortendpoint = None boldfileendpoint = (inputnode, "bold_std") tagdict = dict(variant) # smoothing is done first if "smoothed" in tagdict: fwhm = float(tagdict["smoothed"]) smooth_workflow = init_smooth_wf(fwhm=fwhm) workflow.connect(inputnode, "bold_mask_std", smooth_workflow, "inputnode.mask_file") workflow.connect(*boldfileendpoint, smooth_workflow, "inputnode.in_file") boldfileendpoint = (smooth_workflow, "outputnode.out_file") if "grand_mean_scaled" in tagdict: grand_mean = tagdict["grand_mean_scaled"] assert isinstance(grand_mean, float) grandmeanscaling = pe.Node( interface=GrandMeanScaling(grand_mean=grand_mean), name="grandmeanscaling") workflow.connect(*boldfileendpoint, grandmeanscaling, "in_file") workflow.connect(inputnode, "bold_mask_std", grandmeanscaling, "mask_file") boldfileendpoint = (grandmeanscaling, "out_file") need_to_add_mean = False boldfileendpoint_for_meanfunc = boldfileendpoint # if we use gaussian band-pass filtering, we cannot orthogonalize these regressors # with respect to the filter, as afni tproject doesn't support this filter type # as such we need to remove them before to not re-introduce filtered-out variance simultaneous_bandpass_and_ort = True if "band_pass_filtered" in tagdict: type = first(tagdict["band_pass_filtered"]) if type == "gaussian": simultaneous_bandpass_and_ort = False confounds_to_remove_before_filtering = set(("aroma_motion_[0-9]+", )) if (not simultaneous_bandpass_and_ort and "confounds_removed" in tagdict and not confounds_to_remove_before_filtering.isdisjoint( tagdict["confounds_removed"])): confoundsremovedset = set(tagdict["confounds_removed"]) preconfoundsremoved = confounds_to_remove_before_filtering & confoundsremovedset postconfoundsremoved = confoundsremovedset - confounds_to_remove_before_filtering if len(postconfoundsremoved) == 0: del tagdict["confounds_removed"] else: tagdict["confounds_removed"] = postconfoundsremoved ortendpoint, _ = make_confoundsendpoint("pre", workflow, boldfileendpoint, list(preconfoundsremoved), memcalc) tproject = pe.Node(afni.TProject(polort=1, out_file="tproject.nii"), name="pretproject") workflow.connect(*boldfileendpoint, tproject, "in_file") workflow.connect(metadatanode, "repetition_time", tproject, "TR") workflow.connect(*ortendpoint, tproject, "ort") boldfileendpoint = (tproject, "out_file") need_to_add_mean = True if "band_pass_filtered" in tagdict: type = first(tagdict["band_pass_filtered"]) if type == "frequency_based": bandpass = tagdict["band_pass_filtered"][1:] elif type == "gaussian": def calc_highpass_sigma(temporal_filter_width=None, repetition_time=None): highpass_sigma = temporal_filter_width / (2.0 * repetition_time) return highpass_sigma calchighpasssigma = pe.Node( interface=niu.Function( input_names=["temporal_filter_width", "repetition_time"], output_names=["highpass_sigma"], function=calc_highpass_sigma, ), name="calchighpasssigma", ) workflow.connect(metadatanode, "repetition_time", calchighpasssigma, "repetition_time") calchighpasssigma.inputs.temporal_filter_width = second( tagdict["band_pass_filtered"]) highpass = pe.Node(fsl.TemporalFilter(), name="gaussianfilter") workflow.connect(calchighpasssigma, "highpass_sigma", highpass, "highpass_sigma") workflow.connect(*boldfileendpoint, highpass, "in_file") need_to_add_mean = True if "confounds_removed" in tagdict: confoundnames = tagdict["confounds_removed"] if len(confoundnames) > 0: ortendpoint, _ = make_confoundsendpoint("post", workflow, boldfileendpoint, confoundnames, memcalc) if bandpass is not None or ortendpoint is not None: tproject = pe.Node(afni.TProject(polort=1, out_file="tproject.nii"), name="tproject") workflow.connect(*boldfileendpoint, tproject, "in_file") workflow.connect(metadatanode, "repetition_time", tproject, "TR") if bandpass is not None: tproject.inputs.bandpass = bandpass if ortendpoint is not None: workflow.connect(*ortendpoint, tproject, "ort") boldfileendpoint = (tproject, "out_file") need_to_add_mean = True if need_to_add_mean is True: meanfunc = pe.Node(interface=fsl.ImageMaths(op_string="-Tmean", suffix="_mean"), name="meanfunc") workflow.connect(*boldfileendpoint_for_meanfunc, meanfunc, "in_file") addmean = pe.Node(interface=fsl.BinaryMaths(operation="add"), name="addmean") workflow.connect(*boldfileendpoint, addmean, "in_file") workflow.connect(meanfunc, "out_file", addmean, "operand_file") boldfileendpoint = (addmean, "out_file") applymask = pe.Node( interface=fsl.ApplyMask(), name="applymask", mem_gb=memcalc.volume_std_gb, ) workflow.connect(*boldfileendpoint, applymask, "in_file") workflow.connect(inputnode, "bold_mask_std", applymask, "mask_file") boldfileendpoint = (applymask, "out_file") endpoints = [boldfileendpoint] # boldfile is finished if "confounds_extract" in tagdict: # last confoundnames = tagdict["confounds_extract"] confoundsextractendpoint, confoundsextractendpointwithheader = make_confoundsendpoint( "extract", workflow, boldfileendpoint, confoundnames, memcalc) endpoints.append(confoundsextractendpoint) endpoints.append(confoundsextractendpointwithheader) outnames = [f"out{i+1}" for i in range(len(endpoints))] outputnode = pe.Node( niu.IdentityInterface(fields=[*outnames, "mask_file"]), name="outputnode", ) workflow.connect(inputnode, "bold_mask_std", outputnode, "mask_file") for outname, endpoint in zip(outnames, endpoints): workflow.connect(*endpoint, outputnode, outname) return workflow
def prepro_func(i): try: subj = i for s in (['session2']): # Define input files: 2xfMRI + 1xMPRAGE func1 = data_path + subj + '/Functional_scans/' + s[:-2] + s[ -1] + '_a/epi.nii.gz' #choose this for patients func2 = data_path + subj + '/Functional_scans/' + s[:-2] + s[ -1] + '_b/epi.nii.gz' #choose this for patients #anat = glob.glob(anat_path + subj +'/'+ s + '/anat/reorient/anat_*.nii.gz') #choose this for session 1 lesion_mask_file = anat_path + subj + '/session1/anat/reorient/lesion_seg.nii.gz' old_lesion_mask_file = glob.glob( anat_path + subj + '/session1/anat/reorient/old_lesion_seg.nii.gz' ) #choose this for ones with no old lesion #old_lesion_mask_file = anat_path + subj +'/session1/anat/reorient/old_lesion_seg.nii.gz' #choose this for ones with old lesion anat = glob.glob(anat_path + subj + '/' + s + '/anat/anat2hr/anat_*.nii.gz' ) #choose this for sessions 2 and 3 anat_CSF = glob.glob( anat_path + subj + '/session1/seg_anat/segmentation/anat_*_pve_0.nii.gz' ) # don't change, same for all sessions anat_WM = glob.glob( anat_path + subj + '/session1/seg_anat/segmentation/anat_*_pve_2.nii.gz' ) # don't change, same for all sessions anat_GM = glob.glob( anat_path + subj + '/session1/seg_anat/segmentation/anat_*_pve_1.nii.gz' ) # don't change, same for all sessions anat2MNI_fieldwarp = glob.glob( anat_path + subj + '/session1/anat/nonlinear_reg/anat_*_fieldwarp.nii.gz' ) # don't change, same for all sessions if not os.path.isdir(data_path + subj + '/' + s): # No data exists continue if not os.path.isfile(func1): print '1. functional file ' + func1 + ' not found. Skipping!' continue if not os.path.isfile(func2): print '2. functional file ' + func2 + ' not found. Skipping!' continue if not anat: print 'Preprocessed anatomical file not found. Skipping!' continue if len(anat) > 1: print 'WARNING: found multiple files of preprocessed anatomical image!' continue anat = anat[0] if not anat2MNI_fieldwarp: print 'Anatomical registration to MNI152-space field file not found. Skipping!' continue if len(anat2MNI_fieldwarp) > 1: print 'WARNING: found multiple files of anat2MNI fieldwarp!' continue anat2MNI_fieldwarp = anat2MNI_fieldwarp[0] if not anat_CSF: anat_CSF = glob.glob( anat_path + subj + '/' + s + '/seg_anat/segmentation/anat_*_pve_0.nii.gz') if not anat_CSF: print 'Anatomical segmentation CSF file not found. Skipping!' continue if len(anat_CSF) > 1: print 'WARNING: found multiple files of anatomical CSF file!' continue anat_CSF = anat_CSF[0] if not anat_WM: anat_WM = glob.glob( anat_path + subj + '/' + s + '/seg_anat/segmentation/anat_*_pve_2.nii.gz') if not anat_WM: print 'Anatomical segmentation WM file not found. Skipping!' continue if len(anat_WM) > 1: print 'WARNING: found multiple files of anatomical WM file!' continue anat_WM = anat_WM[0] if not anat_GM: anat_GM = glob.glob( anat_path + subj + '/' + s + '/seg_anat/segmentation/anat_*_pve_1.nii.gz') if not anat_GM: print 'Anatomical segmentation GM file not found. Skipping!' continue if len(anat_GM) > 1: print 'WARNING: found multiple files of anatomical GM file!' continue anat_GM = anat_GM[0] if not os.path.isdir(results_path + subj): os.mkdir(results_path + subj) if not os.path.isdir(results_path + subj + '/' + s): os.mkdir(results_path + subj + '/' + s) for data in acquisitions: os.chdir(results_path + subj + '/' + s) print "Currently processing subject: " + subj + '/' + s + ' ' + data #Initialize workflows workflow = pe.Workflow(name=data) workflow.base_dir = '.' inputnode = pe.Node( interface=util.IdentityInterface(fields=['source_file']), name='inputspec') outputnode = pe.Node( interface=util.IdentityInterface(fields=['result_func']), name='outputspec') if data == 'func1': inputnode.inputs.source_file = func1 else: inputnode.inputs.source_file = func2 # Remove n_dummies first volumes trim = pe.Node(interface=Trim(begin_index=n_dummies), name='trim') workflow.connect(inputnode, 'source_file', trim, 'in_file') # Motion correction + slice timing correction realign4d = pe.Node(interface=SpaceTimeRealigner(), name='realign4d') #realign4d.inputs.ignore_exception=True realign4d.inputs.slice_times = 'asc_alt_siemens' realign4d.inputs.slice_info = 2 # horizontal slices realign4d.inputs.tr = mytr # TR in seconds workflow.connect(trim, 'out_file', realign4d, 'in_file') # Reorient #deoblique = pe.Node(interface=afni.Warp(deoblique=True, outputtype='NIFTI_GZ'), name='deoblique') #leave out if you don't need this #workflow.connect(realign4d, 'out_file', deoblique, 'in_file') reorient = pe.Node( interface=fsl.Reorient2Std(output_type='NIFTI_GZ'), name='reorient') workflow.connect(realign4d, 'out_file', reorient, 'in_file') # AFNI skullstrip and mean image skullstrip tstat1 = pe.Node(interface=afni.TStat(args='-mean', outputtype="NIFTI_GZ"), name='tstat1') automask = pe.Node(interface=afni.Automask( dilate=1, outputtype="NIFTI_GZ"), name='automask') skullstrip = pe.Node(interface=afni.Calc( expr='a*b', outputtype="NIFTI_GZ"), name='skullstrip') tstat2 = pe.Node(interface=afni.TStat(args='-mean', outputtype="NIFTI_GZ"), name='tstat2') workflow.connect(reorient, 'out_file', tstat1, 'in_file') workflow.connect(tstat1, 'out_file', automask, 'in_file') workflow.connect(automask, 'out_file', skullstrip, 'in_file_b') workflow.connect(reorient, 'out_file', skullstrip, 'in_file_a') workflow.connect(skullstrip, 'out_file', tstat2, 'in_file') # Register to anatomical space #can be changed #mean2anat = pe.Node(fsl.FLIRT(bins=40, cost='normmi', dof=7, interp='nearestneighbour', searchr_x=[-180,180], searchr_y=[-180,180], searchr_z=[-180,180]), name='mean2anat') mean2anat = pe.Node(fsl.FLIRT(bins=40, cost='normmi', dof=7, interp='nearestneighbour'), name='mean2anat') #mean2anat = pe.Node(fsl.FLIRT(no_search=True), name='mean2anat') mean2anat.inputs.reference = anat workflow.connect(tstat2, 'out_file', mean2anat, 'in_file') # Transform mean functional image warpmean = pe.Node(interface=fsl.ApplyWarp(), name='warpmean') warpmean.inputs.ref_file = MNI_brain warpmean.inputs.field_file = anat2MNI_fieldwarp workflow.connect(mean2anat, 'out_matrix_file', warpmean, 'premat') workflow.connect(tstat2, 'out_file', warpmean, 'in_file') # ----- inversion matrix and eroded brain mask for regression ----- # create inverse matrix from mean2anat registration invmat = pe.Node(fsl.ConvertXFM(), name='invmat') invmat.inputs.invert_xfm = True workflow.connect(mean2anat, 'out_matrix_file', invmat, 'in_file') # erode functional brain mask erode_brain = pe.Node(fsl.ImageMaths(), name='erode_brain') erode_brain.inputs.args = '-kernel boxv 3 -ero' workflow.connect(automask, 'out_file', erode_brain, 'in_file') # register GM mask to functional image space, this is done for quality control reg_GM = pe.Node(fsl.preprocess.ApplyXFM(), name='register_GM') reg_GM.inputs.apply_xfm = True reg_GM.inputs.in_file = anat_GM workflow.connect(tstat2, 'out_file', reg_GM, 'reference') workflow.connect(invmat, 'out_file', reg_GM, 'in_matrix_file') # --------- motion regression and censor signals ------------------ # normalize motion parameters norm_motion = pe.Node(interface=Function( input_names=['in_file'], output_names=['out_file'], function=normalize_motion_data), name='normalize_motion') workflow.connect(realign4d, 'par_file', norm_motion, 'in_file') # create censor file, for censoring motion get_censor = pe.Node(afni.OneDToolPy(), name='motion_censors') get_censor.inputs.set_nruns = 1 get_censor.inputs.censor_motion = (censor_thr, 'motion') get_censor.inputs.show_censor_count = True if overwrite: get_censor.inputs.args = '-overwrite' workflow.connect(norm_motion, 'out_file', get_censor, 'in_file') # compute motion parameter derivatives (for use in regression) deriv_motion = pe.Node(afni.OneDToolPy(), name='deriv_motion') deriv_motion.inputs.set_nruns = 1 deriv_motion.inputs.derivative = True if overwrite: deriv_motion.inputs.args = '-overwrite' deriv_motion.inputs.out_file = 'motion_derivatives.txt' workflow.connect(norm_motion, 'out_file', deriv_motion, 'in_file') # scale motion parameters and get quadratures quadr_motion = pe.Node(interface=Function( input_names=['in_file', 'multicol'], output_names=['out_file', 'out_quadr_file'], function=scale_and_quadrature), name='quadr_motion') quadr_motion.inputs.multicol = True workflow.connect(norm_motion, 'out_file', quadr_motion, 'in_file') # scale motion derivatives and get quadratures quadr_motion_deriv = pe.Node(interface=Function( input_names=['in_file', 'multicol'], output_names=['out_file', 'out_quadr_file'], function=scale_and_quadrature), name='quadr_motion_deriv') quadr_motion_deriv.inputs.multicol = True workflow.connect(deriv_motion, 'out_file', quadr_motion_deriv, 'in_file') # -------- CSF regression signals --------------- # threshold and erode CSF mask erode_CSF_mask = pe.Node(fsl.ImageMaths(), name='erode_CSF_mask') erode_CSF_mask.inputs.args = '-thr 0.5 -kernel boxv 3 -ero' erode_CSF_mask.inputs.in_file = anat_CSF # register CSF mask to functional image space reg_CSF_mask = pe.Node(fsl.preprocess.ApplyXFM(), name='register_CSF_mask') reg_CSF_mask.inputs.apply_xfm = True workflow.connect(tstat2, 'out_file', reg_CSF_mask, 'reference') workflow.connect(invmat, 'out_file', reg_CSF_mask, 'in_matrix_file') # inverse lesion mask and remove it from CSF mask #remove this if you don't have a lesion mask inverse_lesion_mask = pe.Node(fsl.ImageMaths(), name='inverse_lesion_mask') inverse_lesion_mask.inputs.args = '-add 1 -rem 2' inverse_lesion_mask.inputs.in_file = lesion_mask_file rem_lesion = pe.Node(fsl.ImageMaths(), name='remove_lesion') workflow.connect(erode_CSF_mask, 'out_file', rem_lesion, 'in_file') workflow.connect(inverse_lesion_mask, 'out_file', rem_lesion, 'mask_file') ''' # Transform lesion mask to MNI152 space #remove if lesion masks are already in MNI152 space warp_lesion = pe.Node(interface=fsl.ApplyWarp(), name='warp_lesion') warp_lesion.inputs.ref_file = MNI_brain warp_lesion.inputs.field_file = anat2MNI_fieldwarp warp_lesion.inputs.in_file = lesion_mask_file warp_lesion.inputs.out_file = anat_path + subj +'/'+ s + '/anat/nonlinear_reg/lesion_seg_warp.nii.gz' warp_lesion.run() ''' # inverse old lesion mask and remove it from CSF mask #remove this if you don't have a lesion mask if old_lesion_mask_file: inverse_old_lesion_mask = pe.Node( fsl.ImageMaths(), name='inverse_old_lesion_mask') inverse_old_lesion_mask.inputs.args = '-add 1 -rem 3' #inverse_old_lesion_mask.inputs.in_file = old_lesion_mask_file[0] inverse_old_lesion_mask.inputs.in_file = old_lesion_mask_file rem_old_lesion = pe.Node(fsl.ImageMaths(), name='remove_old_lesion') workflow.connect(rem_lesion, 'out_file', rem_old_lesion, 'in_file') workflow.connect(inverse_old_lesion_mask, 'out_file', rem_old_lesion, 'mask_file') workflow.connect(rem_old_lesion, 'out_file', reg_CSF_mask, 'in_file') ''' # Transform old lesion mask to MNI152 space #remove if lesion masks are already in MNI152 space warp_old_lesion = pe.Node(interface=fsl.ApplyWarp(), name='warp_old_lesion') warp_old_lesion.inputs.ref_file = MNI_brain warp_old_lesion.inputs.field_file = anat2MNI_fieldwarp warp_old_lesion.inputs.in_file = old_lesion_mask_file warp_old_lesion.inputs.out_file = anat_path + subj +'/'+ s + '/anat/nonlinear_reg/old_lesion_seg_warp.nii.gz' warp_old_lesion.run() ''' else: workflow.connect(rem_lesion, 'out_file', reg_CSF_mask, 'in_file') # threshold CSF mask and intersect with functional brain mask thr_CSF_mask = pe.Node(fsl.ImageMaths(), name='threshold_CSF_mask') thr_CSF_mask.inputs.args = '-thr 0.25' workflow.connect(reg_CSF_mask, 'out_file', thr_CSF_mask, 'in_file') workflow.connect(erode_brain, 'out_file', thr_CSF_mask, 'mask_file') # extract CSF values get_CSF_noise = pe.Node(fsl.ImageMeants(), name='get_CSF_noise') workflow.connect(skullstrip, 'out_file', get_CSF_noise, 'in_file') workflow.connect(thr_CSF_mask, 'out_file', get_CSF_noise, 'mask') # compute CSF noise derivatives deriv_CSF = pe.Node(afni.OneDToolPy(), name='deriv_CSF') deriv_CSF.inputs.set_nruns = 1 deriv_CSF.inputs.derivative = True if overwrite: deriv_CSF.inputs.args = '-overwrite' deriv_CSF.inputs.out_file = 'CSF_derivatives.txt' workflow.connect(get_CSF_noise, 'out_file', deriv_CSF, 'in_file') # scale SCF noise and get quadratures quadr_CSF = pe.Node(interface=Function( input_names=['in_file', 'multicol'], output_names=['out_file', 'out_quadr_file'], function=scale_and_quadrature), name='quadr_CSF') quadr_CSF.inputs.multicol = False workflow.connect(get_CSF_noise, 'out_file', quadr_CSF, 'in_file') # scale CSF noise derivatives and get quadratures quadr_CSF_deriv = pe.Node(interface=Function( input_names=['in_file', 'multicol'], output_names=['out_file', 'out_quadr_file'], function=scale_and_quadrature), name='quadr_CSF_deriv') quadr_CSF_deriv.inputs.multicol = False workflow.connect(deriv_CSF, 'out_file', quadr_CSF_deriv, 'in_file') # -------- WM regression signals ----------------- # threshold and erode WM mask erode_WM_mask = pe.Node(fsl.ImageMaths(), name='erode_WM_mask') erode_WM_mask.inputs.args = '-thr 0.5 -kernel boxv 7 -ero' erode_WM_mask.inputs.in_file = anat_WM # registrer WM mask to functional image space reg_WM_mask = pe.Node(fsl.preprocess.ApplyXFM(), name='register_WM_mask') reg_WM_mask.inputs.apply_xfm = True workflow.connect(tstat2, 'out_file', reg_WM_mask, 'reference') workflow.connect(invmat, 'out_file', reg_WM_mask, 'in_matrix_file') workflow.connect(erode_WM_mask, 'out_file', reg_WM_mask, 'in_file') # create inverse nonlinear registration MNI2anat invwarp = pe.Node(fsl.InvWarp(output_type='NIFTI_GZ'), name='invwarp') invwarp.inputs.warp = anat2MNI_fieldwarp invwarp.inputs.reference = anat # transform ventricle mask to functional space reg_ventricles = pe.Node(fsl.ApplyWarp(), name='register_ventricle_mask') reg_ventricles.inputs.in_file = ventricle_mask workflow.connect(tstat2, 'out_file', reg_ventricles, 'ref_file') workflow.connect(invwarp, 'inverse_warp', reg_ventricles, 'field_file') workflow.connect(invmat, 'out_file', reg_ventricles, 'postmat') # threshold WM mask and intersect with functional brain mask thr_WM_mask = pe.Node(fsl.ImageMaths(), name='threshold_WM_mask') thr_WM_mask.inputs.args = '-thr 0.25' workflow.connect(reg_WM_mask, 'out_file', thr_WM_mask, 'in_file') workflow.connect(erode_brain, 'out_file', thr_WM_mask, 'mask_file') # remove ventricles from WM mask exclude_ventricles = pe.Node(fsl.ImageMaths(), name='exclude_ventricles') workflow.connect(thr_WM_mask, 'out_file', exclude_ventricles, 'in_file') workflow.connect(reg_ventricles, 'out_file', exclude_ventricles, 'mask_file') # check that WM is collected from both hemispheres check_WM_bilat = pe.Node(interface=Function( input_names=['in_file'], output_names=['errors'], function=check_bilateralism), name='check_WM_bilateralism') workflow.connect(exclude_ventricles, 'out_file', check_WM_bilat, 'in_file') # extract WM values get_WM_noise = pe.Node(fsl.ImageMeants(), name='get_WM_noise') workflow.connect(skullstrip, 'out_file', get_WM_noise, 'in_file') workflow.connect(exclude_ventricles, 'out_file', get_WM_noise, 'mask') # compute WM noise derivatives deriv_WM = pe.Node(afni.OneDToolPy(), name='deriv_WM') deriv_WM.inputs.set_nruns = 1 deriv_WM.inputs.derivative = True if overwrite: deriv_WM.inputs.args = '-overwrite' deriv_WM.inputs.out_file = 'WM_derivatives.txt' workflow.connect(get_WM_noise, 'out_file', deriv_WM, 'in_file') # scale WM noise and get quadratures quadr_WM = pe.Node(interface=Function( input_names=['in_file', 'multicol'], output_names=['out_file', 'out_quadr_file'], function=scale_and_quadrature), name='quadr_WM') quadr_WM.inputs.multicol = False workflow.connect(get_WM_noise, 'out_file', quadr_WM, 'in_file') # scale WM noise derivatives and get quadratures quadr_WM_deriv = pe.Node(interface=Function( input_names=['in_file', 'multicol'], output_names=['out_file', 'out_quadr_file'], function=scale_and_quadrature), name='quadr_WM_deriv') quadr_WM_deriv.inputs.multicol = False workflow.connect(deriv_WM, 'out_file', quadr_WM_deriv, 'in_file') # ---------- global regression signals ---------------- if global_reg: # register anatomical whole brain mask to functional image space reg_glob_mask = pe.Node(fsl.preprocess.ApplyXFM(), name='register_global_mask') reg_glob_mask.inputs.apply_xfm = True reg_glob_mask.inputs.in_file = anat workflow.connect(tstat2, 'out_file', reg_glob_mask, 'reference') workflow.connect(invmat, 'out_file', reg_glob_mask, 'in_matrix_file') # threshold anatomical brain mask and intersect with functional brain mask thr_glob_mask = pe.Node(fsl.ImageMaths(), name='threshold_global_mask') thr_glob_mask.inputs.args = '-thr -0.1' workflow.connect(reg_glob_mask, 'out_file', thr_glob_mask, 'in_file') workflow.connect(erode_brain, 'out_file', thr_glob_mask, 'mask_file') # extract global signal values get_glob_noise = pe.Node(fsl.ImageMeants(), name='get_global_noise') workflow.connect(skullstrip, 'out_file', get_glob_noise, 'in_file') workflow.connect(thr_glob_mask, 'out_file', get_glob_noise, 'mask') # compute global noise derivative deriv_glob = pe.Node(afni.OneDToolPy(), name='deriv_global') deriv_glob.inputs.set_nruns = 1 deriv_glob.inputs.derivative = True if overwrite: deriv_glob.inputs.args = '-overwrite' deriv_glob.inputs.out_file = 'global_derivatives.txt' workflow.connect(get_glob_noise, 'out_file', deriv_glob, 'in_file') # scale global noise and get quadratures quadr_glob = pe.Node(interface=Function( input_names=['in_file', 'multicol'], output_names=['out_file', 'out_quadr_file'], function=scale_and_quadrature), name='quadr_glob') quadr_glob.inputs.multicol = False workflow.connect(get_glob_noise, 'out_file', quadr_glob, 'in_file') # scale global noise derivatives and get quadratures quadr_glob_deriv = pe.Node(interface=Function( input_names=['in_file', 'multicol'], output_names=['out_file', 'out_quadr_file'], function=scale_and_quadrature), name='quadr_glob_deriv') quadr_glob_deriv.inputs.multicol = False workflow.connect(deriv_glob, 'out_file', quadr_glob_deriv, 'in_file') # ---------- regression matrix ---------- # create bandpass regressors, can not be easily implemented to workflow get_bandpass = pe.Node(interface=Function( input_names=['minf', 'maxf', 'example_file', 'tr'], output_names=['out_tuple'], function=bandpass), name='bandpass_regressors') get_bandpass.inputs.minf = myminf get_bandpass.inputs.maxf = mymaxf get_bandpass.inputs.tr = mytr workflow.connect(norm_motion, 'out_file', get_bandpass, 'example_file') # concatenate regressor time series cat_reg_name = 'cat_regressors' if global_reg: cat_reg_name = cat_reg_name + '_global' cat_reg = pe.Node(interface=Function( input_names=[ 'mot', 'motd', 'motq', 'motdq', 'CSF', 'CSFd', 'CSFq', 'CSFdq', 'WM', 'WMd', 'WMq', 'WMdq', 'include_global', 'glob', 'globd', 'globq', 'globdq' ], output_names=['reg_file_args'], function=concatenate_regressors), name=cat_reg_name) cat_reg.inputs.include_global = global_reg workflow.connect(quadr_motion, 'out_file', cat_reg, 'mot') workflow.connect(quadr_motion_deriv, 'out_file', cat_reg, 'motd') workflow.connect(quadr_motion, 'out_quadr_file', cat_reg, 'motq') workflow.connect(quadr_motion_deriv, 'out_quadr_file', cat_reg, 'motdq') workflow.connect(quadr_CSF, 'out_file', cat_reg, 'CSF') workflow.connect(quadr_CSF_deriv, 'out_file', cat_reg, 'CSFd') workflow.connect(quadr_CSF, 'out_quadr_file', cat_reg, 'CSFq') workflow.connect(quadr_CSF_deriv, 'out_quadr_file', cat_reg, 'CSFdq') workflow.connect(quadr_WM, 'out_file', cat_reg, 'WM') workflow.connect(quadr_WM_deriv, 'out_file', cat_reg, 'WMd') workflow.connect(quadr_WM, 'out_quadr_file', cat_reg, 'WMq') workflow.connect(quadr_WM_deriv, 'out_quadr_file', cat_reg, 'WMdq') if global_reg: workflow.connect(quadr_glob, 'out_file', cat_reg, 'glob') workflow.connect(quadr_glob_deriv, 'out_file', cat_reg, 'globd') workflow.connect(quadr_glob, 'out_quadr_file', cat_reg, 'globq') workflow.connect(quadr_glob_deriv, 'out_quadr_file', cat_reg, 'globdq') else: cat_reg.inputs.glob = None cat_reg.inputs.globd = None cat_reg.inputs.globq = None cat_reg.inputs.globdq = None # create regression matrix deconvolve_name = 'deconvolve' if global_reg: deconvolve_name = deconvolve_name + '_global' deconvolve = pe.Node(afni.Deconvolve(), name=deconvolve_name) deconvolve.inputs.polort = 2 # contstant, linear and quadratic background signals removed deconvolve.inputs.fout = True deconvolve.inputs.tout = True deconvolve.inputs.x1D_stop = True deconvolve.inputs.force_TR = mytr workflow.connect(cat_reg, 'reg_file_args', deconvolve, 'args') workflow.connect(get_bandpass, 'out_tuple', deconvolve, 'ortvec') workflow.connect([(skullstrip, deconvolve, [(('out_file', str2list), 'in_files')])]) # regress out motion and other unwanted signals tproject_name = 'tproject' if global_reg: tproject_name = tproject_name + '_global' tproject = pe.Node(afni.TProject(outputtype="NIFTI_GZ"), name=tproject_name) tproject.inputs.TR = mytr tproject.inputs.polort = 0 # use matrix created with 3dDeconvolve, higher order polynomials not needed tproject.inputs.cenmode = 'NTRP' # interpolate removed time points workflow.connect(get_censor, 'out_file', tproject, 'censor') workflow.connect(skullstrip, 'out_file', tproject, 'in_file') workflow.connect(automask, 'out_file', tproject, 'mask') workflow.connect(deconvolve, 'x1D', tproject, 'ort') # Transform all images warpall_name = 'warpall' if global_reg: warpall_name = warpall_name + '_global' warpall = pe.Node(interface=fsl.ApplyWarp(), name=warpall_name) warpall.inputs.ref_file = MNI_brain warpall.inputs.field_file = anat2MNI_fieldwarp workflow.connect(mean2anat, 'out_matrix_file', warpall, 'premat') workflow.connect(tproject, 'out_file', warpall, 'in_file') workflow.connect(warpall, 'out_file', outputnode, 'result_func') # Run workflow workflow.write_graph() workflow.run() print "FUNCTIONAL PREPROCESSING DONE! Results in ", results_path + subj + '/' + s except: print "Error with patient: ", subj traceback.print_exc()