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-08-05 09:50:52
""" 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 import fsl
from nipype.interfaces.afni import preprocess as afp

from mriqc.workflows.utils import fmri_getidx, fwhm_dict, fd_jenkinson
from mriqc.interfaces.qc import FunctionalQC
from mriqc.interfaces.functional import ComputeDVARS
from mriqc.interfaces.viz import PlotMosaic, PlotFD
from mriqc.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(op.join(settings['output_dir'], 'derivatives')) if not op.exists(deriv_dir): os.makedirs(deriv_dir) # Read FD radius, or default it fd_radius = settings.get('fd_radius', 80.) # Define workflow, inputs and outputs inputnode = pe.Node(niu.IdentityInterface( fields=['bids_dir', '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', 'out_movpar', 'out_dvars']), name='outputnode') # 0. Get data datasource = pe.Node(niu.Function( input_names=['bids_dir', 'data_type', 'subject_id', 'session_id', 'run_id'], output_names=['out_file'], function=bids_getfile), name='datasource') datasource.inputs.data_type = 'func' # Workflow -------------------------------------------------------- # 1. HMC: head motion correct hmcwf = hmc_mcflirt() if settings.get('hmc_afni', False): hmcwf = hmc_afni(st_correct=settings.get('correct_slice_timing', False)) hmcwf.inputs.inputnode.fd_radius = fd_radius 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)) # Compute TSNR using nipype implementation tsnr = pe.Node(nam.TSNR(), name='compute_tsnr') # Compute DVARS dvnode = pe.Node(ComputeDVARS(), name='ComputeDVARS') # 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') plot_fd.inputs.fd_radius = fd_radius merg = pe.Node(niu.Merge(3), name='plot_metadata') workflow.connect([ (inputnode, datasource, [('bids_dir', 'bids_dir'), ('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')]), (mean, plot_mean, [('out_file', 'in_file')]), (tsnr, plot_tsnr, [('tsnr_file', 'in_file')]), (hmcwf, plot_fd, [('outputnode.out_movpar', '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')]), (hmcwf, dvnode, [('outputnode.out_file', 'in_file')]), (bmw, dvnode, [('outputnode.out_file', 'in_mask')]), (mean, measures, [('out_file', 'in_epi')]), (hmcwf, measures, [('outputnode.out_file', 'in_hmc'), ('outputnode.out_movpar', 'fd_movpar')]), (bmw, measures, [('outputnode.out_file', 'in_mask')]), (tsnr, measures, [('tsnr_file', 'in_tsnr')]), (dvnode, measures, [('out_file', 'in_dvars')]), (dvnode, outputnode, [('out_file', 'out_dvars')]), (hmcwf, outputnode, [('outputnode.out_movpar', 'out_movpar')]), ]) 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')]), (measures, datasink, [('summary', 'summary'), ('spacing', 'spacing'), ('size', 'size'), ('fber', 'fber'), ('efc', 'efc'), ('snr', 'snr'), ('gsr', 'gsr'), ('m_tsnr', 'm_tsnr'), ('fd_stats', 'fd_stats'), ('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 hmc_mcflirt(name='fMRI_HMC_mcflirt'): """ An :abbr:`HMC (head motion correction)` for functional scans using FSL MCFLIRT """ workflow = pe.Workflow(name=name) inputnode = pe.Node(niu.IdentityInterface( fields=['in_file', 'fd_radius', 'start_idx', 'stop_idx']), name='inputnode') outputnode = pe.Node(niu.IdentityInterface( fields=['out_file', 'out_movpar']), name='outputnode') mcflirt = pe.Node(fsl.MCFLIRT(mean_vol=True, save_plots=True, save_rms=True, save_mats=True), name="MCFLIRT") workflow.connect([ (inputnode, mcflirt, [('in_file', 'in_file')]), (mcflirt, outputnode, [('out_file', 'out_file'), ('par_file', 'out_movpar')]) ]) return workflow
[docs]def hmc_afni(name='fMRI_HMC_afni', 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', 'fd_radius', 'start_idx', 'stop_idx']), name='inputnode') outputnode = pe.Node(niu.IdentityInterface( fields=['out_file', 'out_movpar']), 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' movpar = pe.Node(niu.Function( function=fd_jenkinson, input_names=['in_file', 'rmax'], output_names=['out_file']), name='Mat2Movpar') workflow.connect([ (inputnode, drop_trs, [('in_file', 'in_file_a'), ('start_idx', 'start_idx'), ('stop_idx', 'stop_idx')]), (inputnode, movpar, [('fd_radius', 'rmax')]), (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')]), (hmc_A, movpar, [('oned_matrix_save', 'in_file')]), (movpar, outputnode, [('out_file', 'out_movpar')]) ]) 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 _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()