fMRI: OpenfMRI.org data, FSL, ANTS, c3daffine¶
A growing number of datasets are available on OpenfMRI. This script demonstrates how to use nipype to analyze a data set:
python fmri_ants_openfmri.py --datasetdir ds107
from __future__ import division
from builtins import range
from nipype import config
config.enable_provenance()
from glob import glob
import os
import nipype.pipeline.engine as pe
import nipype.algorithms.modelgen as model
import nipype.algorithms.rapidart as ra
from nipype.algorithms.misc import TSNR
from nipype.external.six import string_types
from nipype.interfaces.c3 import C3dAffineTool
import nipype.interfaces.io as nio
import nipype.interfaces.utility as niu
from nipype.workflows.fmri.fsl import (create_featreg_preproc,
create_modelfit_workflow,
create_fixed_effects_flow)
from nipype import LooseVersion
from nipype import Workflow, Node, MapNode
from nipype.interfaces import (fsl, Function, ants, freesurfer)
from nipype.interfaces.utility import Merge, IdentityInterface
from nipype.utils.filemanip import filename_to_list
from nipype.interfaces.io import FreeSurferSource
import nipype.interfaces.freesurfer as fs
version = 0
if fsl.Info.version() and \
LooseVersion(fsl.Info.version()) > LooseVersion('5.0.6'):
version = 507
fsl.FSLCommand.set_default_output_type('NIFTI_GZ')
imports = ['import os',
'import nibabel as nb',
'import numpy as np',
'import scipy as sp',
'from nipype.utils.filemanip import filename_to_list, list_to_filename, split_filename',
'from scipy.special import legendre'
]
def median(in_files):
"""Computes an average of the median of each realigned timeseries
Parameters
----------
in_files: one or more realigned Nifti 4D time series
Returns
-------
out_file: a 3D Nifti file
"""
average = None
for idx, filename in enumerate(filename_to_list(in_files)):
img = nb.load(filename)
data = np.median(img.get_data(), axis=3)
if average is None:
average = data
else:
average = average + data
median_img = nb.Nifti1Image(average / float(idx + 1), img.affine,
img.header)
filename = os.path.join(os.getcwd(), 'median.nii.gz')
median_img.to_filename(filename)
return filename
def create_reg_workflow(name='registration'):
"""Create a FEAT preprocessing workflow together with freesurfer
Parameters
----------
name : name of workflow (default: 'registration')
Inputs:
inputspec.source_files : files (filename or list of filenames to register)
inputspec.mean_image : reference image to use
inputspec.anatomical_image : anatomical image to coregister to
inputspec.target_image : registration target
Outputs:
outputspec.func2anat_transform : FLIRT transform
outputspec.anat2target_transform : FLIRT+FNIRT transform
outputspec.transformed_files : transformed files in target space
outputspec.transformed_mean : mean image in target space
"""
register = pe.Workflow(name=name)
inputnode = pe.Node(interface=niu.IdentityInterface(fields=['source_files',
'mean_image',
'anatomical_image',
'target_image',
'target_image_brain',
'config_file']),
name='inputspec')
outputnode = pe.Node(interface=niu.IdentityInterface(fields=['func2anat_transform',
'anat2target_transform',
'transformed_files',
'transformed_mean',
'anat2target',
'mean2anat_mask'
]),
name='outputspec')
Estimate the tissue classes from the anatomical image. But use spm’s segment as FSL appears to be breaking.
stripper = pe.Node(fsl.BET(), name='stripper')
register.connect(inputnode, 'anatomical_image', stripper, 'in_file')
fast = pe.Node(fsl.FAST(), name='fast')
register.connect(stripper, 'out_file', fast, 'in_files')
Binarize the segmentation
binarize = pe.Node(fsl.ImageMaths(op_string='-nan -thr 0.5 -bin'),
name='binarize')
pickindex = lambda x, i: x[i]
register.connect(fast, ('partial_volume_files', pickindex, 2),
binarize, 'in_file')
Calculate rigid transform from mean image to anatomical image
mean2anat = pe.Node(fsl.FLIRT(), name='mean2anat')
mean2anat.inputs.dof = 6
register.connect(inputnode, 'mean_image', mean2anat, 'in_file')
register.connect(stripper, 'out_file', mean2anat, 'reference')
Now use bbr cost function to improve the transform
mean2anatbbr = pe.Node(fsl.FLIRT(), name='mean2anatbbr')
mean2anatbbr.inputs.dof = 6
mean2anatbbr.inputs.cost = 'bbr'
mean2anatbbr.inputs.schedule = os.path.join(os.getenv('FSLDIR'),
'etc/flirtsch/bbr.sch')
register.connect(inputnode, 'mean_image', mean2anatbbr, 'in_file')
register.connect(binarize, 'out_file', mean2anatbbr, 'wm_seg')
register.connect(inputnode, 'anatomical_image', mean2anatbbr, 'reference')
register.connect(mean2anat, 'out_matrix_file',
mean2anatbbr, 'in_matrix_file')
Create a mask of the median image coregistered to the anatomical image
mean2anat_mask = Node(fsl.BET(mask=True), name='mean2anat_mask')
register.connect(mean2anatbbr, 'out_file', mean2anat_mask, 'in_file')
Convert the BBRegister transformation to ANTS ITK format
convert2itk = pe.Node(C3dAffineTool(),
name='convert2itk')
convert2itk.inputs.fsl2ras = True
convert2itk.inputs.itk_transform = True
register.connect(mean2anatbbr, 'out_matrix_file', convert2itk, 'transform_file')
register.connect(inputnode, 'mean_image', convert2itk, 'source_file')
register.connect(stripper, 'out_file', convert2itk, 'reference_file')
Compute registration between the subject’s structural and MNI template This is currently set to perform a very quick registration. However, the registration can be made significantly more accurate for cortical structures by increasing the number of iterations All parameters are set using the example from: #https://github.com/stnava/ANTs/blob/master/Scripts/newAntsExample.sh
reg = pe.Node(ants.Registration(), name='antsRegister')
reg.inputs.output_transform_prefix = "output_"
reg.inputs.transforms = ['Rigid', 'Affine', 'SyN']
reg.inputs.transform_parameters = [(0.1,), (0.1,), (0.2, 3.0, 0.0)]
reg.inputs.number_of_iterations = [[10000, 11110, 11110]] * 2 + [[100, 30, 20]]
reg.inputs.dimension = 3
reg.inputs.write_composite_transform = True
reg.inputs.collapse_output_transforms = True
reg.inputs.initial_moving_transform_com = True
reg.inputs.metric = ['Mattes'] * 2 + [['Mattes', 'CC']]
reg.inputs.metric_weight = [1] * 2 + [[0.5, 0.5]]
reg.inputs.radius_or_number_of_bins = [32] * 2 + [[32, 4]]
reg.inputs.sampling_strategy = ['Regular'] * 2 + [[None, None]]
reg.inputs.sampling_percentage = [0.3] * 2 + [[None, None]]
reg.inputs.convergence_threshold = [1.e-8] * 2 + [-0.01]
reg.inputs.convergence_window_size = [20] * 2 + [5]
reg.inputs.smoothing_sigmas = [[4, 2, 1]] * 2 + [[1, 0.5, 0]]
reg.inputs.sigma_units = ['vox'] * 3
reg.inputs.shrink_factors = [[3, 2, 1]] * 2 + [[4, 2, 1]]
reg.inputs.use_estimate_learning_rate_once = [True] * 3
reg.inputs.use_histogram_matching = [False] * 2 + [True]
reg.inputs.winsorize_lower_quantile = 0.005
reg.inputs.winsorize_upper_quantile = 0.995
reg.inputs.args = '--float'
reg.inputs.output_warped_image = 'output_warped_image.nii.gz'
reg.inputs.num_threads = 4
reg.plugin_args = {'qsub_args': '-pe orte 4',
'sbatch_args': '--mem=6G -c 4'}
register.connect(stripper, 'out_file', reg, 'moving_image')
register.connect(inputnode, 'target_image_brain', reg, 'fixed_image')
Concatenate the affine and ants transforms into a list
pickfirst = lambda x: x[0]
merge = pe.Node(niu.Merge(2), iterfield=['in2'], name='mergexfm')
register.connect(convert2itk, 'itk_transform', merge, 'in2')
register.connect(reg, 'composite_transform', merge, 'in1')
Transform the mean image. First to anatomical and then to target
warpmean = pe.Node(ants.ApplyTransforms(),
name='warpmean')
warpmean.inputs.input_image_type = 0
warpmean.inputs.interpolation = 'Linear'
warpmean.inputs.invert_transform_flags = [False, False]
warpmean.inputs.terminal_output = 'file'
register.connect(inputnode, 'target_image_brain', warpmean, 'reference_image')
register.connect(inputnode, 'mean_image', warpmean, 'input_image')
register.connect(merge, 'out', warpmean, 'transforms')
Transform the remaining images. First to anatomical and then to target
warpall = pe.MapNode(ants.ApplyTransforms(),
iterfield=['input_image'],
name='warpall')
warpall.inputs.input_image_type = 0
warpall.inputs.interpolation = 'Linear'
warpall.inputs.invert_transform_flags = [False, False]
warpall.inputs.terminal_output = 'file'
register.connect(inputnode, 'target_image_brain', warpall, 'reference_image')
register.connect(inputnode, 'source_files', warpall, 'input_image')
register.connect(merge, 'out', warpall, 'transforms')
Assign all the output files
register.connect(reg, 'warped_image', outputnode, 'anat2target')
register.connect(warpmean, 'output_image', outputnode, 'transformed_mean')
register.connect(warpall, 'output_image', outputnode, 'transformed_files')
register.connect(mean2anatbbr, 'out_matrix_file',
outputnode, 'func2anat_transform')
register.connect(mean2anat_mask, 'mask_file',
outputnode, 'mean2anat_mask')
register.connect(reg, 'composite_transform',
outputnode, 'anat2target_transform')
return register
def get_aparc_aseg(files):
"""Return the aparc+aseg.mgz file"""
for name in files:
if 'aparc+aseg.mgz' in name:
return name
raise ValueError('aparc+aseg.mgz not found')
def create_fs_reg_workflow(name='registration'):
"""Create a FEAT preprocessing workflow together with freesurfer
Parameters
----------
name : name of workflow (default: 'registration')
Inputs::
inputspec.source_files : files (filename or list of filenames to register)
inputspec.mean_image : reference image to use
inputspec.target_image : registration target
Outputs::
outputspec.func2anat_transform : FLIRT transform
outputspec.anat2target_transform : FLIRT+FNIRT transform
outputspec.transformed_files : transformed files in target space
outputspec.transformed_mean : mean image in target space
"""
register = Workflow(name=name)
inputnode = Node(interface=IdentityInterface(fields=['source_files',
'mean_image',
'subject_id',
'subjects_dir',
'target_image']),
name='inputspec')
outputnode = Node(interface=IdentityInterface(fields=['func2anat_transform',
'out_reg_file',
'anat2target_transform',
'transforms',
'transformed_mean',
'transformed_files',
'min_cost_file',
'anat2target',
'aparc',
'mean2anat_mask'
]),
name='outputspec')
# Get the subject's freesurfer source directory
fssource = Node(FreeSurferSource(),
name='fssource')
fssource.run_without_submitting = True
register.connect(inputnode, 'subject_id', fssource, 'subject_id')
register.connect(inputnode, 'subjects_dir', fssource, 'subjects_dir')
convert = Node(freesurfer.MRIConvert(out_type='nii'),
name="convert")
register.connect(fssource, 'T1', convert, 'in_file')
# Coregister the median to the surface
bbregister = Node(freesurfer.BBRegister(registered_file=True),
name='bbregister')
bbregister.inputs.init = 'fsl'
bbregister.inputs.contrast_type = 't2'
bbregister.inputs.out_fsl_file = True
bbregister.inputs.epi_mask = True
register.connect(inputnode, 'subject_id', bbregister, 'subject_id')
register.connect(inputnode, 'mean_image', bbregister, 'source_file')
register.connect(inputnode, 'subjects_dir', bbregister, 'subjects_dir')
# Create a mask of the median coregistered to the anatomical image
mean2anat_mask = Node(fsl.BET(mask=True), name='mean2anat_mask')
register.connect(bbregister, 'registered_file', mean2anat_mask, 'in_file')
use aparc+aseg’s brain mask
binarize = Node(fs.Binarize(min=0.5, out_type="nii.gz", dilate=1), name="binarize_aparc")
register.connect(fssource, ("aparc_aseg", get_aparc_aseg), binarize, "in_file")
stripper = Node(fsl.ApplyMask(), name='stripper')
register.connect(binarize, "binary_file", stripper, "mask_file")
register.connect(convert, 'out_file', stripper, 'in_file')
Apply inverse transform to aparc file
aparcxfm = Node(freesurfer.ApplyVolTransform(inverse=True,
interp='nearest'),
name='aparc_inverse_transform')
register.connect(inputnode, 'subjects_dir', aparcxfm, 'subjects_dir')
register.connect(bbregister, 'out_reg_file', aparcxfm, 'reg_file')
register.connect(fssource, ('aparc_aseg', get_aparc_aseg),
aparcxfm, 'target_file')
register.connect(inputnode, 'mean_image', aparcxfm, 'source_file')
Convert the BBRegister transformation to ANTS ITK format
convert2itk = Node(C3dAffineTool(), name='convert2itk')
convert2itk.inputs.fsl2ras = True
convert2itk.inputs.itk_transform = True
register.connect(bbregister, 'out_fsl_file', convert2itk, 'transform_file')
register.connect(inputnode, 'mean_image', convert2itk, 'source_file')
register.connect(stripper, 'out_file', convert2itk, 'reference_file')
Compute registration between the subject’s structural and MNI template This is currently set to perform a very quick registration. However, the registration can be made significantly more accurate for cortical structures by increasing the number of iterations All parameters are set using the example from: #https://github.com/stnava/ANTs/blob/master/Scripts/newAntsExample.sh
reg = Node(ants.Registration(), name='antsRegister')
reg.inputs.output_transform_prefix = "output_"
reg.inputs.transforms = ['Rigid', 'Affine', 'SyN']
reg.inputs.transform_parameters = [(0.1,), (0.1,), (0.2, 3.0, 0.0)]
reg.inputs.number_of_iterations = [[10000, 11110, 11110]] * 2 + [[100, 30, 20]]
reg.inputs.dimension = 3
reg.inputs.write_composite_transform = True
reg.inputs.collapse_output_transforms = True
reg.inputs.initial_moving_transform_com = True
reg.inputs.metric = ['Mattes'] * 2 + [['Mattes', 'CC']]
reg.inputs.metric_weight = [1] * 2 + [[0.5, 0.5]]
reg.inputs.radius_or_number_of_bins = [32] * 2 + [[32, 4]]
reg.inputs.sampling_strategy = ['Regular'] * 2 + [[None, None]]
reg.inputs.sampling_percentage = [0.3] * 2 + [[None, None]]
reg.inputs.convergence_threshold = [1.e-8] * 2 + [-0.01]
reg.inputs.convergence_window_size = [20] * 2 + [5]
reg.inputs.smoothing_sigmas = [[4, 2, 1]] * 2 + [[1, 0.5, 0]]
reg.inputs.sigma_units = ['vox'] * 3
reg.inputs.shrink_factors = [[3, 2, 1]] * 2 + [[4, 2, 1]]
reg.inputs.use_estimate_learning_rate_once = [True] * 3
reg.inputs.use_histogram_matching = [False] * 2 + [True]
reg.inputs.winsorize_lower_quantile = 0.005
reg.inputs.winsorize_upper_quantile = 0.995
reg.inputs.float = True
reg.inputs.output_warped_image = 'output_warped_image.nii.gz'
reg.inputs.num_threads = 4
reg.plugin_args = {'qsub_args': '-pe orte 4',
'sbatch_args': '--mem=6G -c 4'}
register.connect(stripper, 'out_file', reg, 'moving_image')
register.connect(inputnode, 'target_image', reg, 'fixed_image')
Concatenate the affine and ants transforms into a list
pickfirst = lambda x: x[0]
merge = Node(Merge(2), iterfield=['in2'], name='mergexfm')
register.connect(convert2itk, 'itk_transform', merge, 'in2')
register.connect(reg, 'composite_transform', merge, 'in1')
Transform the mean image. First to anatomical and then to target
warpmean = Node(ants.ApplyTransforms(), name='warpmean')
warpmean.inputs.input_image_type = 0
warpmean.inputs.interpolation = 'Linear'
warpmean.inputs.invert_transform_flags = [False, False]
warpmean.inputs.terminal_output = 'file'
warpmean.inputs.args = '--float'
# warpmean.inputs.num_threads = 4
# warpmean.plugin_args = {'sbatch_args': '--mem=4G -c 4'}
Transform the remaining images. First to anatomical and then to target
warpall = pe.MapNode(ants.ApplyTransforms(),
iterfield=['input_image'],
name='warpall')
warpall.inputs.input_image_type = 0
warpall.inputs.interpolation = 'Linear'
warpall.inputs.invert_transform_flags = [False, False]
warpall.inputs.terminal_output = 'file'
warpall.inputs.args = '--float'
warpall.inputs.num_threads = 2
warpall.plugin_args = {'sbatch_args': '--mem=6G -c 2'}
Assign all the output files
register.connect(warpmean, 'output_image', outputnode, 'transformed_mean')
register.connect(warpall, 'output_image', outputnode, 'transformed_files')
register.connect(inputnode, 'target_image', warpmean, 'reference_image')
register.connect(inputnode, 'mean_image', warpmean, 'input_image')
register.connect(merge, 'out', warpmean, 'transforms')
register.connect(inputnode, 'target_image', warpall, 'reference_image')
register.connect(inputnode, 'source_files', warpall, 'input_image')
register.connect(merge, 'out', warpall, 'transforms')
Assign all the output files
register.connect(reg, 'warped_image', outputnode, 'anat2target')
register.connect(aparcxfm, 'transformed_file',
outputnode, 'aparc')
register.connect(bbregister, 'out_fsl_file',
outputnode, 'func2anat_transform')
register.connect(bbregister, 'out_reg_file',
outputnode, 'out_reg_file')
register.connect(bbregister, 'min_cost_file',
outputnode, 'min_cost_file')
register.connect(mean2anat_mask, 'mask_file',
outputnode, 'mean2anat_mask')
register.connect(reg, 'composite_transform',
outputnode, 'anat2target_transform')
register.connect(merge, 'out', outputnode, 'transforms')
return register
Get info for a given subject
def get_subjectinfo(subject_id, base_dir, task_id, model_id):
"""Get info for a given subject
Parameters
----------
subject_id : string
Subject identifier (e.g., sub001)
base_dir : string
Path to base directory of the dataset
task_id : int
Which task to process
model_id : int
Which model to process
Returns
-------
run_ids : list of ints
Run numbers
conds : list of str
Condition names
TR : float
Repetition time
"""
from glob import glob
import os
import numpy as np
condition_info = []
cond_file = os.path.join(base_dir, 'models', 'model%03d' % model_id,
'condition_key.txt')
with open(cond_file, 'rt') as fp:
for line in fp:
info = line.strip().split()
condition_info.append([info[0], info[1], ' '.join(info[2:])])
if len(condition_info) == 0:
raise ValueError('No condition info found in %s' % cond_file)
taskinfo = np.array(condition_info)
n_tasks = len(np.unique(taskinfo[:, 0]))
conds = []
run_ids = []
if task_id > n_tasks:
raise ValueError('Task id %d does not exist' % task_id)
for idx in range(n_tasks):
taskidx = np.where(taskinfo[:, 0] == 'task%03d' % (idx + 1))
conds.append([condition.replace(' ', '_') for condition
in taskinfo[taskidx[0], 2]]) # if 'junk' not in condition])
files = sorted(glob(os.path.join(base_dir,
subject_id,
'BOLD',
'task%03d_run*' % (idx + 1))))
runs = [int(val[-3:]) for val in files]
run_ids.insert(idx, runs)
json_info = os.path.join(base_dir, subject_id, 'BOLD',
'task%03d_run%03d' % (task_id, run_ids[task_id - 1][0]),
'bold_scaninfo.json')
if os.path.exists(json_info):
import json
with open(json_info, 'rt') as fp:
data = json.load(fp)
TR = data['global']['const']['RepetitionTime'] / 1000.
else:
task_scan_key = os.path.join(base_dir, subject_id, 'BOLD',
'task%03d_run%03d' % (task_id, run_ids[task_id - 1][0]),
'scan_key.txt')
if os.path.exists(task_scan_key):
TR = np.genfromtxt(task_scan_key)[1]
else:
TR = np.genfromtxt(os.path.join(base_dir, 'scan_key.txt'))[1]
return run_ids[task_id - 1], conds[task_id - 1], TR
Analyzes an open fmri dataset
def analyze_openfmri_dataset(data_dir, subject=None, model_id=None,
task_id=None, output_dir=None, subj_prefix='*',
hpcutoff=120., use_derivatives=True,
fwhm=6.0, subjects_dir=None, target=None):
"""Analyzes an open fmri dataset
Parameters
----------
data_dir : str
Path to the base data directory
work_dir : str
Nipype working directory (defaults to cwd)
"""
Load nipype workflows
preproc = create_featreg_preproc(whichvol='first')
modelfit = create_modelfit_workflow()
fixed_fx = create_fixed_effects_flow()
if subjects_dir:
registration = create_fs_reg_workflow()
else:
registration = create_reg_workflow()
Remove the plotting connection so that plot iterables don’t propagate to the model stage
preproc.disconnect(preproc.get_node('plot_motion'), 'out_file',
preproc.get_node('outputspec'), 'motion_plots')
Set up openfmri data specific components
subjects = sorted([path.split(os.path.sep)[-1] for path in
glob(os.path.join(data_dir, subj_prefix))])
infosource = pe.Node(niu.IdentityInterface(fields=['subject_id',
'model_id',
'task_id']),
name='infosource')
if len(subject) == 0:
infosource.iterables = [('subject_id', subjects),
('model_id', [model_id]),
('task_id', task_id)]
else:
infosource.iterables = [('subject_id',
[subjects[subjects.index(subj)] for subj in subject]),
('model_id', [model_id]),
('task_id', task_id)]
subjinfo = pe.Node(niu.Function(input_names=['subject_id', 'base_dir',
'task_id', 'model_id'],
output_names=['run_id', 'conds', 'TR'],
function=get_subjectinfo),
name='subjectinfo')
subjinfo.inputs.base_dir = data_dir
Return data components as anat, bold and behav
contrast_file = os.path.join(data_dir, 'models', 'model%03d' % model_id,
'task_contrasts.txt')
has_contrast = os.path.exists(contrast_file)
if has_contrast:
datasource = pe.Node(nio.DataGrabber(infields=['subject_id', 'run_id',
'task_id', 'model_id'],
outfields=['anat', 'bold', 'behav',
'contrasts']),
name='datasource')
else:
datasource = pe.Node(nio.DataGrabber(infields=['subject_id', 'run_id',
'task_id', 'model_id'],
outfields=['anat', 'bold', 'behav']),
name='datasource')
datasource.inputs.base_directory = data_dir
datasource.inputs.template = '*'
if has_contrast:
datasource.inputs.field_template = {'anat': '%s/anatomy/T1_001.nii.gz',
'bold': '%s/BOLD/task%03d_r*/bold.nii.gz',
'behav': ('%s/model/model%03d/onsets/task%03d_'
'run%03d/cond*.txt'),
'contrasts': ('models/model%03d/'
'task_contrasts.txt')}
datasource.inputs.template_args = {'anat': [['subject_id']],
'bold': [['subject_id', 'task_id']],
'behav': [['subject_id', 'model_id',
'task_id', 'run_id']],
'contrasts': [['model_id']]}
else:
datasource.inputs.field_template = {'anat': '%s/anatomy/T1_001.nii.gz',
'bold': '%s/BOLD/task%03d_r*/bold.nii.gz',
'behav': ('%s/model/model%03d/onsets/task%03d_'
'run%03d/cond*.txt')}
datasource.inputs.template_args = {'anat': [['subject_id']],
'bold': [['subject_id', 'task_id']],
'behav': [['subject_id', 'model_id',
'task_id', 'run_id']]}
datasource.inputs.sort_filelist = True
Create meta workflow
wf = pe.Workflow(name='openfmri')
wf.connect(infosource, 'subject_id', subjinfo, 'subject_id')
wf.connect(infosource, 'model_id', subjinfo, 'model_id')
wf.connect(infosource, 'task_id', subjinfo, 'task_id')
wf.connect(infosource, 'subject_id', datasource, 'subject_id')
wf.connect(infosource, 'model_id', datasource, 'model_id')
wf.connect(infosource, 'task_id', datasource, 'task_id')
wf.connect(subjinfo, 'run_id', datasource, 'run_id')
wf.connect([(datasource, preproc, [('bold', 'inputspec.func')]),
])
def get_highpass(TR, hpcutoff):
return hpcutoff / (2. * TR)
gethighpass = pe.Node(niu.Function(input_names=['TR', 'hpcutoff'],
output_names=['highpass'],
function=get_highpass),
name='gethighpass')
wf.connect(subjinfo, 'TR', gethighpass, 'TR')
wf.connect(gethighpass, 'highpass', preproc, 'inputspec.highpass')
Setup a basic set of contrasts, a t-test per condition
def get_contrasts(contrast_file, task_id, conds):
import numpy as np
import os
contrast_def = []
if os.path.exists(contrast_file):
with open(contrast_file, 'rt') as fp:
contrast_def.extend([np.array(row.split()) for row in fp.readlines() if row.strip()])
contrasts = []
for row in contrast_def:
if row[0] != 'task%03d' % task_id:
continue
con = [row[1], 'T', ['cond%03d' % (i + 1) for i in range(len(conds))],
row[2:].astype(float).tolist()]
contrasts.append(con)
# add auto contrasts for each column
for i, cond in enumerate(conds):
con = [cond, 'T', ['cond%03d' % (i + 1)], [1]]
contrasts.append(con)
return contrasts
contrastgen = pe.Node(niu.Function(input_names=['contrast_file',
'task_id', 'conds'],
output_names=['contrasts'],
function=get_contrasts),
name='contrastgen')
art = pe.MapNode(interface=ra.ArtifactDetect(use_differences=[True, False],
use_norm=True,
norm_threshold=1,
zintensity_threshold=3,
parameter_source='FSL',
mask_type='file'),
iterfield=['realigned_files', 'realignment_parameters',
'mask_file'],
name="art")
modelspec = pe.Node(interface=model.SpecifyModel(),
name="modelspec")
modelspec.inputs.input_units = 'secs'
def check_behav_list(behav, run_id, conds):
import numpy as np
num_conds = len(conds)
if isinstance(behav, string_types):
behav = [behav]
behav_array = np.array(behav).flatten()
num_elements = behav_array.shape[0]
return behav_array.reshape(int(num_elements / num_conds),
num_conds).tolist()
reshape_behav = pe.Node(niu.Function(input_names=['behav', 'run_id', 'conds'],
output_names=['behav'],
function=check_behav_list),
name='reshape_behav')
wf.connect(subjinfo, 'TR', modelspec, 'time_repetition')
wf.connect(datasource, 'behav', reshape_behav, 'behav')
wf.connect(subjinfo, 'run_id', reshape_behav, 'run_id')
wf.connect(subjinfo, 'conds', reshape_behav, 'conds')
wf.connect(reshape_behav, 'behav', modelspec, 'event_files')
wf.connect(subjinfo, 'TR', modelfit, 'inputspec.interscan_interval')
wf.connect(subjinfo, 'conds', contrastgen, 'conds')
if has_contrast:
wf.connect(datasource, 'contrasts', contrastgen, 'contrast_file')
else:
contrastgen.inputs.contrast_file = ''
wf.connect(infosource, 'task_id', contrastgen, 'task_id')
wf.connect(contrastgen, 'contrasts', modelfit, 'inputspec.contrasts')
wf.connect([(preproc, art, [('outputspec.motion_parameters',
'realignment_parameters'),
('outputspec.realigned_files',
'realigned_files'),
('outputspec.mask', 'mask_file')]),
(preproc, modelspec, [('outputspec.highpassed_files',
'functional_runs'),
('outputspec.motion_parameters',
'realignment_parameters')]),
(art, modelspec, [('outlier_files', 'outlier_files')]),
(modelspec, modelfit, [('session_info',
'inputspec.session_info')]),
(preproc, modelfit, [('outputspec.highpassed_files',
'inputspec.functional_data')])
])
# Comute TSNR on realigned data regressing polynomials upto order 2
tsnr = MapNode(TSNR(regress_poly=2), iterfield=['in_file'], name='tsnr')
wf.connect(preproc, "outputspec.realigned_files", tsnr, "in_file")
# Compute the median image across runs
calc_median = Node(Function(input_names=['in_files'],
output_names=['median_file'],
function=median,
imports=imports),
name='median')
wf.connect(tsnr, 'detrended_file', calc_median, 'in_files')
Reorder the copes so that now it combines across runs
def sort_copes(copes, varcopes, contrasts):
import numpy as np
if not isinstance(copes, list):
copes = [copes]
varcopes = [varcopes]
num_copes = len(contrasts)
n_runs = len(copes)
all_copes = np.array(copes).flatten()
all_varcopes = np.array(varcopes).flatten()
outcopes = all_copes.reshape(int(len(all_copes) / num_copes),
num_copes).T.tolist()
outvarcopes = all_varcopes.reshape(int(len(all_varcopes) / num_copes),
num_copes).T.tolist()
return outcopes, outvarcopes, n_runs
cope_sorter = pe.Node(niu.Function(input_names=['copes', 'varcopes',
'contrasts'],
output_names=['copes', 'varcopes',
'n_runs'],
function=sort_copes),
name='cope_sorter')
pickfirst = lambda x: x[0]
wf.connect(contrastgen, 'contrasts', cope_sorter, 'contrasts')
wf.connect([(preproc, fixed_fx, [(('outputspec.mask', pickfirst),
'flameo.mask_file')]),
(modelfit, cope_sorter, [('outputspec.copes', 'copes')]),
(modelfit, cope_sorter, [('outputspec.varcopes', 'varcopes')]),
(cope_sorter, fixed_fx, [('copes', 'inputspec.copes'),
('varcopes', 'inputspec.varcopes'),
('n_runs', 'l2model.num_copes')]),
(modelfit, fixed_fx, [('outputspec.dof_file',
'inputspec.dof_files'),
])
])
wf.connect(calc_median, 'median_file', registration, 'inputspec.mean_image')
if subjects_dir:
wf.connect(infosource, 'subject_id', registration, 'inputspec.subject_id')
registration.inputs.inputspec.subjects_dir = subjects_dir
registration.inputs.inputspec.target_image = fsl.Info.standard_image('MNI152_T1_2mm_brain.nii.gz')
if target:
registration.inputs.inputspec.target_image = target
else:
wf.connect(datasource, 'anat', registration, 'inputspec.anatomical_image')
registration.inputs.inputspec.target_image = fsl.Info.standard_image('MNI152_T1_2mm.nii.gz')
registration.inputs.inputspec.target_image_brain = fsl.Info.standard_image('MNI152_T1_2mm_brain.nii.gz')
registration.inputs.inputspec.config_file = 'T1_2_MNI152_2mm'
def merge_files(copes, varcopes, zstats):
out_files = []
splits = []
out_files.extend(copes)
splits.append(len(copes))
out_files.extend(varcopes)
splits.append(len(varcopes))
out_files.extend(zstats)
splits.append(len(zstats))
return out_files, splits
mergefunc = pe.Node(niu.Function(input_names=['copes', 'varcopes',
'zstats'],
output_names=['out_files', 'splits'],
function=merge_files),
name='merge_files')
wf.connect([(fixed_fx.get_node('outputspec'), mergefunc,
[('copes', 'copes'),
('varcopes', 'varcopes'),
('zstats', 'zstats'),
])])
wf.connect(mergefunc, 'out_files', registration, 'inputspec.source_files')
def split_files(in_files, splits):
copes = in_files[:splits[0]]
varcopes = in_files[splits[0]:(splits[0] + splits[1])]
zstats = in_files[(splits[0] + splits[1]):]
return copes, varcopes, zstats
splitfunc = pe.Node(niu.Function(input_names=['in_files', 'splits'],
output_names=['copes', 'varcopes',
'zstats'],
function=split_files),
name='split_files')
wf.connect(mergefunc, 'splits', splitfunc, 'splits')
wf.connect(registration, 'outputspec.transformed_files',
splitfunc, 'in_files')
if subjects_dir:
get_roi_mean = pe.MapNode(fs.SegStats(default_color_table=True),
iterfield=['in_file'], name='get_aparc_means')
get_roi_mean.inputs.avgwf_txt_file = True
wf.connect(fixed_fx.get_node('outputspec'), 'copes', get_roi_mean, 'in_file')
wf.connect(registration, 'outputspec.aparc', get_roi_mean, 'segmentation_file')
get_roi_tsnr = pe.MapNode(fs.SegStats(default_color_table=True),
iterfield=['in_file'], name='get_aparc_tsnr')
get_roi_tsnr.inputs.avgwf_txt_file = True
wf.connect(tsnr, 'tsnr_file', get_roi_tsnr, 'in_file')
wf.connect(registration, 'outputspec.aparc', get_roi_tsnr, 'segmentation_file')
Connect to a datasink
def get_subs(subject_id, conds, run_id, model_id, task_id):
subs = [('_subject_id_%s_' % subject_id, '')]
subs.append(('_model_id_%d' % model_id, 'model%03d' % model_id))
subs.append(('task_id_%d/' % task_id, '/task%03d_' % task_id))
subs.append(('bold_dtype_mcf_mask_smooth_mask_gms_tempfilt_mean_warp',
'mean'))
subs.append(('bold_dtype_mcf_mask_smooth_mask_gms_tempfilt_mean_flirt',
'affine'))
for i in range(len(conds)):
subs.append(('_flameo%d/cope1.' % i, 'cope%02d.' % (i + 1)))
subs.append(('_flameo%d/varcope1.' % i, 'varcope%02d.' % (i + 1)))
subs.append(('_flameo%d/zstat1.' % i, 'zstat%02d.' % (i + 1)))
subs.append(('_flameo%d/tstat1.' % i, 'tstat%02d.' % (i + 1)))
subs.append(('_flameo%d/res4d.' % i, 'res4d%02d.' % (i + 1)))
subs.append(('_warpall%d/cope1_warp.' % i,
'cope%02d.' % (i + 1)))
subs.append(('_warpall%d/varcope1_warp.' % (len(conds) + i),
'varcope%02d.' % (i + 1)))
subs.append(('_warpall%d/zstat1_warp.' % (2 * len(conds) + i),
'zstat%02d.' % (i + 1)))
subs.append(('_warpall%d/cope1_trans.' % i,
'cope%02d.' % (i + 1)))
subs.append(('_warpall%d/varcope1_trans.' % (len(conds) + i),
'varcope%02d.' % (i + 1)))
subs.append(('_warpall%d/zstat1_trans.' % (2 * len(conds) + i),
'zstat%02d.' % (i + 1)))
subs.append(('__get_aparc_means%d/' % i, '/cope%02d_' % (i + 1)))
for i, run_num in enumerate(run_id):
subs.append(('__get_aparc_tsnr%d/' % i, '/run%02d_' % run_num))
subs.append(('__art%d/' % i, '/run%02d_' % run_num))
subs.append(('__dilatemask%d/' % i, '/run%02d_' % run_num))
subs.append(('__realign%d/' % i, '/run%02d_' % run_num))
subs.append(('__modelgen%d/' % i, '/run%02d_' % run_num))
subs.append(('/model%03d/task%03d/' % (model_id, task_id), '/'))
subs.append(('/model%03d/task%03d_' % (model_id, task_id), '/'))
subs.append(('_bold_dtype_mcf_bet_thresh_dil', '_mask'))
subs.append(('_output_warped_image', '_anat2target'))
subs.append(('median_flirt_brain_mask', 'median_brain_mask'))
subs.append(('median_bbreg_brain_mask', 'median_brain_mask'))
return subs
subsgen = pe.Node(niu.Function(input_names=['subject_id', 'conds', 'run_id',
'model_id', 'task_id'],
output_names=['substitutions'],
function=get_subs),
name='subsgen')
wf.connect(subjinfo, 'run_id', subsgen, 'run_id')
datasink = pe.Node(interface=nio.DataSink(),
name="datasink")
wf.connect(infosource, 'subject_id', datasink, 'container')
wf.connect(infosource, 'subject_id', subsgen, 'subject_id')
wf.connect(infosource, 'model_id', subsgen, 'model_id')
wf.connect(infosource, 'task_id', subsgen, 'task_id')
wf.connect(contrastgen, 'contrasts', subsgen, 'conds')
wf.connect(subsgen, 'substitutions', datasink, 'substitutions')
wf.connect([(fixed_fx.get_node('outputspec'), datasink,
[('res4d', 'res4d'),
('copes', 'copes'),
('varcopes', 'varcopes'),
('zstats', 'zstats'),
('tstats', 'tstats')])
])
wf.connect([(modelfit.get_node('modelgen'), datasink,
[('design_cov', 'qa.model'),
('design_image', 'qa.model.@matrix_image'),
('design_file', 'qa.model.@matrix'),
])])
wf.connect([(preproc, datasink, [('outputspec.motion_parameters',
'qa.motion'),
('outputspec.motion_plots',
'qa.motion.plots'),
('outputspec.mask', 'qa.mask')])])
wf.connect(registration, 'outputspec.mean2anat_mask', datasink, 'qa.mask.mean2anat')
wf.connect(art, 'norm_files', datasink, 'qa.art.@norm')
wf.connect(art, 'intensity_files', datasink, 'qa.art.@intensity')
wf.connect(art, 'outlier_files', datasink, 'qa.art.@outlier_files')
wf.connect(registration, 'outputspec.anat2target', datasink, 'qa.anat2target')
wf.connect(tsnr, 'tsnr_file', datasink, 'qa.tsnr.@map')
if subjects_dir:
wf.connect(registration, 'outputspec.min_cost_file', datasink, 'qa.mincost')
wf.connect([(get_roi_tsnr, datasink, [('avgwf_txt_file', 'qa.tsnr'),
('summary_file', 'qa.tsnr.@summary')])])
wf.connect([(get_roi_mean, datasink, [('avgwf_txt_file', 'copes.roi'),
('summary_file', 'copes.roi.@summary')])])
wf.connect([(splitfunc, datasink,
[('copes', 'copes.mni'),
('varcopes', 'varcopes.mni'),
('zstats', 'zstats.mni'),
])])
wf.connect(calc_median, 'median_file', datasink, 'mean')
wf.connect(registration, 'outputspec.transformed_mean', datasink, 'mean.mni')
wf.connect(registration, 'outputspec.func2anat_transform', datasink, 'xfm.mean2anat')
wf.connect(registration, 'outputspec.anat2target_transform', datasink, 'xfm.anat2target')
Set processing parameters
preproc.inputs.inputspec.fwhm = fwhm
gethighpass.inputs.hpcutoff = hpcutoff
modelspec.inputs.high_pass_filter_cutoff = hpcutoff
modelfit.inputs.inputspec.bases = {'dgamma': {'derivs': use_derivatives}}
modelfit.inputs.inputspec.model_serial_correlations = True
modelfit.inputs.inputspec.film_threshold = 1000
datasink.inputs.base_directory = output_dir
return wf
The following functions run the whole workflow.
if __name__ == '__main__':
import argparse
defstr = ' (default %(default)s)'
parser = argparse.ArgumentParser(prog='fmri_openfmri.py',
description=__doc__)
parser.add_argument('-d', '--datasetdir', required=True)
parser.add_argument('-s', '--subject', default=[],
nargs='+', type=str,
help="Subject name (e.g. 'sub001')")
parser.add_argument('-m', '--model', default=1,
help="Model index" + defstr)
parser.add_argument('-x', '--subjectprefix', default='sub*',
help="Subject prefix" + defstr)
parser.add_argument('-t', '--task', default=1, # nargs='+',
type=int, help="Task index" + defstr)
parser.add_argument('--hpfilter', default=120.,
type=float, help="High pass filter cutoff (in secs)" + defstr)
parser.add_argument('--fwhm', default=6.,
type=float, help="Spatial FWHM" + defstr)
parser.add_argument('--derivatives', action="store_true",
help="Use derivatives" + defstr)
parser.add_argument("-o", "--output_dir", dest="outdir",
help="Output directory base")
parser.add_argument("-w", "--work_dir", dest="work_dir",
help="Output directory base")
parser.add_argument("-p", "--plugin", dest="plugin",
default='Linear',
help="Plugin to use")
parser.add_argument("--plugin_args", dest="plugin_args",
help="Plugin arguments")
parser.add_argument("--sd", dest="subjects_dir",
help="FreeSurfer subjects directory (if available)")
parser.add_argument("--target", dest="target_file",
help=("Target in MNI space. Best to use the MindBoggle "
"template - only used with FreeSurfer"
"OASIS-30_Atropos_template_in_MNI152_2mm.nii.gz"))
args = parser.parse_args()
outdir = args.outdir
work_dir = os.getcwd()
if args.work_dir:
work_dir = os.path.abspath(args.work_dir)
if outdir:
outdir = os.path.abspath(outdir)
else:
outdir = os.path.join(work_dir, 'output')
outdir = os.path.join(outdir, 'model%02d' % int(args.model),
'task%03d' % int(args.task))
derivatives = args.derivatives
if derivatives is None:
derivatives = False
wf = analyze_openfmri_dataset(data_dir=os.path.abspath(args.datasetdir),
subject=args.subject,
model_id=int(args.model),
task_id=[int(args.task)],
subj_prefix=args.subjectprefix,
output_dir=outdir,
hpcutoff=args.hpfilter,
use_derivatives=derivatives,
fwhm=args.fwhm,
subjects_dir=args.subjects_dir,
target=args.target_file)
# wf.config['execution']['remove_unnecessary_outputs'] = False
wf.base_dir = work_dir
if args.plugin_args:
wf.run(args.plugin, plugin_args=eval(args.plugin_args))
else:
wf.run(args.plugin)
Example source code
You can download the full source code of this example
.
This same script is also included in the Nipype source distribution under the
examples
directory.