Source code for mriqc.workflows.functional

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vi: set ft=python sts=4 ts=4 sw=4 et:
#
# @Author: oesteban
# @Date:   2016-01-05 16:15:08
# @Email:  code@oscaresteban.es
# @Last modified by:   oesteban
# @Last Modified time: 2016-05-05 15:09:56
""" A QC workflow for fMRI data """
import os
import os.path as op

from nipype.pipeline import engine as pe
from nipype.algorithms import misc as nam
from nipype.interfaces import io as nio
from nipype.interfaces import utility as niu
from nipype.interfaces.afni import preprocess as afp
from .utils import fmri_getidx, fwhm_dict
from ..interfaces.qc import FunctionalQC, FramewiseDisplacement
from ..interfaces.viz import PlotMosaic, PlotFD
from ..utils.misc import bids_getfile, bids_path


[docs]def fmri_qc_workflow(name='fMRIQC', settings=None): """ The fMRI qc workflow """ if settings is None: settings = {} workflow = pe.Workflow(name=name) deriv_dir = op.abspath('./derivatives') if 'work_dir' in settings.keys(): deriv_dir = op.abspath(op.join(settings['work_dir'], 'derivatives')) if not op.exists(deriv_dir): os.makedirs(deriv_dir) # Define workflow, inputs and outputs inputnode = pe.Node(niu.IdentityInterface( fields=['bids_root', 'subject_id', 'session_id', 'run_id', 'site_name', 'start_idx', 'stop_idx']), name='inputnode') get_idx = pe.Node(niu.Function( input_names=['in_file', 'start_idx', 'stop_idx'], function=fmri_getidx, output_names=['start_idx', 'stop_idx']), name='get_idx') outputnode = pe.Node(niu.IdentityInterface( fields=['qc', 'mosaic', 'out_group']), name='outputnode') # 0. Get data datasource = pe.Node(niu.Function( input_names=['bids_root', 'data_type', 'subject_id', 'session_id', 'run_id'], output_names=['out_file'], function=bids_getfile), name='datasource') datasource.inputs.data_type = 'func' # Workflow -------------------------------------------------------- hmcwf = fmri_hmc_workflow( # 1. HMC: head motion correct st_correct=settings.get('correct_slice_timing', False)) mean = pe.Node(afp.TStat( # 2. Compute mean fmri options='-mean', outputtype='NIFTI_GZ'), name='mean') bmw = fmri_bmsk_workflow( # 3. Compute brain mask use_bet=settings.get('use_bet', False)) fdisp = pe.Node(FramewiseDisplacement(), name='generate_FD_file') tsnr = pe.Node(nam.TSNR(), name='compute_tsnr') # AFNI quality measures fwhm = pe.Node(afp.FWHMx(combine=True, detrend=True), name='smoothness') # fwhm.inputs.acf = True # add when AFNI >= 16 outliers = pe.Node(afp.OutlierCount(fraction=True, out_file='ouliers.out'), name='outliers') quality = pe.Node(afp.QualityIndex(automask=True), out_file='quality.out', name='quality') measures = pe.Node(FunctionalQC(), name='measures') # Plots plot_mean = pe.Node(PlotMosaic(title='Mean fMRI'), name='plot_mean') plot_tsnr = pe.Node(PlotMosaic(title='tSNR volume'), name='plot_tSNR') plot_fd = pe.Node(PlotFD(), name='plot_fd') merg = pe.Node(niu.Merge(3), name='plot_metadata') workflow.connect([ (inputnode, datasource, [('bids_root', 'bids_root'), ('subject_id', 'subject_id'), ('session_id', 'session_id'), ('run_id', 'run_id')]), (inputnode, get_idx, [('start_idx', 'start_idx'), ('stop_idx', 'stop_idx')]), (datasource, get_idx, [('out_file', 'in_file')]), (inputnode, merg, [('session_id', 'in1'), ('run_id', 'in2'), ('site_name', 'in3')]), (datasource, hmcwf, [('out_file', 'inputnode.in_file')]), (get_idx, hmcwf, [('start_idx', 'inputnode.start_idx'), ('stop_idx', 'inputnode.stop_idx')]), (hmcwf, bmw, [('outputnode.out_file', 'inputnode.in_file')]), (hmcwf, mean, [('outputnode.out_file', 'in_file')]), (hmcwf, tsnr, [('outputnode.out_file', 'in_file')]), (hmcwf, fdisp, [('outputnode.out_xfms', 'in_file')]), (mean, plot_mean, [('out_file', 'in_file')]), (tsnr, plot_tsnr, [('tsnr_file', 'in_file')]), (fdisp, plot_fd, [('out_file', 'in_file')]), (inputnode, plot_mean, [('subject_id', 'subject')]), (inputnode, plot_tsnr, [('subject_id', 'subject')]), (inputnode, plot_fd, [('subject_id', 'subject')]), (merg, plot_mean, [('out', 'metadata')]), (merg, plot_tsnr, [('out', 'metadata')]), (merg, plot_fd, [('out', 'metadata')]), (mean, fwhm, [('out_file', 'in_file')]), (bmw, fwhm, [('outputnode.out_file', 'mask')]), (hmcwf, outliers, [('outputnode.out_file', 'in_file')]), (bmw, outliers, [('outputnode.out_file', 'mask')]), (hmcwf, quality, [('outputnode.out_file', 'in_file')]), (mean, measures, [('out_file', 'in_epi')]), (hmcwf, measures, [('outputnode.out_file', 'in_hmc')]), (bmw, measures, [('outputnode.out_file', 'in_mask')]), (tsnr, measures, [('tsnr_file', 'in_tsnr')]) ]) if settings.get('mosaic_mask', False): workflow.connect(bmw, 'outputnode.out_file', plot_mean, 'in_mask') workflow.connect(bmw, 'outputnode.out_file', plot_tsnr, 'in_mask') # Save mean mosaic to well-formed path mvmean = pe.Node(niu.Rename( format_string='meanepi_%(subject_id)s_%(session_id)s_%(run_id)s', keep_ext=True), name='rename_mean_mosaic') dsmean = pe.Node(nio.DataSink(base_directory=settings['work_dir'], parameterization=False), name='ds_mean') workflow.connect([ (inputnode, mvmean, [('subject_id', 'subject_id'), ('session_id', 'session_id'), ('run_id', 'run_id')]), (plot_mean, mvmean, [('out_file', 'in_file')]), (mvmean, dsmean, [('out_file', '@mosaic')]) ]) # Save tSNR mosaic to well-formed path mvtsnr = pe.Node(niu.Rename( format_string='tsnr_%(subject_id)s_%(session_id)s_%(run_id)s', keep_ext=True), name='rename_tsnr_mosaic') dstsnr = pe.Node(nio.DataSink(base_directory=settings['work_dir'], parameterization=False), name='ds_tsnr') workflow.connect([ (inputnode, mvtsnr, [('subject_id', 'subject_id'), ('session_id', 'session_id'), ('run_id', 'run_id')]), (plot_tsnr, mvtsnr, [('out_file', 'in_file')]), (mvtsnr, dstsnr, [('out_file', '@mosaic')]) ]) # Save FD plot to well-formed path mvfd = pe.Node(niu.Rename( format_string='fd_%(subject_id)s_%(session_id)s_%(run_id)s', keep_ext=True), name='rename_fd_mosaic') dsfd = pe.Node(nio.DataSink(base_directory=settings['work_dir'], parameterization=False), name='ds_fd') workflow.connect([ (inputnode, mvfd, [('subject_id', 'subject_id'), ('session_id', 'session_id'), ('run_id', 'run_id')]), (plot_fd, mvfd, [('out_file', 'in_file')]), (mvfd, dsfd, [('out_file', '@mosaic')]) ]) # Format name out_name = pe.Node(niu.Function( input_names=['subid', 'sesid', 'runid', 'prefix', 'out_path'], output_names=['out_file'], function=bids_path), name='FormatName') out_name.inputs.out_path = deriv_dir out_name.inputs.prefix = 'func' # Save to JSON file datasink = pe.Node(nio.JSONFileSink(), name='datasink') datasink.inputs.qc_type = 'func' workflow.connect([ (inputnode, out_name, [('subject_id', 'subid'), ('session_id', 'sesid'), ('run_id', 'runid')]), (inputnode, datasink, [('subject_id', 'subject_id'), ('session_id', 'session_id'), ('run_id', 'run_id')]), (plot_mean, datasink, [('out_file', 'mean_plot')]), (plot_tsnr, datasink, [('out_file', 'tsnr_plot')]), (plot_fd, datasink, [('out_file', 'fd_plot')]), (fwhm, datasink, [(('fwhm', fwhm_dict), 'fwhm')]), (outliers, datasink, [(('out_file', _parse_tout), 'outlier')]), (quality, datasink, [(('out_file', _parse_tqual), 'quality')]), (fdisp, datasink, [('fd_stats', 'fd_stats')]), (measures, datasink, [('summary', 'summary'), ('spacing', 'spacing'), ('size', 'size'), ('fber', 'fber'), ('efc', 'efc'), ('snr', 'snr'), ('gsr', 'gsr'), ('m_tsnr', 'm_tsnr'), ('dvars', 'dvars'), ('gcor', 'gcor')]), (out_name, datasink, [('out_file', 'out_file')]), (datasink, outputnode, [('out_file', 'out_file')]) ]) return workflow
[docs]def fmri_bmsk_workflow(name='fMRIBrainMask', use_bet=False): """Comute brain mask of an fmri dataset""" workflow = pe.Workflow(name=name) inputnode = pe.Node(niu.IdentityInterface(fields=['in_file']), name='inputnode') outputnode = pe.Node(niu.IdentityInterface(fields=['out_file']), name='outputnode') if not use_bet: afni_msk = pe.Node(afp.Automask( outputtype='NIFTI_GZ'), name='afni_msk') # Connect brain mask extraction workflow.connect([ (inputnode, afni_msk, [('in_file', 'in_file')]), (afni_msk, outputnode, [('out_file', 'out_file')]) ]) else: from nipype.interfaces.fsl import BET, ErodeImage bet_msk = pe.Node(BET(mask=True, functional=True), name='bet_msk') erode = pe.Node(ErodeImage(kernel_shape='box', kernel_size=1.0), name='erode') # Connect brain mask extraction workflow.connect([ (inputnode, bet_msk, [('in_file', 'in_file')]), (bet_msk, erode, [('mask_file', 'in_file')]), (erode, outputnode, [('out_file', 'out_file')]) ]) return workflow
[docs]def fmri_hmc_workflow(name='fMRI_HMC', st_correct=False): """A head motion correction (HMC) workflow for functional scans""" workflow = pe.Workflow(name=name) inputnode = pe.Node(niu.IdentityInterface( fields=['in_file', 'start_idx', 'stop_idx']), name='inputnode') outputnode = pe.Node(niu.IdentityInterface( fields=['out_file', 'out_xfms']), name='outputnode') drop_trs = pe.Node(afp.Calc(expr='a', outputtype='NIFTI_GZ'), name='drop_trs') deoblique = pe.Node(afp.Refit(deoblique=True), name='deoblique') reorient = pe.Node(afp.Resample( orientation='RPI', outputtype='NIFTI_GZ'), name='reorient') get_mean_RPI = pe.Node(afp.TStat( options='-mean', outputtype='NIFTI_GZ'), name='get_mean_RPI') # calculate hmc parameters hmc = pe.Node( afp.Volreg(args='-Fourier -twopass', zpad=4, outputtype='NIFTI_GZ'), name='motion_correct') get_mean_motion = get_mean_RPI.clone('get_mean_motion') hmc_A = hmc.clone('motion_correct_A') hmc_A.inputs.md1d_file = 'max_displacement.1D' workflow.connect([ (inputnode, drop_trs, [('in_file', 'in_file_a'), ('start_idx', 'start_idx'), ('stop_idx', 'stop_idx')]), (deoblique, reorient, [('out_file', 'in_file')]), (reorient, get_mean_RPI, [('out_file', 'in_file')]), (reorient, hmc, [('out_file', 'in_file')]), (get_mean_RPI, hmc, [('out_file', 'basefile')]), (hmc, get_mean_motion, [('out_file', 'in_file')]), (reorient, hmc_A, [('out_file', 'in_file')]), (get_mean_motion, hmc_A, [('out_file', 'basefile')]), (hmc_A, outputnode, [('out_file', 'out_file'), ('oned_matrix_save', 'out_xfms')]) ]) if st_correct: st_corr = pe.Node(afp.TShift(outputtype='NIFTI_GZ'), name='TimeShifts') workflow.connect([ (drop_trs, st_corr, [('out_file', 'in_file')]), (st_corr, deoblique, [('out_file', 'in_file')]) ]) else: workflow.connect([ (drop_trs, deoblique, [('out_file', 'in_file')]) ]) return workflow
def _merge_dicts(in_qc, subject_id, metadata, fwhm, fd_stats, outlier, quality): in_qc['subject'] = subject_id in_qc['session'] = metadata[0] in_qc['scan'] = metadata[1] try: in_qc['site_name'] = metadata[2] except IndexError: pass # No site_name defined in_qc.update({'fwhm_x': fwhm[0], 'fwhm_y': fwhm[1], 'fwhm_z': fwhm[2], 'fwhm': fwhm[3]}) in_qc.update(fd_stats) in_qc['outlier'] = outlier in_qc['quality'] = quality try: in_qc['tr'] = in_qc['spacing_tr'] except KeyError: pass # TR is not defined return in_qc def _mean(inlist): import numpy as np return np.mean(inlist) def _parse_tqual(in_file): import numpy as np with open(in_file, 'r') as fin: lines = fin.readlines() # remove general information lines = [l for l in lines if l[:2] != "++"] # remove general information and warnings return np.mean([float(l.strip()) for l in lines]) raise RuntimeError('AFNI 3dTqual was not parsed correctly') def _parse_tout(in_file): import numpy as np data = np.loadtxt(in_file) # pylint: disable=no-member return data.mean()