From 9cd31152b57f38cd2ab9c9770da8fad1ae482bd2 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Fri, 29 Aug 2014 13:05:15 +0200 Subject: [PATCH 01/33] WIP: open new workflows group in dmri (preprocess) --- nipype/workflows/dmri/__init__.py | 2 +- nipype/workflows/dmri/preprocess/__init__.py | 1 + nipype/workflows/dmri/preprocess/epi.py | 829 ++++++++++++++++++ .../dmri/preprocess/tests/__init__.py | 2 + .../dmri/preprocess/tests/test_epi.py | 44 + 5 files changed, 877 insertions(+), 1 deletion(-) create mode 100644 nipype/workflows/dmri/preprocess/__init__.py create mode 100644 nipype/workflows/dmri/preprocess/epi.py create mode 100644 nipype/workflows/dmri/preprocess/tests/__init__.py create mode 100644 nipype/workflows/dmri/preprocess/tests/test_epi.py diff --git a/nipype/workflows/dmri/__init__.py b/nipype/workflows/dmri/__init__.py index 3d5a0590cb..01b3d9746f 100644 --- a/nipype/workflows/dmri/__init__.py +++ b/nipype/workflows/dmri/__init__.py @@ -1 +1 @@ -import camino, mrtrix, fsl +import camino, mrtrix, fsl, preprocess diff --git a/nipype/workflows/dmri/preprocess/__init__.py b/nipype/workflows/dmri/preprocess/__init__.py new file mode 100644 index 0000000000..2a2442ec23 --- /dev/null +++ b/nipype/workflows/dmri/preprocess/__init__.py @@ -0,0 +1 @@ +from epi import (motion_correct) diff --git a/nipype/workflows/dmri/preprocess/epi.py b/nipype/workflows/dmri/preprocess/epi.py new file mode 100644 index 0000000000..9f6f01f7b1 --- /dev/null +++ b/nipype/workflows/dmri/preprocess/epi.py @@ -0,0 +1,829 @@ +# coding: utf-8 + +import nipype.pipeline.engine as pe +import nipype.interfaces.utility as niu +import nipype.interfaces.fsl as fsl +import os + +def motion_correct(name='motion_correct'): + """Creates a pipeline that corrects for head motion artifacts in dMRI sequences. + It takes a series of diffusion weighted images and rigidly co-registers + them to one reference image. Finally, the `b`-matrix is rotated accordingly [1]_ + making use of the rotation matrix obtained by FLIRT. + + Search angles have been limited to 3.5 degrees, based on results in [2]_. + + A list of rigid transformation matrices is provided, so that transforms can be + chained. This is useful to correct for artifacts with only one interpolation process (as + previously discussed `here `_), + and also to compute nuisance regressors as proposed by [2]_. + + .. warning:: This workflow rotates the `b`-vectors, so please be advised + that not all the dicom converters ensure the consistency between the resulting + nifti orientation and the gradients table (e.g. dcm2nii checks it). + + .. admonition:: References + + .. [1] Leemans A, and Jones DK, Magn Reson Med. 2009 Jun;61(6):1336-49. + doi: 10.1002/mrm.21890. + + .. [2] Yendiki A et al., Spurious group differences due to head motion in a + diffusion MRI study. Neuroimage. 2013 Nov 21;88C:79-90. + doi: 10.1016/j.neuroimage.2013.11.027 + + Example + ------- + + >>> from nipype.workflows.dmri.fsl.epi import motion_correct + >>> mc = motion_correct() + >>> mc.inputs.inputnode.in_file = 'diffusion.nii' + >>> mc.inputs.inputnode.in_bvec = 'diffusion.bvec' + >>> mc.inputs.inputnode.ref_num = 0 + >>> mc.run() # doctest: +SKIP + + Inputs:: + + inputnode.in_file - input dwi file + inputnode.ref_num - index of the `b0` volume that should be taken as reference + inputnode.in_bvec - gradients file (`b` vectors) + + Outputs:: + + outputnode.out_file - corrected dwi file + outputnode.out_bvec - rotated gradient vectors table + outputnode.out_xfms - list of transformation matrices + + """ + inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'ref_num', 'in_bvec', + 'in_mask']), name='inputnode') + split = pe.Node(fsl.Split(dimension='t'), name='SplitDWIs') + pick_ref = pe.Node(niu.Select(), name='Pick_b0') + flirt = pe.MapNode(fsl.FLIRT(no_search=True, interp='spline', cost='normmi', + cost_func = 'normmi', dof=6, bins=64, save_log=True, + searchr_x=[-4,4], searchr_y=[-4,4], searchr_z=[-4,4], + fine_search=1, coarse_search=10, padding_size=1), + name='CoRegistration', iterfield=['in_file']) + rot_bvec = pe.Node(niu.Function(input_names=['in_bvec', 'in_matrix'], + output_names=['out_file'], function=rotate_bvecs), + name='Rotate_Bvec') + merge = pe.Node(fsl.Merge(dimension='t'), name='MergeDWIs') + outputnode = pe.Node(niu.IdentityInterface(fields=['out_file', + 'out_bvec', 'out_xfms']), + name='outputnode') + + def _checkset(ref_num): + from nipype.interfaces.base import isdefined + if (ref_num is None) or not isdefined(ref_num): + return 0 + return ref_num + + wf = pe.Workflow(name=name) + wf.connect([ + (inputnode, split, [('in_file', 'in_file')]) + ,(split, pick_ref, [('out_files', 'inlist')]) + ,(inputnode, pick_ref, [(('ref_num', _checkset), 'index')]) + ,(inputnode, flirt, [('in_mask', 'ref_weight')]) + ,(split, flirt, [('out_files', 'in_file')]) + ,(inputnode, rot_bvec, [('in_bvec', 'in_bvec')]) + ,(flirt, rot_bvec, [('out_matrix_file', 'in_matrix')]) + ,(pick_ref, flirt, [('out', 'reference')]) + ,(flirt, merge, [('out_file', 'in_files')]) + ,(merge, outputnode, [('merged_file', 'out_file')]) + ,(rot_bvec, outputnode, [('out_file', 'out_bvec')]) + ,(flirt, outputnode, [('out_matrix_file', 'out_xfms')]) + ]) + return wf + +############################################################################### +# Old workflows, mark deprecated +############################################################################### + +def create_motion_correct_pipeline(name='create_motion_correct_pipeline'): + """ + .. deprecated:: 0.9.2. + Please use :func:`.motion_correct` instead + + """ + return motion_correct(name) + +def create_dmri_preprocessing(name='dMRI_preprocessing', use_fieldmap=True, fieldmap_registration=False): + """Creates a workflow that chains the necessary pipelines to + correct for motion, eddy currents, and, if selected, susceptibility + artifacts in EPI dMRI sequences. + + .. warning:: IMPORTANT NOTICE: this workflow rotates the b-vectors, so please be advised \ + that not all the dicom converters ensure the consistency between the resulting \ + nifti orientation and the b matrix table (e.g. dcm2nii checks it). + + + Example + ------- + + >>> nipype_dmri_preprocess = create_dmri_preprocessing('nipype_dmri_prep') + >>> nipype_dmri_preprocess.inputs.inputnode.in_file = 'diffusion.nii' + >>> nipype_dmri_preprocess.inputs.inputnode.in_bvec = 'diffusion.bvec' + >>> nipype_dmri_preprocess.inputs.inputnode.ref_num = 0 + >>> nipype_dmri_preprocess.inputs.inputnode.fieldmap_mag = 'magnitude.nii' + >>> nipype_dmri_preprocess.inputs.inputnode.fieldmap_pha = 'phase.nii' + >>> nipype_dmri_preprocess.inputs.inputnode.te_diff = 2.46 + >>> nipype_dmri_preprocess.inputs.inputnode.epi_echospacing = 0.77 + >>> nipype_dmri_preprocess.inputs.inputnode.epi_rev_encoding = False + >>> nipype_dmri_preprocess.inputs.inputnode.pi_accel_factor = True + >>> nipype_dmri_preprocess.run() # doctest: +SKIP + + + Inputs:: + + inputnode.in_file - The diffusion data + inputnode.in_bvec - The b-matrix file, in FSL format and consistent with the in_file orientation + inputnode.ref_num - The reference volume (a b=0 volume in dMRI) + inputnode.fieldmap_mag - The magnitude of the fieldmap + inputnode.fieldmap_pha - The phase difference of the fieldmap + inputnode.te_diff - TE increment used (in msec.) on the fieldmap acquisition (generally 2.46ms for 3T scanners) + inputnode.epi_echospacing - The EPI EchoSpacing parameter (in msec.) + inputnode.epi_rev_encoding - True if reverse encoding was used (generally False) + inputnode.pi_accel_factor - Parallel imaging factor (aka GRAPPA acceleration factor) + inputnode.vsm_sigma - Sigma (in mm.) of the gaussian kernel used for in-slice smoothing of the deformation field (voxel shift map, vsm) + + + Outputs:: + + outputnode.dmri_corrected + outputnode.bvec_rotated + + + Optional arguments:: + use_fieldmap - True if there are fieldmap files that should be used (default True) + fieldmap_registration - True if registration to fieldmap should be performed (default False) + + + """ + + pipeline = pe.Workflow(name=name) + + inputnode = pe.Node(niu.IdentityInterface( + fields=['in_file', 'in_bvec', 'ref_num', 'fieldmap_mag', + 'fieldmap_pha', 'te_diff', 'epi_echospacing', + 'epi_rev_encoding', 'pi_accel_factor', 'vsm_sigma']), + name='inputnode') + + outputnode = pe.Node(niu.IdentityInterface( + fields=['dmri_corrected', 'bvec_rotated']), + name='outputnode') + + motion = create_motion_correct_pipeline() + eddy = create_eddy_correct_pipeline() + + if use_fieldmap: # we have a fieldmap, so lets use it (yay!) + susceptibility = create_epidewarp_pipeline( + fieldmap_registration=fieldmap_registration) + + pipeline.connect([ + (inputnode, motion, [('in_file', 'inputnode.in_file'), + ('in_bvec', 'inputnode.in_bvec'), + ('ref_num', 'inputnode.ref_num')]), + (inputnode, eddy, [('ref_num', 'inputnode.ref_num')]), + (motion, eddy, [('outputnode.motion_corrected', 'inputnode.in_file')]), + (eddy, susceptibility, [('outputnode.eddy_corrected', 'inputnode.in_file')]), + (inputnode, susceptibility, [('ref_num', 'inputnode.ref_num'), + ('fieldmap_mag', 'inputnode.fieldmap_mag'), + ('fieldmap_pha', 'inputnode.fieldmap_pha'), + ('te_diff', 'inputnode.te_diff'), + ('epi_echospacing', 'inputnode.epi_echospacing'), + ('epi_rev_encoding', 'inputnode.epi_rev_encoding'), + ('pi_accel_factor', 'inputnode.pi_accel_factor'), + ('vsm_sigma', 'inputnode.vsm_sigma')]), + (motion, outputnode, [('outputnode.out_bvec', 'bvec_rotated')]), + (susceptibility, outputnode, [('outputnode.epi_corrected', 'dmri_corrected')]) + ]) + else: # we don't have a fieldmap, so we just carry on without it :( + pipeline.connect([ + (inputnode, motion, [('in_file', 'inputnode.in_file'), + ('in_bvec', 'inputnode.in_bvec'), + ('ref_num', 'inputnode.ref_num')]), + (inputnode, eddy, [('ref_num', 'inputnode.ref_num')]), + (motion, eddy, [('outputnode.motion_corrected', 'inputnode.in_file')]), + (motion, outputnode, [('outputnode.out_bvec', 'bvec_rotated')]), + (eddy, outputnode, [('outputnode.eddy_corrected', 'dmri_corrected')]) + ]) + + return pipeline + + +def create_eddy_correct_pipeline(name='eddy_correct'): + """Creates a pipeline that replaces eddy_correct script in FSL. It takes a + series of diffusion weighted images and linearly co-registers them to one + reference image. No rotation of the B-matrix is performed, so this pipeline + should be executed after the motion correction pipeline. + + Example + ------- + + >>> nipype_eddycorrect = create_eddy_correct_pipeline('nipype_eddycorrect') + >>> nipype_eddycorrect.inputs.inputnode.in_file = 'diffusion.nii' + >>> nipype_eddycorrect.inputs.inputnode.ref_num = 0 + >>> nipype_eddycorrect.run() # doctest: +SKIP + + Inputs:: + + inputnode.in_file + inputnode.ref_num + + Outputs:: + + outputnode.eddy_corrected + """ + + inputnode = pe.Node( + niu.IdentityInterface(fields=['in_file', 'ref_num']), + name='inputnode') + + pipeline = pe.Workflow(name=name) + + split = pe.Node(fsl.Split(dimension='t'), name='split') + pick_ref = pe.Node(niu.Select(), name='pick_ref') + coregistration = pe.MapNode(fsl.FLIRT(no_search=True, padding_size=1, + dof=12, interp='spline'), name='coregistration', iterfield=['in_file']) + merge = pe.Node(fsl.Merge(dimension='t'), name='merge') + outputnode = pe.Node( + niu.IdentityInterface(fields=['eddy_corrected']), + name='outputnode') + + pipeline.connect([ + (inputnode, split, [('in_file', 'in_file')]) + ,(split, pick_ref, [('out_files', 'inlist')]) + ,(inputnode, pick_ref, [('ref_num', 'index')]) + ,(split, coregistration, [('out_files', 'in_file')]) + ,(pick_ref, coregistration, [('out', 'reference')]) + ,(coregistration, merge, [('out_file', 'in_files')]) + ,(merge, outputnode, [('merged_file', 'eddy_corrected')]) + ]) + return pipeline + + + + +def fieldmap_correction(name='fieldmap_correction', nocheck=False): + """ + Fieldmap-based retrospective correction of EPI images for the susceptibility distortion + artifact (Jezzard et al., 1995). Fieldmap images are assumed to be already registered + to EPI data, and a brain mask is required. + + Replaces the former workflow, still available as create_epidewarp_pipeline(). The difference + with respect the epidewarp pipeline is that now the workflow uses the new fsl_prepare_fieldmap + available as of FSL 5.0. + + + + Example + ------- + + >>> nipype_epicorrect = fieldmap_correction('nipype_epidewarp') + >>> nipype_epicorrect.inputs.inputnode.in_file = 'diffusion.nii' + >>> nipype_epicorrect.inputs.inputnode.in_mask = 'brainmask.nii' + >>> nipype_epicorrect.inputs.inputnode.fieldmap_pha = 'phase.nii' + >>> nipype_epicorrect.inputs.inputnode.fieldmap_mag = 'magnitude.nii' + >>> nipype_epicorrect.inputs.inputnode.te_diff = 2.46 + >>> nipype_epicorrect.inputs.inputnode.epi_echospacing = 0.77 + >>> nipype_epicorrect.inputs.inputnode.encoding_direction = 'y' + >>> nipype_epicorrect.run() # doctest: +SKIP + + Inputs:: + + inputnode.in_file - The volume acquired with EPI sequence + inputnode.in_mask - A brain mask + inputnode.fieldmap_pha - The phase difference map from the fieldmapping, registered to in_file + inputnode.fieldmap_mag - The magnitud maps (usually 4D, one magnitude per GRE scan) + from the fieldmapping, registered to in_file + inputnode.te_diff - Time difference in msec. between TE in ms of the fieldmapping (usually a GRE sequence). + inputnode.epi_echospacing - The effective echo spacing (aka dwell time) in msec. of the EPI sequence. If + EPI was acquired with parallel imaging, then the effective echo spacing is + eff_es = es / acc_factor. + inputnode.encoding_direction - The phase encoding direction in EPI acquisition (default y) + inputnode.vsm_sigma - Sigma value of the gaussian smoothing filter applied to the vsm (voxel shift map) + + + Outputs:: + + outputnode.epi_corrected + outputnode.out_vsm + + """ + + inputnode = pe.Node(niu.IdentityInterface( + fields=['in_file', + 'in_mask', + 'fieldmap_pha', + 'fieldmap_mag', + 'te_diff', + 'epi_echospacing', + 'vsm_sigma', + 'encoding_direction' + ]), name='inputnode' + ) + + pipeline = pe.Workflow(name=name) + + # Keep first frame from magnitude + select_mag = pe.Node(fsl.utils.ExtractROI( + t_size=1, t_min=0), name='select_magnitude') + + # Mask magnitude (it is required by PreparedFieldMap) + mask_mag = pe.Node( fsl.maths.ApplyMask(), name='mask_magnitude' ) + + # Run fsl_prepare_fieldmap + fslprep = pe.Node( fsl.PrepareFieldmap(), name='prepare_fieldmap' ) + + if nocheck: + fslprep.inputs.nocheck = True + + # Use FUGUE to generate the voxel shift map (vsm) + vsm = pe.Node(fsl.FUGUE(save_shift=True), name='generate_vsm') + + # VSM demean is not anymore present in the epi_reg script + #vsm_mean = pe.Node(niu.Function(input_names=['in_file', 'mask_file', 'in_unwarped'], output_names=[ + # 'out_file'], function=_vsm_remove_mean), name='vsm_mean_shift') + + # fugue_epi + dwi_split = pe.Node(niu.Function(input_names=[ + 'in_file'], output_names=['out_files'], function=_split_dwi), name='dwi_split') + + # 'fugue -i %s -u %s --loadshift=%s --mask=%s' % ( vol_name, out_vol_name, vsm_name, mask_name ) + dwi_applyxfm = pe.MapNode(fsl.FUGUE( + icorr=True, save_shift=False), iterfield=['in_file'], name='dwi_fugue') + # Merge back all volumes + dwi_merge = pe.Node(fsl.utils.Merge( + dimension='t'), name='dwi_merge') + + outputnode = pe.Node( + niu.IdentityInterface(fields=['epi_corrected','out_vsm']), + name='outputnode') + + pipeline.connect([ + (inputnode, select_mag, [('fieldmap_mag', 'in_file')]) + ,(inputnode, fslprep, [('fieldmap_pha', 'in_phase'),('te_diff', 'delta_TE') ]) + ,(inputnode, mask_mag, [('in_mask', 'mask_file' )]) + ,(select_mag, mask_mag, [('roi_file', 'in_file')]) + ,(mask_mag, fslprep, [('out_file', 'in_magnitude')]) + ,(fslprep, vsm, [('out_fieldmap', 'phasemap_in_file')]) + ,(inputnode, vsm, [('fieldmap_mag', 'in_file'), + ('encoding_direction','unwarp_direction'), + (('te_diff', _ms2sec), 'asym_se_time'), + ('vsm_sigma', 'smooth2d'), + (('epi_echospacing', _ms2sec), 'dwell_time')]) + ,(mask_mag, vsm, [('out_file', 'mask_file')]) + ,(inputnode, dwi_split, [('in_file', 'in_file')]) + ,(dwi_split, dwi_applyxfm, [('out_files', 'in_file')]) + ,(mask_mag, dwi_applyxfm, [('out_file', 'mask_file')]) + ,(vsm, dwi_applyxfm, [('shift_out_file', 'shift_in_file')]) + ,(inputnode, dwi_applyxfm, [('encoding_direction','unwarp_direction')]) + ,(dwi_applyxfm, dwi_merge, [('unwarped_file', 'in_files')]) + ,(dwi_merge, outputnode, [('merged_file', 'epi_corrected')]) + ,(vsm, outputnode, [('shift_out_file','out_vsm') ]) + ]) + + + return pipeline + + +def topup_correction( name='topup_correction' ): + """ + Corrects for susceptibilty distortion of EPI images when one reverse encoding dataset has + been acquired + + + Example + ------- + + >>> nipype_epicorrect = topup_correction('nipype_topup') + >>> nipype_epicorrect.inputs.inputnode.in_file_dir = 'epi.nii' + >>> nipype_epicorrect.inputs.inputnode.in_file_rev = 'epi_rev.nii' + >>> nipype_epicorrect.inputs.inputnode.encoding_direction = ['y', 'y-'] + >>> nipype_epicorrect.inputs.inputnode.ref_num = 0 + >>> nipype_epicorrect.run() # doctest: +SKIP + + Inputs:: + inputnode.in_file_dir - EPI volume acquired in 'forward' phase encoding + inputnode.in_file_rev - EPI volume acquired in 'reversed' phase encoding + inputnode.encoding_direction - Direction encoding of in_file_dir + inputnode.ref_num - Identifier of the reference volumes (usually B0 volume) + + Outputs:: + + outputnode.epi_corrected + + + """ + pipeline = pe.Workflow(name=name) + + inputnode = pe.Node(niu.IdentityInterface( + fields=['in_file_dir', + 'in_file_rev', + 'encoding_direction', + 'readout_times', + 'ref_num' + ]), name='inputnode' + ) + + outputnode = pe.Node( niu.IdentityInterface( + fields=['out_fieldcoef', + 'out_movpar', + 'out_enc_file', + 'epi_corrected' + ]), name='outputnode' + ) + + b0_dir = pe.Node( fsl.ExtractROI( t_size=1 ), name='b0_1' ) + b0_rev = pe.Node( fsl.ExtractROI( t_size=1 ), name='b0_2' ) + combin = pe.Node( niu.Merge(2), name='merge' ) + combin2 = pe.Node( niu.Merge(2), name='merge2' ) + merged = pe.Node( fsl.Merge( dimension='t' ), name='b0_comb' ) + + topup = pe.Node( fsl.TOPUP(), name='topup' ) + applytopup = pe.Node( fsl.ApplyTOPUP(in_index=[1,2] ), name='applytopup' ) + + pipeline.connect([ + (inputnode, b0_dir, [('in_file_dir','in_file'),('ref_num','t_min')] ) + ,(inputnode, b0_rev, [('in_file_rev','in_file'),('ref_num','t_min')] ) + ,(inputnode, combin2, [('in_file_dir','in1'),('in_file_rev','in2') ] ) + ,(b0_dir, combin, [('roi_file','in1')] ) + ,(b0_rev, combin, [('roi_file','in2')] ) + ,(combin, merged, [('out', 'in_files')] ) + ,(merged, topup, [('merged_file','in_file')]) + ,(inputnode, topup, [('encoding_direction','encoding_direction'),('readout_times','readout_times') ]) + ,(topup, applytopup, [('out_fieldcoef','in_topup_fieldcoef'),('out_movpar','in_topup_movpar'), + ('out_enc_file','encoding_file')]) + ,(combin2, applytopup, [('out','in_files')] ) + ,(topup, outputnode, [('out_fieldcoef','out_fieldcoef'),('out_movpar','out_movpar'), + ('out_enc_file','out_enc_file') ]) + ,(applytopup,outputnode, [('out_corrected','epi_corrected')]) + ]) + + return pipeline + + +def create_epidewarp_pipeline(name='epidewarp', fieldmap_registration=False): + """ Replaces the epidewarp.fsl script (http://www.nmr.mgh.harvard.edu/~greve/fbirn/b0/epidewarp.fsl) + for susceptibility distortion correction of dMRI & fMRI acquired with EPI sequences and the fieldmap + information (Jezzard et al., 1995) using FSL's FUGUE. The registration to the (warped) fieldmap + (strictly following the original script) is available using fieldmap_registration=True. + + Example + ------- + + >>> nipype_epicorrect = create_epidewarp_pipeline('nipype_epidewarp', fieldmap_registration=False) + >>> nipype_epicorrect.inputs.inputnode.in_file = 'diffusion.nii' + >>> nipype_epicorrect.inputs.inputnode.fieldmap_mag = 'magnitude.nii' + >>> nipype_epicorrect.inputs.inputnode.fieldmap_pha = 'phase.nii' + >>> nipype_epicorrect.inputs.inputnode.te_diff = 2.46 + >>> nipype_epicorrect.inputs.inputnode.epi_echospacing = 0.77 + >>> nipype_epicorrect.inputs.inputnode.epi_rev_encoding = False + >>> nipype_epicorrect.inputs.inputnode.ref_num = 0 + >>> nipype_epicorrect.inputs.inputnode.pi_accel_factor = 1.0 + >>> nipype_epicorrect.run() # doctest: +SKIP + + Inputs:: + + inputnode.in_file - The volume acquired with EPI sequence + inputnode.fieldmap_mag - The magnitude of the fieldmap + inputnode.fieldmap_pha - The phase difference of the fieldmap + inputnode.te_diff - Time difference between TE in ms. + inputnode.epi_echospacing - The echo spacing (aka dwell time) in the EPI sequence + inputnode.epi_ph_encoding_dir - The phase encoding direction in EPI acquisition (default y) + inputnode.epi_rev_encoding - True if it is acquired with reverse encoding + inputnode.pi_accel_factor - Acceleration factor used for EPI parallel imaging (GRAPPA) + inputnode.vsm_sigma - Sigma value of the gaussian smoothing filter applied to the vsm (voxel shift map) + inputnode.ref_num - The reference volume (B=0 in dMRI or a central frame in fMRI) + + + Outputs:: + + outputnode.epi_corrected + + + Optional arguments:: + + fieldmap_registration - True if registration to fieldmap should be done (default False) + + """ + + inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', + 'fieldmap_mag', + 'fieldmap_pha', + 'te_diff', + 'epi_echospacing', + 'epi_ph_encoding_dir', + 'epi_rev_encoding', + 'pi_accel_factor', + 'vsm_sigma', + 'ref_num', + 'unwarp_direction' + ]), name='inputnode') + + pipeline = pe.Workflow(name=name) + + # Keep first frame from magnitude + select_mag = pe.Node(fsl.utils.ExtractROI( + t_size=1, t_min=0), name='select_magnitude') + + # mask_brain + mask_mag = pe.Node(fsl.BET(mask=True), name='mask_magnitude') + mask_mag_dil = pe.Node(niu.Function(input_names=[ + 'in_file'], output_names=['out_file'], function=_dilate_mask), name='mask_dilate') + + # Compute dwell time + dwell_time = pe.Node(niu.Function(input_names=['dwell_time', 'pi_factor', 'is_reverse_encoding'], output_names=[ + 'dwell_time'], function=_compute_dwelltime), name='dwell_time') + + # Normalize phase diff to be [-pi, pi) + norm_pha = pe.Node(niu.Function(input_names=['in_file'], output_names=[ + 'out_file'], function=_prepare_phasediff), name='normalize_phasediff') + # Execute FSL PRELUDE: prelude -p %s -a %s -o %s -f -v -m %s + prelude = pe.Node(fsl.PRELUDE( + process3d=True), name='phase_unwrap') + fill_phase = pe.Node(niu.Function(input_names=['in_file'], output_names=[ + 'out_file'], function=_fill_phase), name='fill_phasediff') + + # to assure that vsm is same dimension as mag. The input only affects the output dimension. + # The content of the input has no effect on the vsm. The de-warped mag volume is + # meaningless and will be thrown away + # fugue -i %s -u %s -p %s --dwell=%s --asym=%s --mask=%s --saveshift=%s % + # ( mag_name, magdw_name, ph_name, esp, tediff, mask_name, vsmmag_name) + vsm = pe.Node(fsl.FUGUE(save_shift=True), name='generate_vsm') + vsm_mean = pe.Node(niu.Function(input_names=['in_file', 'mask_file', 'in_unwarped'], output_names=[ + 'out_file'], function=_vsm_remove_mean), name='vsm_mean_shift') + + # fugue_epi + dwi_split = pe.Node(niu.Function(input_names=[ + 'in_file'], output_names=['out_files'], function=_split_dwi), name='dwi_split') + # 'fugue -i %s -u %s --loadshift=%s --mask=%s' % ( vol_name, out_vol_name, vsm_name, mask_name ) + dwi_applyxfm = pe.MapNode(fsl.FUGUE( + icorr=True, save_shift=False), iterfield=['in_file'], name='dwi_fugue') + # Merge back all volumes + dwi_merge = pe.Node(fsl.utils.Merge( + dimension='t'), name='dwi_merge') + + outputnode = pe.Node( + niu.IdentityInterface(fields=['epi_corrected']), + name='outputnode') + + pipeline.connect([ + (inputnode, dwell_time, [('epi_echospacing', 'dwell_time'), ('pi_accel_factor', 'pi_factor'), ('epi_rev_encoding', 'is_reverse_encoding')]) + ,(inputnode, select_mag, [('fieldmap_mag', 'in_file')]) + ,(inputnode, norm_pha, [('fieldmap_pha', 'in_file')]) + ,(select_mag, mask_mag, [('roi_file', 'in_file')]) + ,(mask_mag, mask_mag_dil, [('mask_file', 'in_file')]) + ,(select_mag, prelude, [('roi_file', 'magnitude_file')]) + ,(norm_pha, prelude, [('out_file', 'phase_file')]) + ,(mask_mag_dil, prelude, [('out_file', 'mask_file')]) + ,(prelude, fill_phase, [('unwrapped_phase_file', 'in_file')]) + ,(inputnode, vsm, [('fieldmap_mag', 'in_file')]) + ,(fill_phase, vsm, [('out_file', 'phasemap_in_file')]) + ,(inputnode, vsm, [(('te_diff', _ms2sec), 'asym_se_time'), ('vsm_sigma', 'smooth2d')]) + ,(dwell_time, vsm, [(('dwell_time', _ms2sec), 'dwell_time')]) + ,(mask_mag_dil, vsm, [('out_file', 'mask_file')]) + ,(mask_mag_dil, vsm_mean, [('out_file', 'mask_file')]) + ,(vsm, vsm_mean, [('unwarped_file', 'in_unwarped'), ('shift_out_file', 'in_file')]) + ,(inputnode, dwi_split, [('in_file', 'in_file')]) + ,(dwi_split, dwi_applyxfm, [('out_files', 'in_file')]) + ,(dwi_applyxfm, dwi_merge, [('unwarped_file', 'in_files')]) + ,(dwi_merge, outputnode, [('merged_file', 'epi_corrected')]) + ]) + + if fieldmap_registration: + """ Register magfw to example epi. There are some parameters here that may need to be tweaked. Should probably strip the mag + Pre-condition: forward warp the mag in order to reg with func. What does mask do here? + """ + # Select reference volume from EPI (B0 in dMRI and a middle frame in + # fMRI) + select_epi = pe.Node(fsl.utils.ExtractROI( + t_size=1), name='select_epi') + + # fugue -i %s -w %s --loadshift=%s --mask=%s % ( mag_name, magfw_name, + # vsmmag_name, mask_name ), log ) # Forward Map + vsm_fwd = pe.Node(fsl.FUGUE( + save_warped=True), name='vsm_fwd') + vsm_reg = pe.Node(fsl.FLIRT(bins=256, cost='corratio', dof=6, interp='spline', searchr_x=[ + -10, 10], searchr_y=[-10, 10], searchr_z=[-10, 10]), name='vsm_registration') + # 'flirt -in %s -ref %s -out %s -init %s -applyxfm' % ( vsmmag_name, ref_epi, vsmmag_name, magfw_mat_out ) + vsm_applyxfm = pe.Node(fsl.ApplyXfm( + interp='spline'), name='vsm_apply_xfm') + # 'flirt -in %s -ref %s -out %s -init %s -applyxfm' % ( mask_name, ref_epi, mask_name, magfw_mat_out ) + msk_applyxfm = pe.Node(fsl.ApplyXfm( + interp='nearestneighbour'), name='msk_apply_xfm') + + pipeline.connect([ + (inputnode, select_epi, [('in_file', 'in_file'), ('ref_num', 't_min')]) + ,(select_epi, vsm_reg, [('roi_file', 'reference')]) + ,(vsm, vsm_fwd, [('shift_out_file', 'shift_in_file')]) + ,(mask_mag_dil, vsm_fwd, [('out_file', 'mask_file')]) + ,(inputnode, vsm_fwd, [('fieldmap_mag', 'in_file')]) + ,(vsm_fwd, vsm_reg, [('warped_file', 'in_file')]) + ,(vsm_reg, msk_applyxfm, [('out_matrix_file', 'in_matrix_file')]) + ,(select_epi, msk_applyxfm, [('roi_file', 'reference')]) + ,(mask_mag_dil, msk_applyxfm, [('out_file', 'in_file')]) + ,(vsm_reg, vsm_applyxfm, [('out_matrix_file', 'in_matrix_file')]) + ,(select_epi, vsm_applyxfm, [('roi_file', 'reference')]) + ,(vsm_mean, vsm_applyxfm, [('out_file', 'in_file')]) + ,(msk_applyxfm, dwi_applyxfm, [('out_file', 'mask_file')]) + ,(vsm_applyxfm, dwi_applyxfm, [('out_file', 'shift_in_file')]) + ]) + else: + pipeline.connect([ + (mask_mag_dil, dwi_applyxfm, [('out_file', 'mask_file')]) + ,( vsm_mean, dwi_applyxfm, [('out_file', 'shift_in_file')]) + ]) + + return pipeline + + +def avg_b0(in_dwi, in_bval, out_file=None): + """ + A function that averages *b0* volumes from a DWI dataset. + + .. warning:: *b0* should be already registered (head motion artifact should + be corrected). + + """ + import numpy as np + import nibabel as nb + import os.path as op + + if out_file is None: + fname,ext = op.splitext(op.basename(in_dwi)) + if ext == ".gz": + fname,ext2 = op.splitext(fname) + ext = ext2 + ext + out_file = op.abspath("%s_avg_b0%s" % (fname, ext)) + + imgs = nb.four_to_three(nb.load(in_dwi)) + bval = np.loadtxt(in_bval) + + b0s = [] + + for bval, img in zip(bval, imgs): + if bval==0: + b0s.append(img.get_data()) + + b0 = np.average(np.array(b0s), axis=0) + + hdr = imgs[0].get_header().copy() + nii = nb.Nifti1Image(b0, imgs[0].get_affine(), hdr) + + nb.save(nii, out_file) + return out_file + + +def rotate_bvecs(in_bvec, in_matrix): + """ + Rotates the input bvec file accordingly with a list of matrices. + + .. note:: the input affine matrix transforms points in the destination image to their \ + corresponding coordinates in the original image. Therefore, this matrix should be inverted \ + first, as we want to know the target position of :math:`\\vec{r}`. + + """ + import os + import numpy as np + + name, fext = os.path.splitext(os.path.basename(in_bvec)) + if fext == '.gz': + name, _ = os.path.splitext(name) + out_file = os.path.abspath('./%s_rotated.bvec' % name) + bvecs = np.loadtxt(in_bvec).T + new_bvecs = [] + + if len(bvecs) != len(in_matrix): + raise RuntimeError('Number of b-vectors and rotation matrices should match.') + + for bvec, mat in zip(bvecs, in_matrix): + if np.all(bvec==0.0): + new_bvecs.append(bvec) + else: + invrot = np.linalg.inv(np.loadtxt(mat))[:3,:3] + newbvec = invrot.dot(bvec) + new_bvecs.append((newbvec/np.linalg.norm(newbvec))) + + np.savetxt(out_file, np.array(new_bvecs).T, fmt='%0.15f') + return out_file + + +def _cat_logs(in_files): + import shutil + import os + + name, fext = os.path.splitext(os.path.basename(in_files[0])) + if fext == '.gz': + name, _ = os.path.splitext(name) + out_file = os.path.abspath('./%s_ecclog.log' % name) + out_str = '' + with open(out_file, 'wb') as totallog: + for i, fname in enumerate(in_files): + totallog.write('\n\npreprocessing %d\n' % i) + with open(fname) as inlog: + for line in inlog: + totallog.write(line) + return out_file + + +def _compute_dwelltime(dwell_time=0.68, pi_factor=1.0, is_reverse_encoding=False): + dwell_time *= (1.0/pi_factor) + + if is_reverse_encoding: + dwell_time *= -1.0 + + return dwell_time + +def _effective_echospacing( dwell_time, pi_factor=1.0 ): + dwelltime = 1.0e-3 * dwell_time * ( 1.0/pi_factor ) + return dwelltime + + +def _prepare_phasediff(in_file): + import nibabel as nib + import os + import numpy as np + img = nib.load(in_file) + max_diff = np.max(img.get_data().reshape(-1)) + min_diff = np.min(img.get_data().reshape(-1)) + A = (2.0 * np.pi)/(max_diff-min_diff) + B = np.pi - (A * max_diff) + diff_norm = img.get_data() * A + B + + name, fext = os.path.splitext(os.path.basename(in_file)) + if fext == '.gz': + name, _ = os.path.splitext(name) + out_file = os.path.abspath('./%s_2pi.nii.gz' % name) + nib.save(nib.Nifti1Image( + diff_norm, img.get_affine(), img.get_header()), out_file) + return out_file + + +def _dilate_mask(in_file, iterations=4): + import nibabel as nib + import scipy.ndimage as ndimage + import os + img = nib.load(in_file) + img._data = ndimage.binary_dilation(img.get_data(), iterations=iterations) + + name, fext = os.path.splitext(os.path.basename(in_file)) + if fext == '.gz': + name, _ = os.path.splitext(name) + out_file = os.path.abspath('./%s_dil.nii.gz' % name) + nib.save(img, out_file) + return out_file + + +def _fill_phase(in_file): + import nibabel as nib + import os + import numpy as np + img = nib.load(in_file) + dumb_img = nib.Nifti1Image(np.zeros( + img.get_shape()), img.get_affine(), img.get_header()) + out_nii = nib.funcs.concat_images((img, dumb_img)) + name, fext = os.path.splitext(os.path.basename(in_file)) + if fext == '.gz': + name, _ = os.path.splitext(name) + out_file = os.path.abspath('./%s_fill.nii.gz' % name) + nib.save(out_nii, out_file) + return out_file + + +def _vsm_remove_mean(in_file, mask_file, in_unwarped): + import nibabel as nib + import os + import numpy as np + import numpy.ma as ma + img = nib.load(in_file) + msk = nib.load(mask_file).get_data() + img_data = img.get_data() + img_data[msk == 0] = 0 + vsmmag_masked = ma.masked_values(img_data.reshape(-1), 0.0) + vsmmag_masked = vsmmag_masked - vsmmag_masked.mean() + img._data = vsmmag_masked.reshape(img.get_shape()) + name, fext = os.path.splitext(os.path.basename(in_file)) + if fext == '.gz': + name, _ = os.path.splitext(name) + out_file = os.path.abspath('./%s_demeaned.nii.gz' % name) + nib.save(img, out_file) + return out_file + + +def _ms2sec(val): + return val*1e-3; + + +def _split_dwi(in_file): + import nibabel as nib + import os + out_files = [] + frames = nib.funcs.four_to_three(nib.load(in_file)) + name, fext = os.path.splitext(os.path.basename(in_file)) + if fext == '.gz': + name, _ = os.path.splitext(name) + for i, frame in enumerate(frames): + out_file = os.path.abspath('./%s_%03d.nii.gz' % (name, i)) + nib.save(frame, out_file) + out_files.append(out_file) + return out_files diff --git a/nipype/workflows/dmri/preprocess/tests/__init__.py b/nipype/workflows/dmri/preprocess/tests/__init__.py new file mode 100644 index 0000000000..349937997e --- /dev/null +++ b/nipype/workflows/dmri/preprocess/tests/__init__.py @@ -0,0 +1,2 @@ +# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- +# vi: set ft=python sts=4 ts=4 sw=4 et: diff --git a/nipype/workflows/dmri/preprocess/tests/test_epi.py b/nipype/workflows/dmri/preprocess/tests/test_epi.py new file mode 100644 index 0000000000..7ce5cb2ebf --- /dev/null +++ b/nipype/workflows/dmri/preprocess/tests/test_epi.py @@ -0,0 +1,44 @@ +import os + +from nipype.testing import (skipif) +import nipype.workflows.fmri.fsl as fsl_wf +import nipype.interfaces.fsl as fsl +import nipype.interfaces.utility as util +from nipype.interfaces.fsl import no_fsl, no_fsl_course_data + +import nipype.pipeline.engine as pe +import warnings +import tempfile +import shutil +from nipype.workflows.dmri.preprocess.epi import create_eddy_correct_pipeline + + +@skipif(no_fsl) +@skipif(no_fsl_course_data) +def test_create_eddy_correct_pipeline(): + fsl_course_dir = os.path.abspath('fsl_course_data') + + dwi_file = os.path.join(fsl_course_dir, "fdt/subj1/data.nii.gz") + + nipype_eddycorrect = create_eddy_correct_pipeline("nipype_eddycorrect") + nipype_eddycorrect.inputs.inputnode.in_file = dwi_file + nipype_eddycorrect.inputs.inputnode.ref_num = 0 + + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + original_eddycorrect = pe.Node(interface=fsl.EddyCorrect(), name="original_eddycorrect") + original_eddycorrect.inputs.in_file = dwi_file + original_eddycorrect.inputs.ref_num = 0 + + test = pe.Node(util.AssertEqual(), name="eddy_corrected_dwi_test") + + pipeline = pe.Workflow(name="test_eddycorrect") + pipeline.base_dir = tempfile.mkdtemp(prefix="nipype_test_eddycorrect_") + + pipeline.connect([(nipype_eddycorrect, test, [("outputnode.eddy_corrected", "volume1")]), + (original_eddycorrect, test, [("eddy_corrected", "volume2")]), + ]) + + pipeline.run(plugin='Linear') + shutil.rmtree(pipeline.base_dir) + From c0647058a1742ddddd4c99d885188ba574d5df61 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Fri, 29 Aug 2014 19:11:10 +0200 Subject: [PATCH 02/33] enh:included eddy_correction --- nipype/workflows/dmri/fsl/epi.py | 9 +- nipype/workflows/dmri/preprocess/__init__.py | 2 +- nipype/workflows/dmri/preprocess/epi.py | 810 ++++--------------- 3 files changed, 175 insertions(+), 646 deletions(-) diff --git a/nipype/workflows/dmri/fsl/epi.py b/nipype/workflows/dmri/fsl/epi.py index 48fb6e8b32..00769871f5 100644 --- a/nipype/workflows/dmri/fsl/epi.py +++ b/nipype/workflows/dmri/fsl/epi.py @@ -118,11 +118,12 @@ def create_motion_correct_pipeline(name='motion_correct'): (Leemans et al. 2009 - http://www.ncbi.nlm.nih.gov/pubmed/19319973), making use of the rotation matrix obtained by FLIRT. - .. warning:: + .. deprecated:: 1.0.0. + Use :func:`nipype.workflows.dmri.preprocess.epi.motion_correct` instead - IMPORTANT NOTICE: this workflow rotates the b-vectors, so please be adviced - that not all the dicom converters ensure the consistency between the resulting - nifti orientation and the b matrix table (e.g. dcm2nii checks it). + .. warning:: IMPORTANT NOTICE: this workflow rotates the b-vectors, so please be adviced + that not all the dicom converters ensure the consistency between the resulting + nifti orientation and the b matrix table (e.g. dcm2nii checks it). Example diff --git a/nipype/workflows/dmri/preprocess/__init__.py b/nipype/workflows/dmri/preprocess/__init__.py index 2a2442ec23..29a0379ca3 100644 --- a/nipype/workflows/dmri/preprocess/__init__.py +++ b/nipype/workflows/dmri/preprocess/__init__.py @@ -1 +1 @@ -from epi import (motion_correct) +from epi import motion_correct diff --git a/nipype/workflows/dmri/preprocess/epi.py b/nipype/workflows/dmri/preprocess/epi.py index 9f6f01f7b1..f7d6adefa1 100644 --- a/nipype/workflows/dmri/preprocess/epi.py +++ b/nipype/workflows/dmri/preprocess/epi.py @@ -35,17 +35,20 @@ def motion_correct(name='motion_correct'): ------- >>> from nipype.workflows.dmri.fsl.epi import motion_correct - >>> mc = motion_correct() - >>> mc.inputs.inputnode.in_file = 'diffusion.nii' - >>> mc.inputs.inputnode.in_bvec = 'diffusion.bvec' - >>> mc.inputs.inputnode.ref_num = 0 - >>> mc.run() # doctest: +SKIP + >>> hmc = motion_correct() + >>> hmc.inputs.inputnode.in_file = 'diffusion.nii' + >>> hmc.inputs.inputnode.in_bvec = 'diffusion.bvec' + >>> hmc.inputs.inputnode.in_mask = 'mask.nii' + >>> hmc.run() # doctest: +SKIP Inputs:: inputnode.in_file - input dwi file - inputnode.ref_num - index of the `b0` volume that should be taken as reference - inputnode.in_bvec - gradients file (`b` vectors) + inputnode.in_mask - weights mask of reference image (a file with data range \ +in [0.0, 1.0], indicating the weight of each voxel when computing the metric. + inputnode.in_bvec - gradients file (b-vectors) + inputnode.ref_num (optional, default=0) index of the b0 volume that should be \ +taken as reference Outputs:: @@ -58,7 +61,7 @@ def motion_correct(name='motion_correct'): 'in_mask']), name='inputnode') split = pe.Node(fsl.Split(dimension='t'), name='SplitDWIs') pick_ref = pe.Node(niu.Select(), name='Pick_b0') - flirt = pe.MapNode(fsl.FLIRT(no_search=True, interp='spline', cost='normmi', + flirt = pe.MapNode(fsl.FLIRT(interp='spline', cost='normmi', cost_func = 'normmi', dof=6, bins=64, save_log=True, searchr_x=[-4,4], searchr_y=[-4,4], searchr_z=[-4,4], fine_search=1, coarse_search=10, padding_size=1), @@ -71,17 +74,11 @@ def motion_correct(name='motion_correct'): 'out_bvec', 'out_xfms']), name='outputnode') - def _checkset(ref_num): - from nipype.interfaces.base import isdefined - if (ref_num is None) or not isdefined(ref_num): - return 0 - return ref_num - wf = pe.Workflow(name=name) wf.connect([ (inputnode, split, [('in_file', 'in_file')]) ,(split, pick_ref, [('out_files', 'inlist')]) - ,(inputnode, pick_ref, [(('ref_num', _checkset), 'index')]) + ,(inputnode, pick_ref, [(('ref_num', _checkrnum), 'index')]) ,(inputnode, flirt, [('in_mask', 'ref_weight')]) ,(split, flirt, [('out_files', 'in_file')]) ,(inputnode, rot_bvec, [('in_bvec', 'in_bvec')]) @@ -94,552 +91,204 @@ def _checkset(ref_num): ]) return wf -############################################################################### -# Old workflows, mark deprecated -############################################################################### - -def create_motion_correct_pipeline(name='create_motion_correct_pipeline'): - """ - .. deprecated:: 0.9.2. - Please use :func:`.motion_correct` instead - - """ - return motion_correct(name) - -def create_dmri_preprocessing(name='dMRI_preprocessing', use_fieldmap=True, fieldmap_registration=False): - """Creates a workflow that chains the necessary pipelines to - correct for motion, eddy currents, and, if selected, susceptibility - artifacts in EPI dMRI sequences. - - .. warning:: IMPORTANT NOTICE: this workflow rotates the b-vectors, so please be advised \ - that not all the dicom converters ensure the consistency between the resulting \ - nifti orientation and the b matrix table (e.g. dcm2nii checks it). - - - Example - ------- - - >>> nipype_dmri_preprocess = create_dmri_preprocessing('nipype_dmri_prep') - >>> nipype_dmri_preprocess.inputs.inputnode.in_file = 'diffusion.nii' - >>> nipype_dmri_preprocess.inputs.inputnode.in_bvec = 'diffusion.bvec' - >>> nipype_dmri_preprocess.inputs.inputnode.ref_num = 0 - >>> nipype_dmri_preprocess.inputs.inputnode.fieldmap_mag = 'magnitude.nii' - >>> nipype_dmri_preprocess.inputs.inputnode.fieldmap_pha = 'phase.nii' - >>> nipype_dmri_preprocess.inputs.inputnode.te_diff = 2.46 - >>> nipype_dmri_preprocess.inputs.inputnode.epi_echospacing = 0.77 - >>> nipype_dmri_preprocess.inputs.inputnode.epi_rev_encoding = False - >>> nipype_dmri_preprocess.inputs.inputnode.pi_accel_factor = True - >>> nipype_dmri_preprocess.run() # doctest: +SKIP - - - Inputs:: - - inputnode.in_file - The diffusion data - inputnode.in_bvec - The b-matrix file, in FSL format and consistent with the in_file orientation - inputnode.ref_num - The reference volume (a b=0 volume in dMRI) - inputnode.fieldmap_mag - The magnitude of the fieldmap - inputnode.fieldmap_pha - The phase difference of the fieldmap - inputnode.te_diff - TE increment used (in msec.) on the fieldmap acquisition (generally 2.46ms for 3T scanners) - inputnode.epi_echospacing - The EPI EchoSpacing parameter (in msec.) - inputnode.epi_rev_encoding - True if reverse encoding was used (generally False) - inputnode.pi_accel_factor - Parallel imaging factor (aka GRAPPA acceleration factor) - inputnode.vsm_sigma - Sigma (in mm.) of the gaussian kernel used for in-slice smoothing of the deformation field (voxel shift map, vsm) - +def eddy_correct(name='eddy_correct'): + """Creates a pipeline that corrects for artifacts induced by Eddy currents in dMRI + sequences. + It takes a series of diffusion weighted images and linearly co-registers + them to one reference image (the average of all b0s in the dataset). - Outputs:: + DWIs are also modulated by the determinant of the Jacobian as indicated by [3]_ and + [4]_. - outputnode.dmri_corrected - outputnode.bvec_rotated + A list of rigid transformation matrices can be provided, sourcing from a + :func:`.motion_correct` workflow, to initialize registrations in a *motion free* + framework. + A list of affine transformation matrices is available as output, so that transforms + can be chained (discussion + `here `_). - Optional arguments:: - use_fieldmap - True if there are fieldmap files that should be used (default True) - fieldmap_registration - True if registration to fieldmap should be performed (default False) + .. admonition:: References + .. [3] Jones DK, `The signal intensity must be modulated by the determinant of \ + the Jacobian when correcting for eddy currents in diffusion MRI \ + `_, + Proc. ISMRM 18th Annual Meeting, (2010). - """ - - pipeline = pe.Workflow(name=name) - - inputnode = pe.Node(niu.IdentityInterface( - fields=['in_file', 'in_bvec', 'ref_num', 'fieldmap_mag', - 'fieldmap_pha', 'te_diff', 'epi_echospacing', - 'epi_rev_encoding', 'pi_accel_factor', 'vsm_sigma']), - name='inputnode') - - outputnode = pe.Node(niu.IdentityInterface( - fields=['dmri_corrected', 'bvec_rotated']), - name='outputnode') - - motion = create_motion_correct_pipeline() - eddy = create_eddy_correct_pipeline() - - if use_fieldmap: # we have a fieldmap, so lets use it (yay!) - susceptibility = create_epidewarp_pipeline( - fieldmap_registration=fieldmap_registration) - - pipeline.connect([ - (inputnode, motion, [('in_file', 'inputnode.in_file'), - ('in_bvec', 'inputnode.in_bvec'), - ('ref_num', 'inputnode.ref_num')]), - (inputnode, eddy, [('ref_num', 'inputnode.ref_num')]), - (motion, eddy, [('outputnode.motion_corrected', 'inputnode.in_file')]), - (eddy, susceptibility, [('outputnode.eddy_corrected', 'inputnode.in_file')]), - (inputnode, susceptibility, [('ref_num', 'inputnode.ref_num'), - ('fieldmap_mag', 'inputnode.fieldmap_mag'), - ('fieldmap_pha', 'inputnode.fieldmap_pha'), - ('te_diff', 'inputnode.te_diff'), - ('epi_echospacing', 'inputnode.epi_echospacing'), - ('epi_rev_encoding', 'inputnode.epi_rev_encoding'), - ('pi_accel_factor', 'inputnode.pi_accel_factor'), - ('vsm_sigma', 'inputnode.vsm_sigma')]), - (motion, outputnode, [('outputnode.out_bvec', 'bvec_rotated')]), - (susceptibility, outputnode, [('outputnode.epi_corrected', 'dmri_corrected')]) - ]) - else: # we don't have a fieldmap, so we just carry on without it :( - pipeline.connect([ - (inputnode, motion, [('in_file', 'inputnode.in_file'), - ('in_bvec', 'inputnode.in_bvec'), - ('ref_num', 'inputnode.ref_num')]), - (inputnode, eddy, [('ref_num', 'inputnode.ref_num')]), - (motion, eddy, [('outputnode.motion_corrected', 'inputnode.in_file')]), - (motion, outputnode, [('outputnode.out_bvec', 'bvec_rotated')]), - (eddy, outputnode, [('outputnode.eddy_corrected', 'dmri_corrected')]) - ]) - - return pipeline - - -def create_eddy_correct_pipeline(name='eddy_correct'): - """Creates a pipeline that replaces eddy_correct script in FSL. It takes a - series of diffusion weighted images and linearly co-registers them to one - reference image. No rotation of the B-matrix is performed, so this pipeline - should be executed after the motion correction pipeline. + .. [4] Rohde et al., `Comprehensive Approach for Correction of Motion and Distortion \ + in Diffusion-Weighted MRI \ + `_, MRM 51:103-114 (2004). Example ------- - >>> nipype_eddycorrect = create_eddy_correct_pipeline('nipype_eddycorrect') - >>> nipype_eddycorrect.inputs.inputnode.in_file = 'diffusion.nii' - >>> nipype_eddycorrect.inputs.inputnode.ref_num = 0 - >>> nipype_eddycorrect.run() # doctest: +SKIP + >>> from nipype.workflows.dmri.fsl.epi import eddy_correct + >>> ecc = eddy_correct() + >>> ecc.inputs.inputnode.in_file = 'diffusion.nii' + >>> ecc.inputs.inputnode.in_bval = 'diffusion.bval' + >>> ecc.inputs.inputnode.in_mask = 'mask.nii' + >>> ecc.run() # doctest: +SKIP Inputs:: - inputnode.in_file - inputnode.ref_num + inputnode.in_file - input dwi file + inputnode.in_mask - weights mask of reference image (a file with data range \ +sin [0.0, 1.0], indicating the weight of each voxel when computing the metric. + inputnode.in_bval - b-values table + inputnode.in_xfms - list of matrices to initialize registration (from head-motion correction) Outputs:: - outputnode.eddy_corrected - """ - - inputnode = pe.Node( - niu.IdentityInterface(fields=['in_file', 'ref_num']), - name='inputnode') - - pipeline = pe.Workflow(name=name) - - split = pe.Node(fsl.Split(dimension='t'), name='split') - pick_ref = pe.Node(niu.Select(), name='pick_ref') - coregistration = pe.MapNode(fsl.FLIRT(no_search=True, padding_size=1, - dof=12, interp='spline'), name='coregistration', iterfield=['in_file']) - merge = pe.Node(fsl.Merge(dimension='t'), name='merge') - outputnode = pe.Node( - niu.IdentityInterface(fields=['eddy_corrected']), - name='outputnode') - - pipeline.connect([ - (inputnode, split, [('in_file', 'in_file')]) - ,(split, pick_ref, [('out_files', 'inlist')]) - ,(inputnode, pick_ref, [('ref_num', 'index')]) - ,(split, coregistration, [('out_files', 'in_file')]) - ,(pick_ref, coregistration, [('out', 'reference')]) - ,(coregistration, merge, [('out_file', 'in_files')]) - ,(merge, outputnode, [('merged_file', 'eddy_corrected')]) - ]) - return pipeline - - - - -def fieldmap_correction(name='fieldmap_correction', nocheck=False): + outputnode.out_file - corrected dwi file + outputnode.out_xfms - list of transformation matrices """ - Fieldmap-based retrospective correction of EPI images for the susceptibility distortion - artifact (Jezzard et al., 1995). Fieldmap images are assumed to be already registered - to EPI data, and a brain mask is required. - - Replaces the former workflow, still available as create_epidewarp_pipeline(). The difference - with respect the epidewarp pipeline is that now the workflow uses the new fsl_prepare_fieldmap - available as of FSL 5.0. - - - - Example - ------- - - >>> nipype_epicorrect = fieldmap_correction('nipype_epidewarp') - >>> nipype_epicorrect.inputs.inputnode.in_file = 'diffusion.nii' - >>> nipype_epicorrect.inputs.inputnode.in_mask = 'brainmask.nii' - >>> nipype_epicorrect.inputs.inputnode.fieldmap_pha = 'phase.nii' - >>> nipype_epicorrect.inputs.inputnode.fieldmap_mag = 'magnitude.nii' - >>> nipype_epicorrect.inputs.inputnode.te_diff = 2.46 - >>> nipype_epicorrect.inputs.inputnode.epi_echospacing = 0.77 - >>> nipype_epicorrect.inputs.inputnode.encoding_direction = 'y' - >>> nipype_epicorrect.run() # doctest: +SKIP + inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_bval', + 'in_mask', 'in_xfms']), name='inputnode') + split = pe.Node(fsl.Split(dimension='t'), name='SplitDWIs') + avg_b0 = pe.Node(niu.Function(input_names=['in_dwi', 'in_bval'], + output_names=['out_file'], function=b0_average), name='b0_avg') + pick_dwi = pe.Node(niu.Select(), name='Pick_DWIs') + flirt = pe.MapNode(fsl.FLIRT(no_search=True, interp='spline', cost='normmi', + cost_func = 'normmi', dof=12, bins=64, save_log=True, + padding_size=1), name='CoRegistration', + iterfield=['in_file', 'in_matrix_file']) + initmat = pe.Node(niu.Function(input_names=['in_bval', 'in_xfms'], + output_names=['init_xfms'], function=_checkinitxfm), + name='InitXforms') + + mult = pe.MapNode(fsl.BinaryMaths(operation='mul'), name='ModulateDWIs', + iterfield=['in_file', 'operand_value']) + thres = pe.MapNode(fsl.Threshold(thresh=0.0), iterfield=['in_file'], + name='RemoveNegative') + + get_mat = pe.Node(niu.Function(input_names=['in_bval', 'in_xfms'], + output_names=['out_files'], function=recompose_xfm), + name='GatherMatrices') + merge = pe.Node(niu.Function(input_names=['in_dwi', 'in_bval', 'in_corrected'], + output_names=['out_file'], function=recompose_dwi), name='MergeDWIs') + + outputnode = pe.Node(niu.IdentityInterface(fields=['out_file', 'out_xfms']), + name='outputnode') - Inputs:: + wf = pe.Workflow(name=name) + wf.connect([ + (inputnode, split, [('in_file', 'in_file')]) + ,(inputnode, avg_b0, [('in_file', 'in_dwi'), + ('in_bval', 'in_bval')]) + ,(inputnode, merge, [('in_file', 'in_dwi'), + ('in_bval', 'in_bval')]) + ,(inputnode, initmat, [('in_xfms', 'in_xfms'), + ('in_bval', 'in_bval')]) + ,(inputnode, get_mat, [('in_bval', 'in_bval')]) + ,(split, pick_dwi, [('out_files', 'inlist')]) + ,(inputnode, pick_dwi, [(('in_bval', _nonb0), 'index')]) + ,(inputnode, flirt, [('in_mask', 'ref_weight')]) + ,(avg_b0, flirt, [('out_file', 'reference')]) + ,(pick_dwi, flirt, [('out', 'in_file')]) + ,(initmat, flirt, [('init_xfms', 'in_matrix_file')]) + ,(flirt, get_mat, [('out_matrix_file', 'in_xfms')]) + ,(flirt, mult, [(('out_matrix_file',_xfm_jacobian), 'operand_value')]) + ,(flirt, mult, [('out_file', 'in_file')]) + ,(mult, thres, [('out_file', 'in_file')]) + ,(thres, merge, [('out_file', 'in_corrected')]) + ,(get_mat, outputnode, [('out_files', 'out_xfms')]) + ,(merge, outputnode, [('out_file', 'out_file')]) + ]) + return wf - inputnode.in_file - The volume acquired with EPI sequence - inputnode.in_mask - A brain mask - inputnode.fieldmap_pha - The phase difference map from the fieldmapping, registered to in_file - inputnode.fieldmap_mag - The magnitud maps (usually 4D, one magnitude per GRE scan) - from the fieldmapping, registered to in_file - inputnode.te_diff - Time difference in msec. between TE in ms of the fieldmapping (usually a GRE sequence). - inputnode.epi_echospacing - The effective echo spacing (aka dwell time) in msec. of the EPI sequence. If - EPI was acquired with parallel imaging, then the effective echo spacing is - eff_es = es / acc_factor. - inputnode.encoding_direction - The phase encoding direction in EPI acquisition (default y) - inputnode.vsm_sigma - Sigma value of the gaussian smoothing filter applied to the vsm (voxel shift map) +def _checkrnum(ref_num): + from nipype.interfaces.base import isdefined + if (ref_num is None) or not isdefined(ref_num): + return 0 + return ref_num - Outputs:: +def _checkinitxfm(in_bval, in_xfms=None): + from nipype.interfaces.base import isdefined + import numpy as np + import os.path as op + bvals = np.loadtxt(in_bval) + non_b0 = np.where(bvals!=0)[0].tolist() + + init_xfms = [] + if (in_xfms is None) or (not isdefined(in_xfms)) or (len(in_xfms)!=len(bvals)): + for i in non_b0: + xfm_file = op.abspath('init_%04d.mat' % i) + np.savetxt(xfm_file, np.eye(4)) + init_xfms.append(xfm_file) + else: + for i in non_b0: + init_xfms.append(in_xfms[i]) + return init_xfms - outputnode.epi_corrected - outputnode.out_vsm +def _nonb0(in_bval): + import numpy as np + bvals = np.loadtxt(in_bval) + return np.where(bvals!=0)[0].tolist() +def recompose_dwi(in_dwi, in_bval, in_corrected, out_file=None): """ - - inputnode = pe.Node(niu.IdentityInterface( - fields=['in_file', - 'in_mask', - 'fieldmap_pha', - 'fieldmap_mag', - 'te_diff', - 'epi_echospacing', - 'vsm_sigma', - 'encoding_direction' - ]), name='inputnode' - ) - - pipeline = pe.Workflow(name=name) - - # Keep first frame from magnitude - select_mag = pe.Node(fsl.utils.ExtractROI( - t_size=1, t_min=0), name='select_magnitude') - - # Mask magnitude (it is required by PreparedFieldMap) - mask_mag = pe.Node( fsl.maths.ApplyMask(), name='mask_magnitude' ) - - # Run fsl_prepare_fieldmap - fslprep = pe.Node( fsl.PrepareFieldmap(), name='prepare_fieldmap' ) - - if nocheck: - fslprep.inputs.nocheck = True - - # Use FUGUE to generate the voxel shift map (vsm) - vsm = pe.Node(fsl.FUGUE(save_shift=True), name='generate_vsm') - - # VSM demean is not anymore present in the epi_reg script - #vsm_mean = pe.Node(niu.Function(input_names=['in_file', 'mask_file', 'in_unwarped'], output_names=[ - # 'out_file'], function=_vsm_remove_mean), name='vsm_mean_shift') - - # fugue_epi - dwi_split = pe.Node(niu.Function(input_names=[ - 'in_file'], output_names=['out_files'], function=_split_dwi), name='dwi_split') - - # 'fugue -i %s -u %s --loadshift=%s --mask=%s' % ( vol_name, out_vol_name, vsm_name, mask_name ) - dwi_applyxfm = pe.MapNode(fsl.FUGUE( - icorr=True, save_shift=False), iterfield=['in_file'], name='dwi_fugue') - # Merge back all volumes - dwi_merge = pe.Node(fsl.utils.Merge( - dimension='t'), name='dwi_merge') - - outputnode = pe.Node( - niu.IdentityInterface(fields=['epi_corrected','out_vsm']), - name='outputnode') - - pipeline.connect([ - (inputnode, select_mag, [('fieldmap_mag', 'in_file')]) - ,(inputnode, fslprep, [('fieldmap_pha', 'in_phase'),('te_diff', 'delta_TE') ]) - ,(inputnode, mask_mag, [('in_mask', 'mask_file' )]) - ,(select_mag, mask_mag, [('roi_file', 'in_file')]) - ,(mask_mag, fslprep, [('out_file', 'in_magnitude')]) - ,(fslprep, vsm, [('out_fieldmap', 'phasemap_in_file')]) - ,(inputnode, vsm, [('fieldmap_mag', 'in_file'), - ('encoding_direction','unwarp_direction'), - (('te_diff', _ms2sec), 'asym_se_time'), - ('vsm_sigma', 'smooth2d'), - (('epi_echospacing', _ms2sec), 'dwell_time')]) - ,(mask_mag, vsm, [('out_file', 'mask_file')]) - ,(inputnode, dwi_split, [('in_file', 'in_file')]) - ,(dwi_split, dwi_applyxfm, [('out_files', 'in_file')]) - ,(mask_mag, dwi_applyxfm, [('out_file', 'mask_file')]) - ,(vsm, dwi_applyxfm, [('shift_out_file', 'shift_in_file')]) - ,(inputnode, dwi_applyxfm, [('encoding_direction','unwarp_direction')]) - ,(dwi_applyxfm, dwi_merge, [('unwarped_file', 'in_files')]) - ,(dwi_merge, outputnode, [('merged_file', 'epi_corrected')]) - ,(vsm, outputnode, [('shift_out_file','out_vsm') ]) - ]) - - - return pipeline - - -def topup_correction( name='topup_correction' ): + Recompose back the dMRI data accordingly the b-values table after EC correction """ - Corrects for susceptibilty distortion of EPI images when one reverse encoding dataset has - been acquired - - - Example - ------- + import numpy as np + import nibabel as nb + import os.path as op - >>> nipype_epicorrect = topup_correction('nipype_topup') - >>> nipype_epicorrect.inputs.inputnode.in_file_dir = 'epi.nii' - >>> nipype_epicorrect.inputs.inputnode.in_file_rev = 'epi_rev.nii' - >>> nipype_epicorrect.inputs.inputnode.encoding_direction = ['y', 'y-'] - >>> nipype_epicorrect.inputs.inputnode.ref_num = 0 - >>> nipype_epicorrect.run() # doctest: +SKIP + if out_file is None: + fname,ext = op.splitext(op.basename(in_dwi)) + if ext == ".gz": + fname,ext2 = op.splitext(fname) + ext = ext2 + ext + out_file = op.abspath("%s_eccorrect%s" % (fname, ext)) - Inputs:: - inputnode.in_file_dir - EPI volume acquired in 'forward' phase encoding - inputnode.in_file_rev - EPI volume acquired in 'reversed' phase encoding - inputnode.encoding_direction - Direction encoding of in_file_dir - inputnode.ref_num - Identifier of the reference volumes (usually B0 volume) + im = nb.load(in_dwi) + dwidata = im.get_data() + bvals = np.loadtxt(in_bval) + non_b0 = np.where(bvals!=0)[0].tolist() - Outputs:: + if len(non_b0)!=len(in_corrected): + raise RuntimeError('Length of DWIs in b-values table and after correction should match') - outputnode.epi_corrected + for bindex, dwi in zip(non_b0, in_corrected): + dwidata[...,bindex] = nb.load(dwi).get_data() + nb.Nifti1Image(dwidata, im.get_affine(), im.get_header()).to_filename(out_file) + return out_file +def recompose_xfm(in_bval, in_xfms): """ - pipeline = pe.Workflow(name=name) - - inputnode = pe.Node(niu.IdentityInterface( - fields=['in_file_dir', - 'in_file_rev', - 'encoding_direction', - 'readout_times', - 'ref_num' - ]), name='inputnode' - ) - - outputnode = pe.Node( niu.IdentityInterface( - fields=['out_fieldcoef', - 'out_movpar', - 'out_enc_file', - 'epi_corrected' - ]), name='outputnode' - ) - - b0_dir = pe.Node( fsl.ExtractROI( t_size=1 ), name='b0_1' ) - b0_rev = pe.Node( fsl.ExtractROI( t_size=1 ), name='b0_2' ) - combin = pe.Node( niu.Merge(2), name='merge' ) - combin2 = pe.Node( niu.Merge(2), name='merge2' ) - merged = pe.Node( fsl.Merge( dimension='t' ), name='b0_comb' ) - - topup = pe.Node( fsl.TOPUP(), name='topup' ) - applytopup = pe.Node( fsl.ApplyTOPUP(in_index=[1,2] ), name='applytopup' ) - - pipeline.connect([ - (inputnode, b0_dir, [('in_file_dir','in_file'),('ref_num','t_min')] ) - ,(inputnode, b0_rev, [('in_file_rev','in_file'),('ref_num','t_min')] ) - ,(inputnode, combin2, [('in_file_dir','in1'),('in_file_rev','in2') ] ) - ,(b0_dir, combin, [('roi_file','in1')] ) - ,(b0_rev, combin, [('roi_file','in2')] ) - ,(combin, merged, [('out', 'in_files')] ) - ,(merged, topup, [('merged_file','in_file')]) - ,(inputnode, topup, [('encoding_direction','encoding_direction'),('readout_times','readout_times') ]) - ,(topup, applytopup, [('out_fieldcoef','in_topup_fieldcoef'),('out_movpar','in_topup_movpar'), - ('out_enc_file','encoding_file')]) - ,(combin2, applytopup, [('out','in_files')] ) - ,(topup, outputnode, [('out_fieldcoef','out_fieldcoef'),('out_movpar','out_movpar'), - ('out_enc_file','out_enc_file') ]) - ,(applytopup,outputnode, [('out_corrected','epi_corrected')]) - ]) - - return pipeline - - -def create_epidewarp_pipeline(name='epidewarp', fieldmap_registration=False): - """ Replaces the epidewarp.fsl script (http://www.nmr.mgh.harvard.edu/~greve/fbirn/b0/epidewarp.fsl) - for susceptibility distortion correction of dMRI & fMRI acquired with EPI sequences and the fieldmap - information (Jezzard et al., 1995) using FSL's FUGUE. The registration to the (warped) fieldmap - (strictly following the original script) is available using fieldmap_registration=True. - - Example - ------- - - >>> nipype_epicorrect = create_epidewarp_pipeline('nipype_epidewarp', fieldmap_registration=False) - >>> nipype_epicorrect.inputs.inputnode.in_file = 'diffusion.nii' - >>> nipype_epicorrect.inputs.inputnode.fieldmap_mag = 'magnitude.nii' - >>> nipype_epicorrect.inputs.inputnode.fieldmap_pha = 'phase.nii' - >>> nipype_epicorrect.inputs.inputnode.te_diff = 2.46 - >>> nipype_epicorrect.inputs.inputnode.epi_echospacing = 0.77 - >>> nipype_epicorrect.inputs.inputnode.epi_rev_encoding = False - >>> nipype_epicorrect.inputs.inputnode.ref_num = 0 - >>> nipype_epicorrect.inputs.inputnode.pi_accel_factor = 1.0 - >>> nipype_epicorrect.run() # doctest: +SKIP - - Inputs:: - - inputnode.in_file - The volume acquired with EPI sequence - inputnode.fieldmap_mag - The magnitude of the fieldmap - inputnode.fieldmap_pha - The phase difference of the fieldmap - inputnode.te_diff - Time difference between TE in ms. - inputnode.epi_echospacing - The echo spacing (aka dwell time) in the EPI sequence - inputnode.epi_ph_encoding_dir - The phase encoding direction in EPI acquisition (default y) - inputnode.epi_rev_encoding - True if it is acquired with reverse encoding - inputnode.pi_accel_factor - Acceleration factor used for EPI parallel imaging (GRAPPA) - inputnode.vsm_sigma - Sigma value of the gaussian smoothing filter applied to the vsm (voxel shift map) - inputnode.ref_num - The reference volume (B=0 in dMRI or a central frame in fMRI) - - - Outputs:: - - outputnode.epi_corrected + Insert identity transformation matrices in b0 volumes to build up a list + """ + import numpy as np + import os.path as op + bvals = np.loadtxt(in_bval) + out_matrix = np.array([np.eye(4)] * len(bvals)) + xfms = iter([np.loadtxt(xfm) for xfm in in_xfms]) + out_files = [] - Optional arguments:: + for i, b in enumerate(bvals): + if b == 0.0: + mat = np.eye(4) + else: + mat = xfms.next() - fieldmap_registration - True if registration to fieldmap should be done (default False) + out_name = 'eccor_%04d.mat' % i + out_files.append(out_name) + np.savetxt(out_name, mat) - """ + return out_files - inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', - 'fieldmap_mag', - 'fieldmap_pha', - 'te_diff', - 'epi_echospacing', - 'epi_ph_encoding_dir', - 'epi_rev_encoding', - 'pi_accel_factor', - 'vsm_sigma', - 'ref_num', - 'unwarp_direction' - ]), name='inputnode') - - pipeline = pe.Workflow(name=name) - - # Keep first frame from magnitude - select_mag = pe.Node(fsl.utils.ExtractROI( - t_size=1, t_min=0), name='select_magnitude') - - # mask_brain - mask_mag = pe.Node(fsl.BET(mask=True), name='mask_magnitude') - mask_mag_dil = pe.Node(niu.Function(input_names=[ - 'in_file'], output_names=['out_file'], function=_dilate_mask), name='mask_dilate') - - # Compute dwell time - dwell_time = pe.Node(niu.Function(input_names=['dwell_time', 'pi_factor', 'is_reverse_encoding'], output_names=[ - 'dwell_time'], function=_compute_dwelltime), name='dwell_time') - - # Normalize phase diff to be [-pi, pi) - norm_pha = pe.Node(niu.Function(input_names=['in_file'], output_names=[ - 'out_file'], function=_prepare_phasediff), name='normalize_phasediff') - # Execute FSL PRELUDE: prelude -p %s -a %s -o %s -f -v -m %s - prelude = pe.Node(fsl.PRELUDE( - process3d=True), name='phase_unwrap') - fill_phase = pe.Node(niu.Function(input_names=['in_file'], output_names=[ - 'out_file'], function=_fill_phase), name='fill_phasediff') - - # to assure that vsm is same dimension as mag. The input only affects the output dimension. - # The content of the input has no effect on the vsm. The de-warped mag volume is - # meaningless and will be thrown away - # fugue -i %s -u %s -p %s --dwell=%s --asym=%s --mask=%s --saveshift=%s % - # ( mag_name, magdw_name, ph_name, esp, tediff, mask_name, vsmmag_name) - vsm = pe.Node(fsl.FUGUE(save_shift=True), name='generate_vsm') - vsm_mean = pe.Node(niu.Function(input_names=['in_file', 'mask_file', 'in_unwarped'], output_names=[ - 'out_file'], function=_vsm_remove_mean), name='vsm_mean_shift') - - # fugue_epi - dwi_split = pe.Node(niu.Function(input_names=[ - 'in_file'], output_names=['out_files'], function=_split_dwi), name='dwi_split') - # 'fugue -i %s -u %s --loadshift=%s --mask=%s' % ( vol_name, out_vol_name, vsm_name, mask_name ) - dwi_applyxfm = pe.MapNode(fsl.FUGUE( - icorr=True, save_shift=False), iterfield=['in_file'], name='dwi_fugue') - # Merge back all volumes - dwi_merge = pe.Node(fsl.utils.Merge( - dimension='t'), name='dwi_merge') - - outputnode = pe.Node( - niu.IdentityInterface(fields=['epi_corrected']), - name='outputnode') - - pipeline.connect([ - (inputnode, dwell_time, [('epi_echospacing', 'dwell_time'), ('pi_accel_factor', 'pi_factor'), ('epi_rev_encoding', 'is_reverse_encoding')]) - ,(inputnode, select_mag, [('fieldmap_mag', 'in_file')]) - ,(inputnode, norm_pha, [('fieldmap_pha', 'in_file')]) - ,(select_mag, mask_mag, [('roi_file', 'in_file')]) - ,(mask_mag, mask_mag_dil, [('mask_file', 'in_file')]) - ,(select_mag, prelude, [('roi_file', 'magnitude_file')]) - ,(norm_pha, prelude, [('out_file', 'phase_file')]) - ,(mask_mag_dil, prelude, [('out_file', 'mask_file')]) - ,(prelude, fill_phase, [('unwrapped_phase_file', 'in_file')]) - ,(inputnode, vsm, [('fieldmap_mag', 'in_file')]) - ,(fill_phase, vsm, [('out_file', 'phasemap_in_file')]) - ,(inputnode, vsm, [(('te_diff', _ms2sec), 'asym_se_time'), ('vsm_sigma', 'smooth2d')]) - ,(dwell_time, vsm, [(('dwell_time', _ms2sec), 'dwell_time')]) - ,(mask_mag_dil, vsm, [('out_file', 'mask_file')]) - ,(mask_mag_dil, vsm_mean, [('out_file', 'mask_file')]) - ,(vsm, vsm_mean, [('unwarped_file', 'in_unwarped'), ('shift_out_file', 'in_file')]) - ,(inputnode, dwi_split, [('in_file', 'in_file')]) - ,(dwi_split, dwi_applyxfm, [('out_files', 'in_file')]) - ,(dwi_applyxfm, dwi_merge, [('unwarped_file', 'in_files')]) - ,(dwi_merge, outputnode, [('merged_file', 'epi_corrected')]) - ]) - - if fieldmap_registration: - """ Register magfw to example epi. There are some parameters here that may need to be tweaked. Should probably strip the mag - Pre-condition: forward warp the mag in order to reg with func. What does mask do here? - """ - # Select reference volume from EPI (B0 in dMRI and a middle frame in - # fMRI) - select_epi = pe.Node(fsl.utils.ExtractROI( - t_size=1), name='select_epi') - - # fugue -i %s -w %s --loadshift=%s --mask=%s % ( mag_name, magfw_name, - # vsmmag_name, mask_name ), log ) # Forward Map - vsm_fwd = pe.Node(fsl.FUGUE( - save_warped=True), name='vsm_fwd') - vsm_reg = pe.Node(fsl.FLIRT(bins=256, cost='corratio', dof=6, interp='spline', searchr_x=[ - -10, 10], searchr_y=[-10, 10], searchr_z=[-10, 10]), name='vsm_registration') - # 'flirt -in %s -ref %s -out %s -init %s -applyxfm' % ( vsmmag_name, ref_epi, vsmmag_name, magfw_mat_out ) - vsm_applyxfm = pe.Node(fsl.ApplyXfm( - interp='spline'), name='vsm_apply_xfm') - # 'flirt -in %s -ref %s -out %s -init %s -applyxfm' % ( mask_name, ref_epi, mask_name, magfw_mat_out ) - msk_applyxfm = pe.Node(fsl.ApplyXfm( - interp='nearestneighbour'), name='msk_apply_xfm') - - pipeline.connect([ - (inputnode, select_epi, [('in_file', 'in_file'), ('ref_num', 't_min')]) - ,(select_epi, vsm_reg, [('roi_file', 'reference')]) - ,(vsm, vsm_fwd, [('shift_out_file', 'shift_in_file')]) - ,(mask_mag_dil, vsm_fwd, [('out_file', 'mask_file')]) - ,(inputnode, vsm_fwd, [('fieldmap_mag', 'in_file')]) - ,(vsm_fwd, vsm_reg, [('warped_file', 'in_file')]) - ,(vsm_reg, msk_applyxfm, [('out_matrix_file', 'in_matrix_file')]) - ,(select_epi, msk_applyxfm, [('roi_file', 'reference')]) - ,(mask_mag_dil, msk_applyxfm, [('out_file', 'in_file')]) - ,(vsm_reg, vsm_applyxfm, [('out_matrix_file', 'in_matrix_file')]) - ,(select_epi, vsm_applyxfm, [('roi_file', 'reference')]) - ,(vsm_mean, vsm_applyxfm, [('out_file', 'in_file')]) - ,(msk_applyxfm, dwi_applyxfm, [('out_file', 'mask_file')]) - ,(vsm_applyxfm, dwi_applyxfm, [('out_file', 'shift_in_file')]) - ]) - else: - pipeline.connect([ - (mask_mag_dil, dwi_applyxfm, [('out_file', 'mask_file')]) - ,( vsm_mean, dwi_applyxfm, [('out_file', 'shift_in_file')]) - ]) - return pipeline +def _xfm_jacobian(in_xfm): + import numpy as np + from math import fabs + return [fabs(np.linalg.det(np.loadtxt(xfm))) for xfm in in_xfm] -def avg_b0(in_dwi, in_bval, out_file=None): +def b0_average(in_dwi, in_bval, out_file=None): """ - A function that averages *b0* volumes from a DWI dataset. + A function that averages the *b0* volumes from a DWI dataset. .. warning:: *b0* should be already registered (head motion artifact should be corrected). @@ -706,124 +355,3 @@ def rotate_bvecs(in_bvec, in_matrix): np.savetxt(out_file, np.array(new_bvecs).T, fmt='%0.15f') return out_file - - -def _cat_logs(in_files): - import shutil - import os - - name, fext = os.path.splitext(os.path.basename(in_files[0])) - if fext == '.gz': - name, _ = os.path.splitext(name) - out_file = os.path.abspath('./%s_ecclog.log' % name) - out_str = '' - with open(out_file, 'wb') as totallog: - for i, fname in enumerate(in_files): - totallog.write('\n\npreprocessing %d\n' % i) - with open(fname) as inlog: - for line in inlog: - totallog.write(line) - return out_file - - -def _compute_dwelltime(dwell_time=0.68, pi_factor=1.0, is_reverse_encoding=False): - dwell_time *= (1.0/pi_factor) - - if is_reverse_encoding: - dwell_time *= -1.0 - - return dwell_time - -def _effective_echospacing( dwell_time, pi_factor=1.0 ): - dwelltime = 1.0e-3 * dwell_time * ( 1.0/pi_factor ) - return dwelltime - - -def _prepare_phasediff(in_file): - import nibabel as nib - import os - import numpy as np - img = nib.load(in_file) - max_diff = np.max(img.get_data().reshape(-1)) - min_diff = np.min(img.get_data().reshape(-1)) - A = (2.0 * np.pi)/(max_diff-min_diff) - B = np.pi - (A * max_diff) - diff_norm = img.get_data() * A + B - - name, fext = os.path.splitext(os.path.basename(in_file)) - if fext == '.gz': - name, _ = os.path.splitext(name) - out_file = os.path.abspath('./%s_2pi.nii.gz' % name) - nib.save(nib.Nifti1Image( - diff_norm, img.get_affine(), img.get_header()), out_file) - return out_file - - -def _dilate_mask(in_file, iterations=4): - import nibabel as nib - import scipy.ndimage as ndimage - import os - img = nib.load(in_file) - img._data = ndimage.binary_dilation(img.get_data(), iterations=iterations) - - name, fext = os.path.splitext(os.path.basename(in_file)) - if fext == '.gz': - name, _ = os.path.splitext(name) - out_file = os.path.abspath('./%s_dil.nii.gz' % name) - nib.save(img, out_file) - return out_file - - -def _fill_phase(in_file): - import nibabel as nib - import os - import numpy as np - img = nib.load(in_file) - dumb_img = nib.Nifti1Image(np.zeros( - img.get_shape()), img.get_affine(), img.get_header()) - out_nii = nib.funcs.concat_images((img, dumb_img)) - name, fext = os.path.splitext(os.path.basename(in_file)) - if fext == '.gz': - name, _ = os.path.splitext(name) - out_file = os.path.abspath('./%s_fill.nii.gz' % name) - nib.save(out_nii, out_file) - return out_file - - -def _vsm_remove_mean(in_file, mask_file, in_unwarped): - import nibabel as nib - import os - import numpy as np - import numpy.ma as ma - img = nib.load(in_file) - msk = nib.load(mask_file).get_data() - img_data = img.get_data() - img_data[msk == 0] = 0 - vsmmag_masked = ma.masked_values(img_data.reshape(-1), 0.0) - vsmmag_masked = vsmmag_masked - vsmmag_masked.mean() - img._data = vsmmag_masked.reshape(img.get_shape()) - name, fext = os.path.splitext(os.path.basename(in_file)) - if fext == '.gz': - name, _ = os.path.splitext(name) - out_file = os.path.abspath('./%s_demeaned.nii.gz' % name) - nib.save(img, out_file) - return out_file - - -def _ms2sec(val): - return val*1e-3; - - -def _split_dwi(in_file): - import nibabel as nib - import os - out_files = [] - frames = nib.funcs.four_to_three(nib.load(in_file)) - name, fext = os.path.splitext(os.path.basename(in_file)) - if fext == '.gz': - name, _ = os.path.splitext(name) - for i, frame in enumerate(frames): - out_file = os.path.abspath('./%s_%03d.nii.gz' % (name, i)) - nib.save(frame, out_file) - out_files.append(out_file) - return out_files From 4a372e23c2f7c106eb5f75e2323fbf8c7247aac4 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Fri, 29 Aug 2014 19:15:37 +0200 Subject: [PATCH 03/33] removed hardcoded tests --- .../dmri/preprocess/tests/test_epi.py | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/nipype/workflows/dmri/preprocess/tests/test_epi.py b/nipype/workflows/dmri/preprocess/tests/test_epi.py index 7ce5cb2ebf..065432faf6 100644 --- a/nipype/workflows/dmri/preprocess/tests/test_epi.py +++ b/nipype/workflows/dmri/preprocess/tests/test_epi.py @@ -10,35 +10,35 @@ import warnings import tempfile import shutil -from nipype.workflows.dmri.preprocess.epi import create_eddy_correct_pipeline - - -@skipif(no_fsl) -@skipif(no_fsl_course_data) -def test_create_eddy_correct_pipeline(): - fsl_course_dir = os.path.abspath('fsl_course_data') - - dwi_file = os.path.join(fsl_course_dir, "fdt/subj1/data.nii.gz") - - nipype_eddycorrect = create_eddy_correct_pipeline("nipype_eddycorrect") - nipype_eddycorrect.inputs.inputnode.in_file = dwi_file - nipype_eddycorrect.inputs.inputnode.ref_num = 0 - - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - original_eddycorrect = pe.Node(interface=fsl.EddyCorrect(), name="original_eddycorrect") - original_eddycorrect.inputs.in_file = dwi_file - original_eddycorrect.inputs.ref_num = 0 - - test = pe.Node(util.AssertEqual(), name="eddy_corrected_dwi_test") - - pipeline = pe.Workflow(name="test_eddycorrect") - pipeline.base_dir = tempfile.mkdtemp(prefix="nipype_test_eddycorrect_") - - pipeline.connect([(nipype_eddycorrect, test, [("outputnode.eddy_corrected", "volume1")]), - (original_eddycorrect, test, [("eddy_corrected", "volume2")]), - ]) - - pipeline.run(plugin='Linear') - shutil.rmtree(pipeline.base_dir) +from nipype.workflows.dmri.preprocess.epi import eddy_correct + + +#@skipif(no_fsl) +#@skipif(no_fsl_course_data) +#def test_eddy_correct(): +# fsl_course_dir = os.path.abspath('fsl_course_data') +# dwi_file = os.path.join(fsl_course_dir, "fdt/subj1/data.nii.gz") +# bval_file = os.path.join(fsl_course_dir, "fdt/subj1/bval.txt") +# +# ecc = eddy_correct() +# ecc.inputs.inputnode.in_file = dwi_file +# ecc.inputs.inputnode.in_bval = bval_file +# +# with warnings.catch_warnings(): +# warnings.simplefilter("ignore") +# original_eddycorrect = pe.Node(interface=fsl.EddyCorrect(), name="original_eddycorrect") +# original_eddycorrect.inputs.in_file = dwi_file +# original_eddycorrect.inputs.ref_num = 0 +# +# test = pe.Node(util.AssertEqual(), name="eddy_corrected_dwi_test") +# +# pipeline = pe.Workflow(name="test_eddycorrect") +# pipeline.base_dir = tempfile.mkdtemp(prefix="nipype_test_eddycorrect_") +# +# pipeline.connect([(nipype_eddycorrect, test, [("outputnode.eddy_corrected", "volume1")]), +# (original_eddycorrect, test, [("eddy_corrected", "volume2")]), +# ]) +# +# pipeline.run(plugin='Linear') +# shutil.rmtree(pipeline.base_dir) From f45ce74ce2db2f22150c9e1488abfe20d5f845bb Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Sat, 30 Aug 2014 16:10:49 +0200 Subject: [PATCH 04/33] enh:implementing new fmb workflow --- nipype/workflows/dmri/preprocess/__init__.py | 2 +- nipype/workflows/dmri/preprocess/epi.py | 262 +++++++++---------- nipype/workflows/dmri/setup.py | 1 + 3 files changed, 122 insertions(+), 143 deletions(-) diff --git a/nipype/workflows/dmri/preprocess/__init__.py b/nipype/workflows/dmri/preprocess/__init__.py index 29a0379ca3..a61edb1c44 100644 --- a/nipype/workflows/dmri/preprocess/__init__.py +++ b/nipype/workflows/dmri/preprocess/__init__.py @@ -1 +1 @@ -from epi import motion_correct +from epi import hmc_pipeline, ecc_pipeline, sdc_fmb diff --git a/nipype/workflows/dmri/preprocess/epi.py b/nipype/workflows/dmri/preprocess/epi.py index f7d6adefa1..3a215567f3 100644 --- a/nipype/workflows/dmri/preprocess/epi.py +++ b/nipype/workflows/dmri/preprocess/epi.py @@ -3,20 +3,26 @@ import nipype.pipeline.engine as pe import nipype.interfaces.utility as niu import nipype.interfaces.fsl as fsl +import nipype.interfaces.ants as ants import os -def motion_correct(name='motion_correct'): - """Creates a pipeline that corrects for head motion artifacts in dMRI sequences. +from .utils import * + +def hmc_pipeline(name='motion_correct'): + """ + HMC stands for head-motion correction. + + Creates a pipeline that corrects for head motion artifacts in dMRI sequences. It takes a series of diffusion weighted images and rigidly co-registers - them to one reference image. Finally, the `b`-matrix is rotated accordingly [1]_ + them to one reference image. Finally, the `b`-matrix is rotated accordingly [Leemans09]_ making use of the rotation matrix obtained by FLIRT. - Search angles have been limited to 3.5 degrees, based on results in [2]_. + Search angles have been limited to 3.5 degrees, based on results in [Yendiki13]_. A list of rigid transformation matrices is provided, so that transforms can be chained. This is useful to correct for artifacts with only one interpolation process (as previously discussed `here `_), - and also to compute nuisance regressors as proposed by [2]_. + and also to compute nuisance regressors as proposed by [Yendiki13]_. .. warning:: This workflow rotates the `b`-vectors, so please be advised that not all the dicom converters ensure the consistency between the resulting @@ -24,12 +30,13 @@ def motion_correct(name='motion_correct'): .. admonition:: References - .. [1] Leemans A, and Jones DK, Magn Reson Med. 2009 Jun;61(6):1336-49. - doi: 10.1002/mrm.21890. + .. [Leemans09] Leemans A, and Jones DK, `The B-matrix must be rotated when correcting + for subject motion in DTI data `_, + Magn Reson Med. 61(6):1336-49. 2009. doi: 10.1002/mrm.21890. - .. [2] Yendiki A et al., Spurious group differences due to head motion in a - diffusion MRI study. Neuroimage. 2013 Nov 21;88C:79-90. - doi: 10.1016/j.neuroimage.2013.11.027 + .. [Yendiki13] Yendiki A et al., `Spurious group differences due to head motion in + a diffusion MRI study `_. + Neuroimage. 21(88C):79-90. 2013. doi: 10.1016/j.neuroimage.2013.11.027 Example ------- @@ -91,14 +98,17 @@ def motion_correct(name='motion_correct'): ]) return wf -def eddy_correct(name='eddy_correct'): - """Creates a pipeline that corrects for artifacts induced by Eddy currents in dMRI +def ecc_pipeline(name='eddy_correct'): + """ + ECC stands for Eddy currents correction. + + Creates a pipeline that corrects for artifacts induced by Eddy currents in dMRI sequences. It takes a series of diffusion weighted images and linearly co-registers them to one reference image (the average of all b0s in the dataset). - DWIs are also modulated by the determinant of the Jacobian as indicated by [3]_ and - [4]_. + DWIs are also modulated by the determinant of the Jacobian as indicated by [Jones10]_ and + [Rohde04]_. A list of rigid transformation matrices can be provided, sourcing from a :func:`.motion_correct` workflow, to initialize registrations in a *motion free* @@ -110,12 +120,12 @@ def eddy_correct(name='eddy_correct'): .. admonition:: References - .. [3] Jones DK, `The signal intensity must be modulated by the determinant of \ - the Jacobian when correcting for eddy currents in diffusion MRI \ + .. [Jones10] Jones DK, `The signal intensity must be modulated by the determinant of + the Jacobian when correcting for eddy currents in diffusion MRI `_, Proc. ISMRM 18th Annual Meeting, (2010). - .. [4] Rohde et al., `Comprehensive Approach for Correction of Motion and Distortion \ + .. [Rohde04] Rohde et al., `Comprehensive Approach for Correction of Motion and Distortion \ in Diffusion-Weighted MRI \ `_, MRM 51:103-114 (2004). @@ -196,6 +206,99 @@ def eddy_correct(name='eddy_correct'): ]) return wf +def sdc_fmb(name='fmb_correction', + fugue_params=dict(smooth3d=2.0, despike_2dfilter=True), + bmap_params=dict(delta_te=2.46e-3), + epi_params=dict()): + """ + SDC stands for susceptibility distortion correction. FMB stands for fieldmap-based. + + The fieldmap based method (FMB) implements SDC by using a mapping of the B0 field + as proposed by [Jezzard95]_. This workflow uses the implementation of FSL + (`FUGUE `_). Phase unwrapping is performed + using `PRELUDE `_ + [Jenkinson03]_. + + + .. admonition:: References + + .. [Jezzard95] Jezzard P, and Balaban RS, `Correction for geometric distortion in echo + planar images from B0 field variations `_, + MRM 34(1):65-73. (1995). doi: 10.1002/mrm.1910340111. + + .. [Jenkinson03] Jenkinson M., `Fast, automated, N-dimensional phase-unwrapping algorithm + `_, MRM 49(1):193-197, 2003, doi: 10.1002/mrm.10354. + + """ + inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_bval', 'in_mask', + 'bmap_pha', 'bmap_mag']), + name='inputnode') + + + firstmag = pe.Node(fsl.ExtractROI(t_min=0, t_size=1), name='GetFirst') + n4 = pe.Node(ants.N4BiasFieldCorrection(dimension=3), name='Bias') + bet = pe.Node(fsl.BET(frac=0.4, mask=True), name='BrainExtraction') + dilate = pe.Node(fsl.maths.MathsCommand(nan2zeros=True, + args='-kernel sphere 5 -dilM'), name='MskDilate') + pha2rads = pe.Node(niu.Function(input_names=['in_file'], output_names=['out_file'], + function=siemens2rads), name='PreparePhase') + prelude = pe.Node(fsl.PRELUDE(process3d=True), name='PhaseUnwrap') + rad2rsec = pe.Node(niu.Function(input_names=['in_file', 'delta_te'], + output_names=['out_file'], function=rads2radsec), name='ToRadSec') + rad2rsec.inputs.delta_te = bmap_params['delta_te'] + + avg_b0 = pe.Node(niu.Function(input_names=['in_dwi', 'in_bval'], + output_names=['out_file'], function=b0_average), name='b0_avg') + + flirt = pe.Node(fsl.FLIRT(interp='spline', cost='normmi', cost_func='normmi', + dof=6, bins=64, save_log=True, padding_size=10, + searchr_x=[-4,4], searchr_y=[-4,4], searchr_z=[-4,4], + fine_search=1, coarse_search=10), + name='BmapMag2B0') + applyxfm = pe.Node(fsl.ApplyXfm(interp='spline', padding_size=10, apply_xfm=True), + name='BmapPha2B0') + + pre_fugue = pe.Node(fsl.FUGUE(save_fmap=True), name='PreliminaryFugue') + demean = pe.Node(niu.Function(input_names=['in_file', 'in_mask'], + output_names=['out_file'], function=demean_image), + name='DemeanFmap') + + cleanup = cleanup_edge_pipeline() + + wf = pe.Workflow(name=name) + wf.connect([ + (inputnode, pha2rads, [('bmap_pha', 'in_file')]) + ,(inputnode, firstmag, [('bmap_mag', 'in_file')]) + ,(inputnode, avg_b0, [('in_file', 'in_dwi'), + ('in_bval', 'in_bval')]) + ,(firstmag, n4, [('roi_file', 'input_image')]) + ,(n4, bet, [('output_image', 'in_file')]) + ,(bet, dilate, [('mask_file', 'in_file')]) + ,(pha2rads, prelude, [('out_file', 'phase_file')]) + ,(n4, prelude, [('output_image', 'magnitude_file')]) + ,(dilate, prelude, [('out_file', 'mask_file')]) + ,(prelude, rad2rsec, [('unwrapped_phase_file', 'in_file')]) + + ,(avg_b0, flirt, [('out_file', 'reference')]) + ,(inputnode, flirt, [('in_mask', 'ref_weight')]) + ,(n4, flirt, [('output_image', 'in_file')]) + ,(dilate, flirt, [('out_file', 'in_weight')]) + + ,(avg_b0, applyxfm, [('out_file', 'reference')]) + ,(rad2rsec, applyxfm, [('out_file', 'in_file')]) + ,(flirt, applyxfm, [('out_matrix_file', 'in_matrix_file')]) + + ,(applyxfm, pre_fugue, [('out_file', 'fmap_in_file')]) + ,(inputnode, pre_fugue, [('in_mask', 'mask_file')]) + + ,(pre_fugue, demean, [('fmap_out_file', 'in_file')]) + ,(inputnode, demean, [('in_mask', 'in_mask')]) + + ,(demean, cleanup, [('out_file', 'inputnode.in_file')]) + ,(inputnode, cleanup, [('in_mask', 'inputnode.in_mask')]) + ]) + return wf + def _checkrnum(ref_num): from nipype.interfaces.base import isdefined @@ -226,132 +329,7 @@ def _nonb0(in_bval): bvals = np.loadtxt(in_bval) return np.where(bvals!=0)[0].tolist() -def recompose_dwi(in_dwi, in_bval, in_corrected, out_file=None): - """ - Recompose back the dMRI data accordingly the b-values table after EC correction - """ - import numpy as np - import nibabel as nb - import os.path as op - - if out_file is None: - fname,ext = op.splitext(op.basename(in_dwi)) - if ext == ".gz": - fname,ext2 = op.splitext(fname) - ext = ext2 + ext - out_file = op.abspath("%s_eccorrect%s" % (fname, ext)) - - im = nb.load(in_dwi) - dwidata = im.get_data() - bvals = np.loadtxt(in_bval) - non_b0 = np.where(bvals!=0)[0].tolist() - - if len(non_b0)!=len(in_corrected): - raise RuntimeError('Length of DWIs in b-values table and after correction should match') - - for bindex, dwi in zip(non_b0, in_corrected): - dwidata[...,bindex] = nb.load(dwi).get_data() - - nb.Nifti1Image(dwidata, im.get_affine(), im.get_header()).to_filename(out_file) - return out_file - -def recompose_xfm(in_bval, in_xfms): - """ - Insert identity transformation matrices in b0 volumes to build up a list - """ - import numpy as np - import os.path as op - - bvals = np.loadtxt(in_bval) - out_matrix = np.array([np.eye(4)] * len(bvals)) - xfms = iter([np.loadtxt(xfm) for xfm in in_xfms]) - out_files = [] - - for i, b in enumerate(bvals): - if b == 0.0: - mat = np.eye(4) - else: - mat = xfms.next() - - out_name = 'eccor_%04d.mat' % i - out_files.append(out_name) - np.savetxt(out_name, mat) - - return out_files - - def _xfm_jacobian(in_xfm): import numpy as np from math import fabs return [fabs(np.linalg.det(np.loadtxt(xfm))) for xfm in in_xfm] - - -def b0_average(in_dwi, in_bval, out_file=None): - """ - A function that averages the *b0* volumes from a DWI dataset. - - .. warning:: *b0* should be already registered (head motion artifact should - be corrected). - - """ - import numpy as np - import nibabel as nb - import os.path as op - - if out_file is None: - fname,ext = op.splitext(op.basename(in_dwi)) - if ext == ".gz": - fname,ext2 = op.splitext(fname) - ext = ext2 + ext - out_file = op.abspath("%s_avg_b0%s" % (fname, ext)) - - imgs = nb.four_to_three(nb.load(in_dwi)) - bval = np.loadtxt(in_bval) - - b0s = [] - - for bval, img in zip(bval, imgs): - if bval==0: - b0s.append(img.get_data()) - - b0 = np.average(np.array(b0s), axis=0) - - hdr = imgs[0].get_header().copy() - nii = nb.Nifti1Image(b0, imgs[0].get_affine(), hdr) - - nb.save(nii, out_file) - return out_file - - -def rotate_bvecs(in_bvec, in_matrix): - """ - Rotates the input bvec file accordingly with a list of matrices. - - .. note:: the input affine matrix transforms points in the destination image to their \ - corresponding coordinates in the original image. Therefore, this matrix should be inverted \ - first, as we want to know the target position of :math:`\\vec{r}`. - - """ - import os - import numpy as np - - name, fext = os.path.splitext(os.path.basename(in_bvec)) - if fext == '.gz': - name, _ = os.path.splitext(name) - out_file = os.path.abspath('./%s_rotated.bvec' % name) - bvecs = np.loadtxt(in_bvec).T - new_bvecs = [] - - if len(bvecs) != len(in_matrix): - raise RuntimeError('Number of b-vectors and rotation matrices should match.') - - for bvec, mat in zip(bvecs, in_matrix): - if np.all(bvec==0.0): - new_bvecs.append(bvec) - else: - invrot = np.linalg.inv(np.loadtxt(mat))[:3,:3] - newbvec = invrot.dot(bvec) - new_bvecs.append((newbvec/np.linalg.norm(newbvec))) - - np.savetxt(out_file, np.array(new_bvecs).T, fmt='%0.15f') - return out_file diff --git a/nipype/workflows/dmri/setup.py b/nipype/workflows/dmri/setup.py index 599494d5b0..f4112813ca 100644 --- a/nipype/workflows/dmri/setup.py +++ b/nipype/workflows/dmri/setup.py @@ -9,6 +9,7 @@ def configuration(parent_package='',top_path=None): config.add_subpackage('mrtrix') config.add_subpackage('fsl') config.add_subpackage('connectivity') + config.add_subpackage('preprocess') return config From 576feff1fe3ea86bc0daf2b8ce75822658345855 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Sat, 30 Aug 2014 16:11:32 +0200 Subject: [PATCH 05/33] add preprocessing utils --- nipype/workflows/dmri/preprocess/utils.py | 255 ++++++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 nipype/workflows/dmri/preprocess/utils.py diff --git a/nipype/workflows/dmri/preprocess/utils.py b/nipype/workflows/dmri/preprocess/utils.py new file mode 100644 index 0000000000..703eb84cb5 --- /dev/null +++ b/nipype/workflows/dmri/preprocess/utils.py @@ -0,0 +1,255 @@ +#!/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: 2014-08-30 10:53:13 +# @Last Modified by: oesteban +# @Last Modified time: 2014-08-30 16:06:15 + +def cleanup_edge_pipeline(name='Cleanup'): + """ + Perform some de-spiking filtering to clean up the edge of the fieldmap + (copied from fsl_prepare_fieldmap) + """ + import nipype.pipeline.engine as pe + import nipype.interfaces.utility as niu + import nipype.interfaces.fsl as fsl + + inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_mask']), + name='inputnode') + outputnode = pe.Node(niu.IdentityInterface(fields=['out_file']), + name='outputnode') + + fugue = pe.Node(fsl.FUGUE(save_fmap=True, despike_2dfilter=True, despike_threshold=2.1), + name='Despike') + erode = pe.Node(fsl.maths.MathsCommand(nan2zeros=True, + args='-kernel 2D -ero'), name='MskErode') + newmsk = pe.Node(fsl.MultiImageMaths(op_string='-sub %s -thr 0.5 -bin'), + name='NewMask') + applymsk = pe.Node(fsl.ApplyMask(nan2zeros=True), name='ApplyMask') + join = pe.Node(niu.Merge(2), name='Merge') + addedge = pe.Node(fsl.MultiImageMaths(op_string='-mas %s -add %s'), + name='AddEdge') + + wf = pe.Workflow(name=name) + wf.connect([ + (inputnode, fugue, [('in_file', 'fmap_in_file'), + ('in_mask', 'mask_file')]) + ,(inputnode, erode, [('in_mask', 'in_file')]) + ,(inputnode, newmsk, [('in_mask', 'in_file')]) + ,(erode, newmsk, [('out_file', 'operand_files')]) + ,(fugue, applymsk, [('fmap_out_file', 'in_file')]) + ,(newmsk, applymsk, [('out_file', 'mask_file')]) + ,(erode, join, [('out_file', 'in1')]) + ,(applymsk, join, [('out_file', 'in2')]) + ,(inputnode, addedge, [('in_file', 'in_file')]) + ,(join, addedge, [('out', 'operand_files')]) + ,(addedge, outputnode, [('out_file', 'out_file')]) + ]) + return wf + + +def recompose_dwi(in_dwi, in_bval, in_corrected, out_file=None): + """ + Recompose back the dMRI data accordingly the b-values table after EC correction + """ + import numpy as np + import nibabel as nb + import os.path as op + + if out_file is None: + fname,ext = op.splitext(op.basename(in_dwi)) + if ext == ".gz": + fname,ext2 = op.splitext(fname) + ext = ext2 + ext + out_file = op.abspath("%s_eccorrect%s" % (fname, ext)) + + im = nb.load(in_dwi) + dwidata = im.get_data() + bvals = np.loadtxt(in_bval) + non_b0 = np.where(bvals!=0)[0].tolist() + + if len(non_b0)!=len(in_corrected): + raise RuntimeError('Length of DWIs in b-values table and after correction should match') + + for bindex, dwi in zip(non_b0, in_corrected): + dwidata[...,bindex] = nb.load(dwi).get_data() + + nb.Nifti1Image(dwidata, im.get_affine(), im.get_header()).to_filename(out_file) + return out_file + +def recompose_xfm(in_bval, in_xfms): + """ + Insert identity transformation matrices in b0 volumes to build up a list + """ + import numpy as np + import os.path as op + + bvals = np.loadtxt(in_bval) + out_matrix = np.array([np.eye(4)] * len(bvals)) + xfms = iter([np.loadtxt(xfm) for xfm in in_xfms]) + out_files = [] + + for i, b in enumerate(bvals): + if b == 0.0: + mat = np.eye(4) + else: + mat = xfms.next() + + out_name = 'eccor_%04d.mat' % i + out_files.append(out_name) + np.savetxt(out_name, mat) + + return out_files + + +def b0_average(in_dwi, in_bval, out_file=None): + """ + A function that averages the *b0* volumes from a DWI dataset. + + .. warning:: *b0* should be already registered (head motion artifact should + be corrected). + + """ + import numpy as np + import nibabel as nb + import os.path as op + + if out_file is None: + fname,ext = op.splitext(op.basename(in_dwi)) + if ext == ".gz": + fname,ext2 = op.splitext(fname) + ext = ext2 + ext + out_file = op.abspath("%s_avg_b0%s" % (fname, ext)) + + imgs = np.array(nb.four_to_three(nb.load(in_dwi))) + bval = np.loadtxt(in_bval) + b0s = [im.get_data().astype(np.float32) for im in imgs[np.where(bval==0)]] + b0 = np.average(np.array(b0s), axis=0) + + hdr = imgs[0].get_header().copy() + hdr.set_data_shape(b0.shape) + hdr.set_xyzt_units('mm') + hdr.set_data_dtype(np.float32) + nb.Nifti1Image(b0, imgs[0].get_affine(), hdr).to_filename(out_file) + return out_file + + +def rotate_bvecs(in_bvec, in_matrix): + """ + Rotates the input bvec file accordingly with a list of matrices. + + .. note:: the input affine matrix transforms points in the destination image to their \ + corresponding coordinates in the original image. Therefore, this matrix should be inverted \ + first, as we want to know the target position of :math:`\\vec{r}`. + + """ + import os + import numpy as np + + name, fext = os.path.splitext(os.path.basename(in_bvec)) + if fext == '.gz': + name, _ = os.path.splitext(name) + out_file = os.path.abspath('./%s_rotated.bvec' % name) + bvecs = np.loadtxt(in_bvec).T + new_bvecs = [] + + if len(bvecs) != len(in_matrix): + raise RuntimeError('Number of b-vectors and rotation matrices should match.') + + for bvec, mat in zip(bvecs, in_matrix): + if np.all(bvec==0.0): + new_bvecs.append(bvec) + else: + invrot = np.linalg.inv(np.loadtxt(mat))[:3,:3] + newbvec = invrot.dot(bvec) + new_bvecs.append((newbvec/np.linalg.norm(newbvec))) + + np.savetxt(out_file, np.array(new_bvecs).T, fmt='%0.15f') + return out_file + +def siemens2rads(in_file, out_file=None): + """ + Converts input phase difference map to rads + """ + import numpy as np + import nibabel as nb + import os.path as op + import math + + if out_file is None: + fname, fext = op.splitext(op.basename(in_file)) + if fext == '.gz': + fname, _ = op.splitext(fname) + out_file = op.abspath('./%s_rads.nii.gz' % fname) + + in_file = np.atleast_1d(in_file).tolist() + im = nb.load(in_file[0]) + data = im.get_data().astype(np.float32) + hdr = im.get_header().copy() + + if len(in_file) == 2: + data = data - nb.load(in_file[1]).get_data().astype(np.float32) + elif (data.ndim == 4) and (data.shape[-1] == 2): + data = np.squeeze(data[...,0] - data[...,1]) + hdr.set_data_shape(data.shape[:3]) + + imin = data.min() + imax = data.max() + data = (2.0 * math.pi * (data - imin)/(imax-imin)) - math.pi + hdr.set_data_dtype(np.float32) + hdr.set_xyzt_units('mm') + hdr['datatype'] = 16 + nb.Nifti1Image(data, im.get_affine(), hdr).to_filename(out_file) + return out_file + +def rads2radsec(in_file, delta_te, out_file=None): + """ + Converts input phase difference map to rads + """ + import numpy as np + import nibabel as nb + import os.path as op + import math + + if out_file is None: + fname, fext = op.splitext(op.basename(in_file)) + if fext == '.gz': + fname, _ = op.splitext(fname) + out_file = op.abspath('./%s_radsec.nii.gz' % fname) + + im = nb.load(in_file) + data = im.get_data().astype(np.float32) * (1.0/delta_te) + nb.Nifti1Image(data, im.get_affine(), + im.get_header()).to_filename(out_file) + return out_file + +def demean_image(in_file, in_mask=None, out_file=None): + """ + Demean image data inside mask + """ + import numpy as np + import nibabel as nb + import os.path as op + import math + + if out_file is None: + fname, fext = op.splitext(op.basename(in_file)) + if fext == '.gz': + fname, _ = op.splitext(fname) + out_file = op.abspath('./%s_demean.nii.gz' % fname) + + im = nb.load(in_file) + data = im.get_data().astype(np.float32) + msk = np.ones_like(data) + + if not in_mask is None: + msk = nb.load(in_mask).get_data().astype(np.float32) + msk[msk>0] = 1.0 + msk[msk<1] = 0.0 + + mean = np.median(data[msk==1].reshape(-1)) + data[msk==1] = data[msk==1] - mean + nb.Nifti1Image(data, im.get_affine(), im.get_header()).to_filename(out_file) + return out_file From aea5ee87990232a32e7bd52766befde88d07d950 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Sat, 30 Aug 2014 19:21:58 +0200 Subject: [PATCH 06/33] enh:new fmb_sdc workflow --- nipype/workflows/dmri/preprocess/epi.py | 90 +++++++++++++++-------- nipype/workflows/dmri/preprocess/utils.py | 28 ++++++- 2 files changed, 84 insertions(+), 34 deletions(-) diff --git a/nipype/workflows/dmri/preprocess/epi.py b/nipype/workflows/dmri/preprocess/epi.py index 3a215567f3..ef192ea5b6 100644 --- a/nipype/workflows/dmri/preprocess/epi.py +++ b/nipype/workflows/dmri/preprocess/epi.py @@ -76,6 +76,8 @@ def hmc_pipeline(name='motion_correct'): rot_bvec = pe.Node(niu.Function(input_names=['in_bvec', 'in_matrix'], output_names=['out_file'], function=rotate_bvecs), name='Rotate_Bvec') + thres = pe.MapNode(fsl.Threshold(thresh=0.0), iterfield=['in_file'], + name='RemoveNegative') merge = pe.Node(fsl.Merge(dimension='t'), name='MergeDWIs') outputnode = pe.Node(niu.IdentityInterface(fields=['out_file', 'out_bvec', 'out_xfms']), @@ -91,7 +93,8 @@ def hmc_pipeline(name='motion_correct'): ,(inputnode, rot_bvec, [('in_bvec', 'in_bvec')]) ,(flirt, rot_bvec, [('out_matrix_file', 'in_matrix')]) ,(pick_ref, flirt, [('out', 'reference')]) - ,(flirt, merge, [('out_file', 'in_files')]) + ,(flirt, thres, [('out_file', 'in_file')]) + ,(thres, merge, [('out_file', 'in_files')]) ,(merge, outputnode, [('merged_file', 'out_file')]) ,(rot_bvec, outputnode, [('out_file', 'out_bvec')]) ,(flirt, outputnode, [('out_matrix_file', 'out_xfms')]) @@ -207,9 +210,11 @@ def ecc_pipeline(name='eddy_correct'): return wf def sdc_fmb(name='fmb_correction', - fugue_params=dict(smooth3d=2.0, despike_2dfilter=True), + fugue_params=dict(smooth3d=2.0), bmap_params=dict(delta_te=2.46e-3), - epi_params=dict()): + epi_params=dict(echospacing=0.77e-3, + acc_factor=3, + enc_dir='y-')): """ SDC stands for susceptibility distortion correction. FMB stands for fieldmap-based. @@ -233,6 +238,8 @@ def sdc_fmb(name='fmb_correction', inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_bval', 'in_mask', 'bmap_pha', 'bmap_mag']), name='inputnode') + outputnode = pe.Node(niu.IdentityInterface(fields=['out_file', 'out_vsm']), + name='outputnode') firstmag = pe.Node(fsl.ExtractROI(t_min=0, t_size=1), name='GetFirst') @@ -265,37 +272,58 @@ def sdc_fmb(name='fmb_correction', cleanup = cleanup_edge_pipeline() + addvol = pe.Node(niu.Function(input_names=['in_file'], output_names=['out_file'], + function=add_empty_vol), name='AddEmptyVol') + + vsm = pe.Node(fsl.FUGUE(save_shift=True, **fugue_params), + name="ComputeVSM") + vsm.inputs.asym_se_time = bmap_params['delta_te'] + vsm.inputs.dwell_time = epi_params['echospacing'] / (1.0 * epi_params['acc_factor']) + + split = pe.Node(fsl.Split(dimension='t'), name='SplitDWIs') + merge = pe.Node(fsl.Merge(dimension='t'), name='MergeDWIs') + unwarp = pe.MapNode(fsl.FUGUE(icorr=True, forward_warping=False), + iterfield=['in_file'], name='UnwarpDWIs') + unwarp.inputs.unwarp_direction=epi_params['enc_dir'] + thres = pe.MapNode(fsl.Threshold(thresh=0.0), iterfield=['in_file'], + name='RemoveNegative') + wf = pe.Workflow(name=name) wf.connect([ - (inputnode, pha2rads, [('bmap_pha', 'in_file')]) - ,(inputnode, firstmag, [('bmap_mag', 'in_file')]) - ,(inputnode, avg_b0, [('in_file', 'in_dwi'), + (inputnode, pha2rads, [('bmap_pha', 'in_file')]) + ,(inputnode, firstmag, [('bmap_mag', 'in_file')]) + ,(inputnode, avg_b0, [('in_file', 'in_dwi'), ('in_bval', 'in_bval')]) - ,(firstmag, n4, [('roi_file', 'input_image')]) - ,(n4, bet, [('output_image', 'in_file')]) - ,(bet, dilate, [('mask_file', 'in_file')]) - ,(pha2rads, prelude, [('out_file', 'phase_file')]) - ,(n4, prelude, [('output_image', 'magnitude_file')]) - ,(dilate, prelude, [('out_file', 'mask_file')]) - ,(prelude, rad2rsec, [('unwrapped_phase_file', 'in_file')]) - - ,(avg_b0, flirt, [('out_file', 'reference')]) - ,(inputnode, flirt, [('in_mask', 'ref_weight')]) - ,(n4, flirt, [('output_image', 'in_file')]) - ,(dilate, flirt, [('out_file', 'in_weight')]) - - ,(avg_b0, applyxfm, [('out_file', 'reference')]) - ,(rad2rsec, applyxfm, [('out_file', 'in_file')]) - ,(flirt, applyxfm, [('out_matrix_file', 'in_matrix_file')]) - - ,(applyxfm, pre_fugue, [('out_file', 'fmap_in_file')]) - ,(inputnode, pre_fugue, [('in_mask', 'mask_file')]) - - ,(pre_fugue, demean, [('fmap_out_file', 'in_file')]) - ,(inputnode, demean, [('in_mask', 'in_mask')]) - - ,(demean, cleanup, [('out_file', 'inputnode.in_file')]) - ,(inputnode, cleanup, [('in_mask', 'inputnode.in_mask')]) + ,(firstmag, n4, [('roi_file', 'input_image')]) + ,(n4, bet, [('output_image', 'in_file')]) + ,(bet, dilate, [('mask_file', 'in_file')]) + ,(pha2rads, prelude, [('out_file', 'phase_file')]) + ,(n4, prelude, [('output_image', 'magnitude_file')]) + ,(dilate, prelude, [('out_file', 'mask_file')]) + ,(prelude, rad2rsec, [('unwrapped_phase_file', 'in_file')]) + ,(avg_b0, flirt, [('out_file', 'reference')]) + ,(inputnode, flirt, [('in_mask', 'ref_weight')]) + ,(n4, flirt, [('output_image', 'in_file')]) + ,(dilate, flirt, [('out_file', 'in_weight')]) + ,(avg_b0, applyxfm, [('out_file', 'reference')]) + ,(rad2rsec, applyxfm, [('out_file', 'in_file')]) + ,(flirt, applyxfm, [('out_matrix_file', 'in_matrix_file')]) + ,(applyxfm, pre_fugue, [('out_file', 'fmap_in_file')]) + ,(inputnode, pre_fugue, [('in_mask', 'mask_file')]) + ,(pre_fugue, demean, [('fmap_out_file', 'in_file')]) + ,(inputnode, demean, [('in_mask', 'in_mask')]) + ,(demean, cleanup, [('out_file', 'inputnode.in_file')]) + ,(inputnode, cleanup, [('in_mask', 'inputnode.in_mask')]) + ,(cleanup, addvol, [('outputnode.out_file', 'in_file')]) + ,(inputnode, vsm, [('in_mask', 'mask_file')]) + ,(addvol, vsm, [('out_file', 'fmap_in_file')]) + ,(inputnode, split, [('in_file', 'in_file')]) + ,(split, unwarp, [('out_files', 'in_file')]) + ,(vsm, unwarp, [('shift_out_file', 'shift_in_file')]) + ,(unwarp, thres, [('unwarped_file', 'in_file')]) + ,(thres, merge, [('out_file', 'in_files')]) + ,(merge, outputnode, [('merged_file', 'out_file')]) + ,(vsm, outputnode, [('shift_out_file', 'out_vsm')]) ]) return wf diff --git a/nipype/workflows/dmri/preprocess/utils.py b/nipype/workflows/dmri/preprocess/utils.py index 703eb84cb5..04247372df 100644 --- a/nipype/workflows/dmri/preprocess/utils.py +++ b/nipype/workflows/dmri/preprocess/utils.py @@ -5,7 +5,7 @@ # @Author: oesteban # @Date: 2014-08-30 10:53:13 # @Last Modified by: oesteban -# @Last Modified time: 2014-08-30 16:06:15 +# @Last Modified time: 2014-08-30 18:54:43 def cleanup_edge_pipeline(name='Cleanup'): """ @@ -190,9 +190,9 @@ def siemens2rads(in_file, out_file=None): hdr = im.get_header().copy() if len(in_file) == 2: - data = data - nb.load(in_file[1]).get_data().astype(np.float32) + data = nb.load(in_file[1]).get_data().astype(np.float32) - data elif (data.ndim == 4) and (data.shape[-1] == 2): - data = np.squeeze(data[...,0] - data[...,1]) + data = np.squeeze(data[...,1] - data[...,0]) hdr.set_data_shape(data.shape[:3]) imin = data.min() @@ -253,3 +253,25 @@ def demean_image(in_file, in_mask=None, out_file=None): data[msk==1] = data[msk==1] - mean nb.Nifti1Image(data, im.get_affine(), im.get_header()).to_filename(out_file) return out_file + + +def add_empty_vol(in_file, out_file=None): + """ + Adds an empty vol to the phase difference image + """ + import nibabel as nb + import os.path as op + import numpy as np + import math + + if out_file is None: + fname, fext = op.splitext(op.basename(in_file)) + if fext == '.gz': + fname, _ = op.splitext(fname) + out_file = op.abspath('./%s_4D.nii.gz' % fname) + + im = nb.load(in_file) + zim = nb.Nifti1Image(np.zeros_like(im.get_data()), im.get_affine(), + im.get_header()) + nb.funcs.concat_images([im, zim]).to_filename(out_file) + return out_file From 41d0f19a10c4e9a5eb394748ce9cd18106d786ca Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Sat, 30 Aug 2014 19:26:00 +0200 Subject: [PATCH 07/33] enh:new fmb_sdc workflow --- nipype/workflows/dmri/preprocess/epi.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nipype/workflows/dmri/preprocess/epi.py b/nipype/workflows/dmri/preprocess/epi.py index ef192ea5b6..93e3b877c8 100644 --- a/nipype/workflows/dmri/preprocess/epi.py +++ b/nipype/workflows/dmri/preprocess/epi.py @@ -222,8 +222,10 @@ def sdc_fmb(name='fmb_correction', as proposed by [Jezzard95]_. This workflow uses the implementation of FSL (`FUGUE `_). Phase unwrapping is performed using `PRELUDE `_ - [Jenkinson03]_. + [Jenkinson03]_. Preparation of the fieldmap is performed reproducing the script in FSL + `fsl_prepare_fieldmap `_. + .. warning:: Only SIEMENS format fieldmaps are supported. .. admonition:: References From b9f1c25efcbeb0f4366139d41fa80cc5fa9eda7c Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Sun, 31 Aug 2014 11:30:00 +0200 Subject: [PATCH 08/33] fix:doctests of new workflows --- nipype/workflows/dmri/preprocess/epi.py | 26 +++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/nipype/workflows/dmri/preprocess/epi.py b/nipype/workflows/dmri/preprocess/epi.py index 93e3b877c8..d57426c6cd 100644 --- a/nipype/workflows/dmri/preprocess/epi.py +++ b/nipype/workflows/dmri/preprocess/epi.py @@ -1,4 +1,6 @@ # 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: import nipype.pipeline.engine as pe import nipype.interfaces.utility as niu @@ -41,8 +43,8 @@ def hmc_pipeline(name='motion_correct'): Example ------- - >>> from nipype.workflows.dmri.fsl.epi import motion_correct - >>> hmc = motion_correct() + >>> from nipype.workflows.dmri.preprocess.epi import hmc_pipeline + >>> hmc = hmc_pipeline() >>> hmc.inputs.inputnode.in_file = 'diffusion.nii' >>> hmc.inputs.inputnode.in_bvec = 'diffusion.bvec' >>> hmc.inputs.inputnode.in_mask = 'mask.nii' @@ -128,15 +130,15 @@ def ecc_pipeline(name='eddy_correct'): `_, Proc. ISMRM 18th Annual Meeting, (2010). - .. [Rohde04] Rohde et al., `Comprehensive Approach for Correction of Motion and Distortion \ - in Diffusion-Weighted MRI \ + .. [Rohde04] Rohde et al., `Comprehensive Approach for Correction of Motion and Distortion + in Diffusion-Weighted MRI `_, MRM 51:103-114 (2004). Example ------- - >>> from nipype.workflows.dmri.fsl.epi import eddy_correct - >>> ecc = eddy_correct() + >>> from nipype.workflows.dmri.preprocess.epi import ecc_pipeline + >>> ecc = ecc_pipeline() >>> ecc.inputs.inputnode.in_file = 'diffusion.nii' >>> ecc.inputs.inputnode.in_bval = 'diffusion.bval' >>> ecc.inputs.inputnode.in_mask = 'mask.nii' @@ -225,6 +227,18 @@ def sdc_fmb(name='fmb_correction', [Jenkinson03]_. Preparation of the fieldmap is performed reproducing the script in FSL `fsl_prepare_fieldmap `_. + Example + ------- + + >>> from nipype.workflows.dmri.preprocess.epi import sdc_fmb + >>> fmb = sdc_fmb() + >>> fmb.inputs.inputnode.in_file = 'diffusion.nii' + >>> fmb.inputs.inputnode.in_bval = 'diffusion.bval' + >>> fmb.inputs.inputnode.in_mask = 'mask.nii' + >>> fmb.inputs.inputnode.bmap_mag = 'magnitude.nii' + >>> fmb.inputs.inputnode.bmap_pha = 'phase.nii' + >>> fmb.run() # doctest: +SKIP + .. warning:: Only SIEMENS format fieldmaps are supported. .. admonition:: References From 0ad073680747b0c62ba5a9676c3a6d4139d2a172 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Mon, 1 Sep 2014 18:27:42 +0200 Subject: [PATCH 09/33] enh:new all-corrections workflows and example When merged the new dipy interfaces I've written, this example should be updated adding a denoising node. --- examples/dmri_preprocessing.py | 147 +++++++++++++ nipype/workflows/dmri/preprocess/__init__.py | 3 +- nipype/workflows/dmri/preprocess/epi.py | 201 +++++++++++++++++- .../dmri/preprocess/tests/test_epi.py | 30 +-- 4 files changed, 364 insertions(+), 17 deletions(-) create mode 100644 examples/dmri_preprocessing.py diff --git a/examples/dmri_preprocessing.py b/examples/dmri_preprocessing.py new file mode 100644 index 0000000000..19fdee7b54 --- /dev/null +++ b/examples/dmri_preprocessing.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Author: oesteban +# @Date: 2014-08-31 20:32:22 +# @Last Modified by: oesteban +# @Last Modified time: 2014-09-01 18:12:14 +""" +=================== +dMRI: Preprocessing +=================== + +Introduction +============ + +This script, dmri_preprocessing.py, demonstrates how to prepare dMRI data +for tractography and connectivity analysis with nipype. + +We perform this analysis using the FSL course data, which can be acquired from here: +http://www.fmrib.ox.ac.uk/fslcourse/fsl_course_data2.tar.gz + +Can be executed in command line using ``python dmri_preprocessing.py`` + + +Import necessary modules from nipype. +""" + +import nipype.interfaces.io as nio # Data i/o +import nipype.interfaces.utility as util # utility +import nipype.pipeline.engine as pe # pypeline engine +import nipype.interfaces.fsl as fsl +import nipype.algorithms.misc as misc +import os # system functions + + +""" +Load specific nipype's workflows for preprocessing of dMRI data: +:class:`nipype.workflows.dmri.preprocess.epi.all_peb_pipeline`, +as data include a *b0* volume with reverse encoding direction +(*P>>>A*, or *y*), in contrast with the general acquisition encoding +that is *A>>>P* or *-y* (in RAS systems). +""" + +from nipype.workflows.dmri.preprocess.epi import all_peb_pipeline + + +""" +Map field names into individual subject runs +""" + +info = dict(dwi=[['subject_id', 'dwidata']], + bvecs=[['subject_id','bvecs']], + bvals=[['subject_id','bvals']], + dwi_rev=[['subject_id','nodif_PA']]) + +infosource = pe.Node(interface=util.IdentityInterface(fields=['subject_id']), + name="infosource") + +# Set the subject 1 identifier in subject_list, +# we choose the preproc dataset as it contains uncorrected files. +subject_list = ['subj1_preproc'] + + +"""Here we set up iteration over all the subjects. The following line +is a particular example of the flexibility of the system. The +``datasource`` attribute ``iterables`` tells the pipeline engine that +it should repeat the analysis on each of the items in the +``subject_list``. In the current example, the entire first level +preprocessing and estimation will be repeated for each subject +contained in subject_list. +""" + +infosource.iterables = ('subject_id', subject_list) + + +""" +Now we create a :class:`nipype.interfaces.io.DataGrabber` object and +fill in the information from above about the layout of our data. The +:class:`~nipype.pipeline.engine.Node` module wraps the interface object +and provides additional housekeeping and pipeline specific +functionality. +""" + +datasource = pe.Node(nio.DataGrabber(infields=['subject_id'], + outfields=info.keys()), name = 'datasource') + +datasource.inputs.template = "%s/%s" + +# This needs to point to the fdt folder you can find after extracting +# http://www.fmrib.ox.ac.uk/fslcourse/fsl_course_data2.tar.gz +datasource.inputs.base_directory = os.path.abspath('fdt1') +datasource.inputs.field_template = dict(dwi='%s/%s.nii.gz', dwi_rev='%s/%s.nii.gz') +datasource.inputs.template_args = info +datasource.inputs.sort_filelist = True + + +""" +An inputnode is used to pass the data obtained by the data grabber to the actual processing functions +""" + +inputnode = pe.Node(util.IdentityInterface(fields=["dwi", "bvecs", "bvals", + "dwi_rev"]), name="inputnode") + + +""" + +Setup for dMRI preprocessing +============================ + +In this section we initialize the appropriate workflow for preprocessing of diffusion images. +Particularly, we look into the ``acqparams.txt`` file of the selected subject to gather the +encoding direction, acceleration factor (in parallel sequences it is > 1), and readout time or +echospacing. + +""" + +epi_AP = {'echospacing': 66.5e-3, 'acc_factor': 1, 'enc_dir': 'y-'} +epi_PA = {'echospacing': 66.5e-3, 'acc_factor': 1, 'enc_dir': 'y'} +prep = all_peb_pipeline(epi_params=epi_AP, altepi_params=epi_PA) + + +""" +Connect nodes in workflow +========================= + +We create a higher level workflow to connect the nodes. Please excuse the author for +writing the arguments of the ``connect`` function in a not-standard fashion with readability +aims. +""" + +wf = pe.Workflow(name="dMRI_Preprocessing") +wf.base_dir = os.path.abspath('preprocessing_dmri_tutorial') +wf.connect([ + (infosource, datasource, [('subject_id', 'subject_id')]) + ,(datasource, prep, [('dwi', 'inputnode.in_file'), + ('dwi_rev', 'inputnode.alt_file'), + ('bvals', 'inputnode.in_bval'), + ('bvecs', 'inputnode.in_bvec')]) +]) + + +""" +Run the workflow as command line executable +""" + +if __name__ == '__main__': + wf.run() + wf.write_graph() diff --git a/nipype/workflows/dmri/preprocess/__init__.py b/nipype/workflows/dmri/preprocess/__init__.py index a61edb1c44..4b3dd5ff36 100644 --- a/nipype/workflows/dmri/preprocess/__init__.py +++ b/nipype/workflows/dmri/preprocess/__init__.py @@ -1 +1,2 @@ -from epi import hmc_pipeline, ecc_pipeline, sdc_fmb +from epi import (all_fmb_pipeline, all_peb_pipeline, + hmc_pipeline, ecc_pipeline, sdc_fmb, sdc_peb) diff --git a/nipype/workflows/dmri/preprocess/epi.py b/nipype/workflows/dmri/preprocess/epi.py index d57426c6cd..27acbe5b28 100644 --- a/nipype/workflows/dmri/preprocess/epi.py +++ b/nipype/workflows/dmri/preprocess/epi.py @@ -5,11 +5,126 @@ import nipype.pipeline.engine as pe import nipype.interfaces.utility as niu import nipype.interfaces.fsl as fsl +import nipype.interfaces.freesurfer as fs import nipype.interfaces.ants as ants import os from .utils import * + +def all_fmb_pipeline(name='hmc_sdc_ecc'): + """ + Builds a pipeline including three artifact corrections: head-motion correction (HMC), + susceptibility-derived distortion correction (SDC), and Eddy currents-derived distortion + correction (ECC). + """ + inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_bvec', 'in_bval', + 'bmap_pha', 'bmap_mag']), name='inputnode') + + outputnode = pe.Node(niu.IdentityInterface(fields=['out_file', 'out_mask', 'out_bvec']), + name='outputnode') + + + avg_b0_0 = pe.Node(niu.Function(input_names=['in_dwi', 'in_bval'], + output_names=['out_file'], function=b0_average), name='b0_avg_pre') + avg_b0_1 = pe.Node(niu.Function(input_names=['in_dwi', 'in_bval'], + output_names=['out_file'], function=b0_average), name='b0_avg_post') + bet_dwi0 = pe.Node(fsl.BET(frac=0.3, mask=True, robust=True), name='bet_dwi_pre') + bet_dwi1 = pe.Node(fsl.BET(frac=0.3, mask=True, robust=True), name='bet_dwi_post') + + hmc = hmc_pipeline() + sdc = sdc_fmb() + ecc = ecc_pipeline() + + regrid = pe.Node(fs.MRIConvert(vox_size=(2.0, 2.0, 2.0), out_orientation='RAS'), + name='Reslice') + + wf = pe.Workflow('dMRI_Artifacts') + wf.connect([ + (inputnode, hmc, [('in_file', 'inputnode.in_file'), + ('in_bvec', 'inputnode.in_bvec')]) + ,(inputnode, avg_b0_0, [('in_file', 'in_dwi'), + ('in_bval', 'in_bval')]) + ,(avg_b0_0, bet_dwi0, [('out_file','in_file')]) + ,(bet_dwi0, hmc, [('mask_file', 'inputnode.in_mask')]) + + ,(hmc, sdc, [('outputnode.out_file', 'inputnode.in_file')]) + ,(bet_dwi0, sdc, [('mask_file', 'inputnode.in_mask')]) + ,(inputnode, sdc, [('in_bval', 'inputnode.in_bval'), + ('bmap_pha', 'inputnode.bmap_pha'), + ('bmap_mag', 'inputnode.bmap_mag')]) + ,(inputnode, ecc, [('in_bval', 'inputnode.in_bval')]) + ,(bet_dwi0, ecc, [('mask_file', 'inputnode.in_mask')]) + ,(sdc, ecc, [('outputnode.out_file', 'inputnode.in_file')]) + ,(hmc, outputnode, [('outputnode.out_bvec', 'out_bvec')]) + ,(ecc, regrid, [('outputnode.out_file', 'in_file')]) + ,(regrid, outputnode, [('out_file', 'out_file')]) + ,(regrid, avg_b0_1, [('out_file', 'in_dwi')]) + ,(inputnode, avg_b0_1, [('in_bval', 'in_bval')]) + ,(avg_b0_1, bet_dwi1, [('out_file','in_file')]) + ,(bet_dwi1, outputnode, [('mask_file', 'out_mask')]) + ]) + return wf + + +def all_peb_pipeline(name='hmc_sdc_ecc', + epi_params=dict(echospacing=0.77e-3, + acc_factor=3, + enc_dir='y-'), + altepi_params=dict(echospacing=0.77e-3, + acc_factor=3, + enc_dir='y')): + """ + Builds a pipeline including three artifact corrections: head-motion correction (HMC), + susceptibility-derived distortion correction (SDC), and Eddy currents-derived distortion + correction (ECC). + """ + inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_bvec', 'in_bval', + 'alt_file']), name='inputnode') + + outputnode = pe.Node(niu.IdentityInterface(fields=['out_file', 'out_mask', 'out_bvec']), + name='outputnode') + + avg_b0_0 = pe.Node(niu.Function(input_names=['in_dwi', 'in_bval'], + output_names=['out_file'], function=b0_average), name='b0_avg_pre') + avg_b0_1 = pe.Node(niu.Function(input_names=['in_dwi', 'in_bval'], + output_names=['out_file'], function=b0_average), name='b0_avg_post') + bet_dwi0 = pe.Node(fsl.BET(frac=0.3, mask=True, robust=True), name='bet_dwi_pre') + bet_dwi1 = pe.Node(fsl.BET(frac=0.3, mask=True, robust=True), name='bet_dwi_post') + + hmc = hmc_pipeline() + sdc = sdc_peb(epi_params=epi_params, altepi_params=altepi_params) + ecc = ecc_pipeline() + + regrid = pe.Node(fs.MRIConvert(vox_size=(2.0, 2.0, 2.0), out_orientation='RAS'), + name='Reslice') + + wf = pe.Workflow('dMRI_Artifacts') + wf.connect([ + (inputnode, hmc, [('in_file', 'inputnode.in_file'), + ('in_bvec', 'inputnode.in_bvec')]) + ,(inputnode, avg_b0_0, [('in_file', 'in_dwi'), + ('in_bval', 'in_bval')]) + ,(avg_b0_0, bet_dwi0, [('out_file','in_file')]) + ,(bet_dwi0, hmc, [('mask_file', 'inputnode.in_mask')]) + ,(hmc, sdc, [('outputnode.out_file', 'inputnode.in_file')]) + ,(bet_dwi0, sdc, [('mask_file', 'inputnode.in_mask')]) + ,(inputnode, sdc, [('in_bval', 'inputnode.in_bval'), + ('alt_file', 'inputnode.alt_file')]) + ,(inputnode, ecc, [('in_bval', 'inputnode.in_bval')]) + ,(bet_dwi0, ecc, [('mask_file', 'inputnode.in_mask')]) + ,(sdc, ecc, [('outputnode.out_file', 'inputnode.in_file')]) + ,(hmc, outputnode, [('outputnode.out_bvec', 'out_bvec')]) + ,(ecc, regrid, [('outputnode.out_file', 'in_file')]) + ,(regrid, outputnode, [('out_file', 'out_file')]) + ,(regrid, avg_b0_1, [('out_file', 'in_dwi')]) + ,(inputnode, avg_b0_1, [('in_bval', 'in_bval')]) + ,(avg_b0_1, bet_dwi1, [('out_file','in_file')]) + ,(bet_dwi1, outputnode, [('mask_file', 'out_mask')]) + ]) + return wf + + def hmc_pipeline(name='motion_correct'): """ HMC stands for head-motion correction. @@ -116,7 +231,7 @@ def ecc_pipeline(name='eddy_correct'): [Rohde04]_. A list of rigid transformation matrices can be provided, sourcing from a - :func:`.motion_correct` workflow, to initialize registrations in a *motion free* + :func:`.hmc_pipeline` workflow, to initialize registrations in a *motion free* framework. A list of affine transformation matrices is available as output, so that transforms @@ -344,6 +459,90 @@ def sdc_fmb(name='fmb_correction', return wf +def sdc_peb(name='peb_correction', + epi_params=dict(echospacing=0.77e-3, + acc_factor=3, + enc_dir='y-'), + altepi_params=dict(echospacing=0.77e-3, + acc_factor=3, + enc_dir='y')): + """ + SDC stands for susceptibility distortion correction. PEB stands for phase-encoding-based. + + The phase-encoding-based (PEB) method implements SDC by acquiring diffusion images with + two different enconding directions [Andersson2003]_. The most typical case is acquiring + with opposed phase-gradient blips (e.g. *A>>>P* and *P>>>A*, or equivalently, *-y* and *y*) + as in [Chiou2000]_, but it is also possible to use orthogonal configurations [Cordes2000]_ + (e.g. *A>>>P* and *L>>>R*, or equivalently *-y* and *x*). + This workflow uses the implementation of FSL + (`TOPUP `_). + + Example + ------- + + >>> from nipype.workflows.dmri.preprocess.epi import sdc_peb + >>> peb = sdc_peb() + >>> peb.inputs.inputnode.in_file = 'diffusion.nii' + >>> peb.inputs.inputnode.in_bval = 'diffusion.bval' + >>> peb.inputs.inputnode.in_mask = 'mask.nii' + >>> peb.run() # doctest: +SKIP + + .. admonition:: References + + .. [Andersson2003] Andersson JL et al., `How to correct susceptibility distortions in + spin-echo echo-planar images: application to diffusion tensor imaging + `_. + Neuroimage. 2003 Oct;20(2):870-88. doi: 10.1016/S1053-8119(03)00336-7 + + .. [Cordes2000] Cordes D et al., Geometric distortion correction in EPI using two + images with orthogonal phase-encoding directions, in Proc. ISMRM (8), p.1712, + Denver, US, 2000. + + .. [Chiou2000] Chiou JY, and Nalcioglu O, A simple method to correct off-resonance + related distortion in echo planar imaging, in Proc. ISMRM (8), p.1712, Denver, US, + 2000. + + """ + + inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_bval', 'in_mask', + 'alt_file', 'ref_num']), + name='inputnode') + outputnode = pe.Node(niu.IdentityInterface(fields=['out_file', 'out_vsm']), + name='outputnode') + + b0_ref = pe.Node(fsl.ExtractROI(t_size=1), name='b0_ref') + b0_alt = pe.Node(fsl.ExtractROI(t_size=1), name='b0_alt') + b0_comb = pe.Node(niu.Merge(2), name='b0_list') + b0_merge = pe.Node(fsl.Merge(dimension='t'), name='b0_merged') + + topup = pe.Node(fsl.TOPUP(), name='topup') + topup.inputs.encoding_direction = [epi_params['enc_dir'], altepi_params['enc_dir']] + topup.inputs.readout_times = [epi_params['echospacing']/(1.0*epi_params['acc_factor']), + altepi_params['echospacing']/(1.0*altepi_params['acc_factor'])] + unwarp = pe.Node(fsl.ApplyTOPUP(in_index=[1,2]), name='unwarp') + dwi_comb = pe.Node(niu.Merge(2), name='DWIcomb') + + wf = pe.Workflow(name=name) + wf.connect([ + (inputnode, b0_ref, [('in_file','in_file'), + (('ref_num', _checkrnum),'t_min')]) + ,(inputnode, b0_alt, [('alt_file','in_file'), + (('ref_num', _checkrnum),'t_min')]) + ,(inputnode, dwi_comb, [('in_file','in1'), + ('alt_file','in2') ] ) + ,(b0_ref, b0_comb, [('roi_file','in1')] ) + ,(b0_alt, b0_comb, [('roi_file','in2')] ) + ,(b0_comb, b0_merge, [('out', 'in_files')] ) + ,(b0_merge, topup, [('merged_file','in_file')]) + ,(topup, unwarp, [('out_fieldcoef','in_topup_fieldcoef'), + ('out_movpar','in_topup_movpar'), + ('out_enc_file','encoding_file')]) + ,(dwi_comb, unwarp, [('out','in_files')]) + ,(unwarp, outputnode, [('out_corrected','out_file')]) + ]) + return wf + + def _checkrnum(ref_num): from nipype.interfaces.base import isdefined if (ref_num is None) or not isdefined(ref_num): diff --git a/nipype/workflows/dmri/preprocess/tests/test_epi.py b/nipype/workflows/dmri/preprocess/tests/test_epi.py index 065432faf6..a5bc06c761 100644 --- a/nipype/workflows/dmri/preprocess/tests/test_epi.py +++ b/nipype/workflows/dmri/preprocess/tests/test_epi.py @@ -1,18 +1,18 @@ -import os - -from nipype.testing import (skipif) -import nipype.workflows.fmri.fsl as fsl_wf -import nipype.interfaces.fsl as fsl -import nipype.interfaces.utility as util -from nipype.interfaces.fsl import no_fsl, no_fsl_course_data - -import nipype.pipeline.engine as pe -import warnings -import tempfile -import shutil -from nipype.workflows.dmri.preprocess.epi import eddy_correct - - +#import os +# +#from nipype.testing import (skipif) +#import nipype.workflows.fmri.fsl as fsl_wf +#import nipype.interfaces.fsl as fsl +#import nipype.interfaces.utility as util +#from nipype.interfaces.fsl import no_fsl, no_fsl_course_data +# +#import nipype.pipeline.engine as pe +#import warnings +#import tempfile +#import shutil +#from nipype.workflows.dmri.preprocess.epi import eddy_correct +# +# #@skipif(no_fsl) #@skipif(no_fsl_course_data) #def test_eddy_correct(): From 3efd8843f677a1676516904b20396754297a5e0a Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Mon, 1 Sep 2014 19:29:49 +0200 Subject: [PATCH 10/33] multiple fixes and new features * ApplyTopup is now called with correct arguments when the reversed encoding image is only one b0 * Added documentation * Deprecated old workflows in nipype.workflows.dmri.fsl (added warnings in both documentation and code) * Updated CHANGES --- CHANGES | 2 + nipype/workflows/dmri/fsl/epi.py | 82 +++++++++++++++++++++---- nipype/workflows/dmri/preprocess/epi.py | 13 ++-- 3 files changed, 76 insertions(+), 21 deletions(-) diff --git a/CHANGES b/CHANGES index db27b05bd1..a80a6ba192 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,8 @@ Next Release ============ +* ENH: Deep revision of worflows for correction of dMRI artifacts. New dmri_preprocessing + example. * ENH: New Freesurfer interface: MRIPretess * ENH: New miscelaneous interface: AddCSVRow * ENH: FUGUE interface has been refactored to use the name_template system, 3 examples diff --git a/nipype/workflows/dmri/fsl/epi.py b/nipype/workflows/dmri/fsl/epi.py index 00769871f5..2d1da79ca4 100644 --- a/nipype/workflows/dmri/fsl/epi.py +++ b/nipype/workflows/dmri/fsl/epi.py @@ -4,17 +4,22 @@ import nipype.interfaces.utility as niu import nipype.interfaces.fsl as fsl import os +import warnings def create_dmri_preprocessing(name='dMRI_preprocessing', use_fieldmap=True, fieldmap_registration=False): - """Creates a workflow that chains the necessary pipelines to + """ + Creates a workflow that chains the necessary pipelines to correct for motion, eddy currents, and, if selected, susceptibility artifacts in EPI dMRI sequences. - .. warning:: + .. deprecated:: 0.9.3 + Use :func:`nipype.workflows.dmri.preprocess.epi.all_fmb_pipeline` or + :func:`nipype.workflows.dmri.preprocess.epi.all_peb_pipeline` instead. + - IMPORTANT NOTICE: this workflow rotates the b-vectors, so please be adviced - that not all the dicom converters ensure the consistency between the resulting - nifti orientation and the b matrix table (e.g. dcm2nii checks it). + .. warning:: This workflow rotates the b-vectors, so please be + advised that not all the dicom converters ensure the consistency between the resulting + nifti orientation and the b matrix table (e.g. dcm2nii checks it). Example @@ -54,12 +59,16 @@ def create_dmri_preprocessing(name='dMRI_preprocessing', use_fieldmap=True, fiel Optional arguments:: + use_fieldmap - True if there are fieldmap files that should be used (default True) fieldmap_registration - True if registration to fieldmap should be performed (default False) """ + warnings.warn(('This workflow is deprecated from v.1.0.0, use of available ' + 'nipype.workflows.dmri.preprocess.epi.all_*'), DeprecationWarning) + pipeline = pe.Workflow(name=name) inputnode = pe.Node(niu.IdentityInterface( @@ -118,10 +127,12 @@ def create_motion_correct_pipeline(name='motion_correct'): (Leemans et al. 2009 - http://www.ncbi.nlm.nih.gov/pubmed/19319973), making use of the rotation matrix obtained by FLIRT. - .. deprecated:: 1.0.0. - Use :func:`nipype.workflows.dmri.preprocess.epi.motion_correct` instead - .. warning:: IMPORTANT NOTICE: this workflow rotates the b-vectors, so please be adviced + .. deprecated:: 0.9.3 + Use :func:`nipype.workflows.dmri.preprocess.epi.hmc_pipeline` instead. + + + .. warning:: This workflow rotates the b-vectors, so please be adviced that not all the dicom converters ensure the consistency between the resulting nifti orientation and the b matrix table (e.g. dcm2nii checks it). @@ -148,6 +159,10 @@ def create_motion_correct_pipeline(name='motion_correct'): """ + warnings.warn(('This workflow is deprecated from v.1.0.0, use ' + 'nipype.workflows.dmri.preprocess.epi.hmc_pipeline instead'), + DeprecationWarning) + inputnode = pe.Node( niu.IdentityInterface( fields=['in_file', 'ref_num', 'in_bvec']), @@ -184,7 +199,13 @@ def create_motion_correct_pipeline(name='motion_correct'): def create_eddy_correct_pipeline(name='eddy_correct'): - """Creates a pipeline that replaces eddy_correct script in FSL. It takes a + """ + + .. deprecated:: 0.9.3 + Use :func:`nipype.workflows.dmri.preprocess.epi.ecc_pipeline` instead. + + + Creates a pipeline that replaces eddy_correct script in FSL. It takes a series of diffusion weighted images and linearly co-registers them to one reference image. No rotation of the B-matrix is performed, so this pipeline should be executed after the motion correction pipeline. @@ -207,6 +228,10 @@ def create_eddy_correct_pipeline(name='eddy_correct'): outputnode.eddy_corrected """ + warnings.warn(('This workflow is deprecated from v.1.0.0, use ' + 'nipype.workflows.dmri.preprocess.epi.ecc_pipeline instead'), + DeprecationWarning) + inputnode = pe.Node( niu.IdentityInterface(fields=['in_file', 'ref_num']), name='inputnode') @@ -238,6 +263,11 @@ def create_eddy_correct_pipeline(name='eddy_correct'): def fieldmap_correction(name='fieldmap_correction', nocheck=False): """ + + .. deprecated:: 0.9.3 + Use :func:`nipype.workflows.dmri.preprocess.epi.sdc_fmb` instead. + + Fieldmap-based retrospective correction of EPI images for the susceptibility distortion artifact (Jezzard et al., 1995). Fieldmap images are assumed to be already registered to EPI data, and a brain mask is required. @@ -247,7 +277,6 @@ def fieldmap_correction(name='fieldmap_correction', nocheck=False): available as of FSL 5.0. - Example ------- @@ -283,6 +312,10 @@ def fieldmap_correction(name='fieldmap_correction', nocheck=False): """ + warnings.warn(('This workflow is deprecated from v.1.0.0, use ' + 'nipype.workflows.dmri.preprocess.epi.sdc_fmb instead'), + DeprecationWarning) + inputnode = pe.Node(niu.IdentityInterface( fields=['in_file', 'in_mask', @@ -361,8 +394,13 @@ def fieldmap_correction(name='fieldmap_correction', nocheck=False): def topup_correction( name='topup_correction' ): """ - Corrects for susceptibilty distortion of EPI images when one reverse encoding dataset has - been acquired + + .. deprecated:: 0.9.3 + Use :func:`nipype.workflows.dmri.preprocess.epi.sdc_peb` instead. + + + Corrects for susceptibilty distortion of EPI images when one reverse encoding dataset has + been acquired Example @@ -375,18 +413,26 @@ def topup_correction( name='topup_correction' ): >>> nipype_epicorrect.inputs.inputnode.ref_num = 0 >>> nipype_epicorrect.run() # doctest: +SKIP + Inputs:: + inputnode.in_file_dir - EPI volume acquired in 'forward' phase encoding inputnode.in_file_rev - EPI volume acquired in 'reversed' phase encoding inputnode.encoding_direction - Direction encoding of in_file_dir inputnode.ref_num - Identifier of the reference volumes (usually B0 volume) + Outputs:: outputnode.epi_corrected """ + + warnings.warn(('This workflow is deprecated from v.1.0.0, use ' + 'nipype.workflows.dmri.preprocess.epi.sdc_peb instead'), + DeprecationWarning) + pipeline = pe.Workflow(name=name) inputnode = pe.Node(niu.IdentityInterface( @@ -436,11 +482,18 @@ def topup_correction( name='topup_correction' ): def create_epidewarp_pipeline(name='epidewarp', fieldmap_registration=False): - """ Replaces the epidewarp.fsl script (http://www.nmr.mgh.harvard.edu/~greve/fbirn/b0/epidewarp.fsl) + """ + Replaces the epidewarp.fsl script (http://www.nmr.mgh.harvard.edu/~greve/fbirn/b0/epidewarp.fsl) for susceptibility distortion correction of dMRI & fMRI acquired with EPI sequences and the fieldmap information (Jezzard et al., 1995) using FSL's FUGUE. The registration to the (warped) fieldmap (strictly following the original script) is available using fieldmap_registration=True. + + .. warning:: This workflow makes use of ``epidewarp.fsl`` a script of FSL deprecated long + time ago. The use of this workflow is not recommended, use + :func:`nipype.workflows.dmri.preprocess.epi.sdc_fmb` instead. + + Example ------- @@ -480,6 +533,9 @@ def create_epidewarp_pipeline(name='epidewarp', fieldmap_registration=False): """ + warnings.warn(('This workflow reproduces a deprecated FSL script.'), + DeprecationWarning) + inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'fieldmap_mag', 'fieldmap_pha', diff --git a/nipype/workflows/dmri/preprocess/epi.py b/nipype/workflows/dmri/preprocess/epi.py index 27acbe5b28..69b3b98969 100644 --- a/nipype/workflows/dmri/preprocess/epi.py +++ b/nipype/workflows/dmri/preprocess/epi.py @@ -519,8 +519,7 @@ def sdc_peb(name='peb_correction', topup.inputs.encoding_direction = [epi_params['enc_dir'], altepi_params['enc_dir']] topup.inputs.readout_times = [epi_params['echospacing']/(1.0*epi_params['acc_factor']), altepi_params['echospacing']/(1.0*altepi_params['acc_factor'])] - unwarp = pe.Node(fsl.ApplyTOPUP(in_index=[1,2]), name='unwarp') - dwi_comb = pe.Node(niu.Merge(2), name='DWIcomb') + unwarp = pe.Node(fsl.ApplyTOPUP(in_index=[1], method='jac'), name='unwarp') wf = pe.Workflow(name=name) wf.connect([ @@ -528,16 +527,14 @@ def sdc_peb(name='peb_correction', (('ref_num', _checkrnum),'t_min')]) ,(inputnode, b0_alt, [('alt_file','in_file'), (('ref_num', _checkrnum),'t_min')]) - ,(inputnode, dwi_comb, [('in_file','in1'), - ('alt_file','in2') ] ) - ,(b0_ref, b0_comb, [('roi_file','in1')] ) - ,(b0_alt, b0_comb, [('roi_file','in2')] ) - ,(b0_comb, b0_merge, [('out', 'in_files')] ) + ,(b0_ref, b0_comb, [('roi_file','in1')]) + ,(b0_alt, b0_comb, [('roi_file','in2')]) + ,(b0_comb, b0_merge, [('out', 'in_files')]) ,(b0_merge, topup, [('merged_file','in_file')]) ,(topup, unwarp, [('out_fieldcoef','in_topup_fieldcoef'), ('out_movpar','in_topup_movpar'), ('out_enc_file','encoding_file')]) - ,(dwi_comb, unwarp, [('out','in_files')]) + ,(inputnode, unwarp, [('in_file','in_files')]) ,(unwarp, outputnode, [('out_corrected','out_file')]) ]) return wf From f9e794aa7fe75f284e0d1f4ea7e8e8ffffa41565 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Tue, 2 Sep 2014 09:52:48 +0200 Subject: [PATCH 11/33] updating dmri preprocess example --- examples/dmri_preprocessing.py | 76 ++++++++++++++++++++++++++-------- 1 file changed, 58 insertions(+), 18 deletions(-) diff --git a/examples/dmri_preprocessing.py b/examples/dmri_preprocessing.py index 19fdee7b54..097722ea01 100644 --- a/examples/dmri_preprocessing.py +++ b/examples/dmri_preprocessing.py @@ -3,7 +3,7 @@ # @Author: oesteban # @Date: 2014-08-31 20:32:22 # @Last Modified by: oesteban -# @Last Modified time: 2014-09-01 18:12:14 +# @Last Modified time: 2014-09-02 09:41:46 """ =================== dMRI: Preprocessing @@ -23,13 +23,16 @@ Import necessary modules from nipype. """ - +import os # system functions import nipype.interfaces.io as nio # Data i/o import nipype.interfaces.utility as util # utility -import nipype.pipeline.engine as pe # pypeline engine -import nipype.interfaces.fsl as fsl import nipype.algorithms.misc as misc -import os # system functions + +import nipype.pipeline.engine as pe # pypeline engine + +from nipype.interfaces import fsl +from nipype.interfaces import ants + """ @@ -48,9 +51,9 @@ """ info = dict(dwi=[['subject_id', 'dwidata']], - bvecs=[['subject_id','bvecs']], - bvals=[['subject_id','bvals']], - dwi_rev=[['subject_id','nodif_PA']]) + bvecs=[['subject_id', 'bvecs']], + bvals=[['subject_id', 'bvals']], + dwi_rev=[['subject_id', 'nodif_PA']]) infosource = pe.Node(interface=util.IdentityInterface(fields=['subject_id']), name="infosource") @@ -81,20 +84,22 @@ """ datasource = pe.Node(nio.DataGrabber(infields=['subject_id'], - outfields=info.keys()), name = 'datasource') + outfields=info.keys()), name='datasource') datasource.inputs.template = "%s/%s" # This needs to point to the fdt folder you can find after extracting # http://www.fmrib.ox.ac.uk/fslcourse/fsl_course_data2.tar.gz datasource.inputs.base_directory = os.path.abspath('fdt1') -datasource.inputs.field_template = dict(dwi='%s/%s.nii.gz', dwi_rev='%s/%s.nii.gz') +datasource.inputs.field_template = dict(dwi='%s/%s.nii.gz', + dwi_rev='%s/%s.nii.gz') datasource.inputs.template_args = info datasource.inputs.sort_filelist = True """ -An inputnode is used to pass the data obtained by the data grabber to the actual processing functions +An inputnode is used to pass the data obtained by the data grabber to the +actual processing functions """ inputnode = pe.Node(util.IdentityInterface(fields=["dwi", "bvecs", "bvals", @@ -106,10 +111,20 @@ Setup for dMRI preprocessing ============================ -In this section we initialize the appropriate workflow for preprocessing of diffusion images. -Particularly, we look into the ``acqparams.txt`` file of the selected subject to gather the -encoding direction, acceleration factor (in parallel sequences it is > 1), and readout time or -echospacing. +In this section we initialize the appropriate workflow for preprocessing of +diffusion images. + +Artifacts correction +-------------------- + +We will use the combination of ```topup``` and ```eddy``` as suggested by FSL. + +In order to configure the susceptibility distortion correction (SDC), we first +write the specific parameters of our echo-planar imaging (EPI) images. + +Particularly, we look into the ``acqparams.txt`` file of the selected subject +to gather the encoding direction, acceleration factor (in parallel sequences +it is > 1), and readout time or echospacing. """ @@ -118,13 +133,28 @@ prep = all_peb_pipeline(epi_params=epi_AP, altepi_params=epi_PA) +""" + +Bias field correction +--------------------- + +Finally, we set up a node to estimate a single multiplicative bias field from +the *b0* image, as suggested in [Jeurissen2014]_. + +""" + +n4 = pe.Node(ants.N4BiasFieldCorrection(dimension=3), name='Bias_b0') +split = pe.Node(fsl.Split(dimension='t'), name='SplitDWIs') +merge = pe.Node(niu.Function(input_names=['in_dwi', 'in_bval', 'in_corrected'], + output_names=['out_file'], function=recompose_dwi), name='MergeDWIs') + """ Connect nodes in workflow ========================= -We create a higher level workflow to connect the nodes. Please excuse the author for -writing the arguments of the ``connect`` function in a not-standard fashion with readability -aims. +We create a higher level workflow to connect the nodes. Please excuse the +author for writing the arguments of the ``connect`` function in a not-standard +style with readability aims. """ wf = pe.Workflow(name="dMRI_Preprocessing") @@ -145,3 +175,13 @@ if __name__ == '__main__': wf.run() wf.write_graph() + + +""" + +.. admonition:: References + + .. [Jeurissen2014] Jeurissen B. et al., `Multi-tissue constrained spherical deconvolution + for improved analysis of multi-shell diffusion MRI data + `_. NeuroImage (2014). + doi: 10.1016/j.neuroimage.2014.07.061 From 55457a25bc2e50fdb334bc67fba43124814e0324 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Tue, 2 Sep 2014 13:22:16 +0200 Subject: [PATCH 12/33] ENH: new artifact correction workflow on fsl Uses TOPUP and Eddy for correction. Tested on the new dmri_preprocess.py example with the fsl course data. --- examples/dmri_preprocessing.py | 47 +++--- nipype/workflows/dmri/preprocess/__init__.py | 5 +- nipype/workflows/dmri/preprocess/epi.py | 166 ++++++++++++++++--- nipype/workflows/dmri/preprocess/utils.py | 74 ++++++++- 4 files changed, 240 insertions(+), 52 deletions(-) diff --git a/examples/dmri_preprocessing.py b/examples/dmri_preprocessing.py index 097722ea01..90d06ee68c 100644 --- a/examples/dmri_preprocessing.py +++ b/examples/dmri_preprocessing.py @@ -3,7 +3,7 @@ # @Author: oesteban # @Date: 2014-08-31 20:32:22 # @Last Modified by: oesteban -# @Last Modified time: 2014-09-02 09:41:46 +# @Last Modified time: 2014-09-02 13:12:12 """ =================== dMRI: Preprocessing @@ -15,17 +15,18 @@ This script, dmri_preprocessing.py, demonstrates how to prepare dMRI data for tractography and connectivity analysis with nipype. -We perform this analysis using the FSL course data, which can be acquired from here: -http://www.fmrib.ox.ac.uk/fslcourse/fsl_course_data2.tar.gz +We perform this analysis using the FSL course data, which can be acquired from +here: http://www.fmrib.ox.ac.uk/fslcourse/fsl_course_data2.tar.gz Can be executed in command line using ``python dmri_preprocessing.py`` Import necessary modules from nipype. """ + import os # system functions import nipype.interfaces.io as nio # Data i/o -import nipype.interfaces.utility as util # utility +import nipype.interfaces.utility as niu # utility import nipype.algorithms.misc as misc import nipype.pipeline.engine as pe # pypeline engine @@ -34,7 +35,6 @@ from nipype.interfaces import ants - """ Load specific nipype's workflows for preprocessing of dMRI data: :class:`nipype.workflows.dmri.preprocess.epi.all_peb_pipeline`, @@ -43,8 +43,7 @@ that is *A>>>P* or *-y* (in RAS systems). """ -from nipype.workflows.dmri.preprocess.epi import all_peb_pipeline - +from nipype.workflows.dmri.preprocess.epi import all_fsl_pipeline, remove_bias """ Map field names into individual subject runs @@ -55,7 +54,7 @@ bvals=[['subject_id', 'bvals']], dwi_rev=[['subject_id', 'nodif_PA']]) -infosource = pe.Node(interface=util.IdentityInterface(fields=['subject_id']), +infosource = pe.Node(interface=niu.IdentityInterface(fields=['subject_id']), name="infosource") # Set the subject 1 identifier in subject_list, @@ -102,7 +101,7 @@ actual processing functions """ -inputnode = pe.Node(util.IdentityInterface(fields=["dwi", "bvecs", "bvals", +inputnode = pe.Node(niu.IdentityInterface(fields=["dwi", "bvecs", "bvals", "dwi_rev"]), name="inputnode") @@ -117,7 +116,7 @@ Artifacts correction -------------------- -We will use the combination of ```topup``` and ```eddy``` as suggested by FSL. +We will use the combination of ``topup`` and ``eddy`` as suggested by FSL. In order to configure the susceptibility distortion correction (SDC), we first write the specific parameters of our echo-planar imaging (EPI) images. @@ -128,9 +127,9 @@ """ -epi_AP = {'echospacing': 66.5e-3, 'acc_factor': 1, 'enc_dir': 'y-'} -epi_PA = {'echospacing': 66.5e-3, 'acc_factor': 1, 'enc_dir': 'y'} -prep = all_peb_pipeline(epi_params=epi_AP, altepi_params=epi_PA) +epi_AP = {'echospacing': 66.5e-3, 'enc_dir': 'y-'} +epi_PA = {'echospacing': 66.5e-3, 'enc_dir': 'y'} +prep = all_fsl_pipeline(epi_params=epi_AP, altepi_params=epi_PA) """ @@ -138,15 +137,13 @@ Bias field correction --------------------- -Finally, we set up a node to estimate a single multiplicative bias field from -the *b0* image, as suggested in [Jeurissen2014]_. +Finally, we set up a node to correct for a single multiplicative bias field +from computed on the *b0* image, as suggested in [Jeurissen2014]_. """ -n4 = pe.Node(ants.N4BiasFieldCorrection(dimension=3), name='Bias_b0') -split = pe.Node(fsl.Split(dimension='t'), name='SplitDWIs') -merge = pe.Node(niu.Function(input_names=['in_dwi', 'in_bval', 'in_corrected'], - output_names=['out_file'], function=recompose_dwi), name='MergeDWIs') +bias = remove_bias() + """ Connect nodes in workflow @@ -165,6 +162,9 @@ ('dwi_rev', 'inputnode.alt_file'), ('bvals', 'inputnode.in_bval'), ('bvecs', 'inputnode.in_bvec')]) + ,(prep, bias, [('outputnode.out_file', 'inputnode.in_file'), + ('outputnode.out_mask', 'inputnode.in_mask')]) + ,(datasource, bias, [('bvals', 'inputnode.in_bval')]) ]) @@ -176,12 +176,3 @@ wf.run() wf.write_graph() - -""" - -.. admonition:: References - - .. [Jeurissen2014] Jeurissen B. et al., `Multi-tissue constrained spherical deconvolution - for improved analysis of multi-shell diffusion MRI data - `_. NeuroImage (2014). - doi: 10.1016/j.neuroimage.2014.07.061 diff --git a/nipype/workflows/dmri/preprocess/__init__.py b/nipype/workflows/dmri/preprocess/__init__.py index 4b3dd5ff36..63d61376a4 100644 --- a/nipype/workflows/dmri/preprocess/__init__.py +++ b/nipype/workflows/dmri/preprocess/__init__.py @@ -1,2 +1,3 @@ -from epi import (all_fmb_pipeline, all_peb_pipeline, - hmc_pipeline, ecc_pipeline, sdc_fmb, sdc_peb) +from epi import (all_fmb_pipeline, all_peb_pipeline, all_fsl_pipeline, + hmc_pipeline, ecc_pipeline, sdc_fmb, sdc_peb, + remove_bias) diff --git a/nipype/workflows/dmri/preprocess/epi.py b/nipype/workflows/dmri/preprocess/epi.py index 69b3b98969..d93cf61627 100644 --- a/nipype/workflows/dmri/preprocess/epi.py +++ b/nipype/workflows/dmri/preprocess/epi.py @@ -70,10 +70,12 @@ def all_fmb_pipeline(name='hmc_sdc_ecc'): def all_peb_pipeline(name='hmc_sdc_ecc', epi_params=dict(echospacing=0.77e-3, acc_factor=3, - enc_dir='y-'), + enc_dir='y-', + epi_factor=1), altepi_params=dict(echospacing=0.77e-3, acc_factor=3, - enc_dir='y')): + enc_dir='y', + epi_factor=1)): """ Builds a pipeline including three artifact corrections: head-motion correction (HMC), susceptibility-derived distortion correction (SDC), and Eddy currents-derived distortion @@ -96,6 +98,10 @@ def all_peb_pipeline(name='hmc_sdc_ecc', sdc = sdc_peb(epi_params=epi_params, altepi_params=altepi_params) ecc = ecc_pipeline() + rot_bvec = pe.Node(niu.Function(input_names=['in_bvec', 'eddy_params'], + output_names=['out_file'], function=eddy_rotate_bvecs), + name='Rotate_Bvec') + regrid = pe.Node(fs.MRIConvert(vox_size=(2.0, 2.0, 2.0), out_orientation='RAS'), name='Reslice') @@ -120,6 +126,76 @@ def all_peb_pipeline(name='hmc_sdc_ecc', ,(regrid, avg_b0_1, [('out_file', 'in_dwi')]) ,(inputnode, avg_b0_1, [('in_bval', 'in_bval')]) ,(avg_b0_1, bet_dwi1, [('out_file','in_file')]) + ,(inputnode, rot_bvec, [('in_bvec', 'in_bvec')]) + ,(ecc, rot_bvec, [('out_parameter', 'eddy_params')]) + ,(bet_dwi1, outputnode, [('mask_file', 'out_mask')]) + ,(rot_bvec, outputnode, [('out_file', 'out_bvec')]) + ]) + return wf + + +def all_fsl_pipeline(name='fsl_all_correct', + epi_params=dict(echospacing=0.77e-3, + acc_factor=3, + enc_dir='y-'), + altepi_params=dict(echospacing=0.77e-3, + acc_factor=3, + enc_dir='y')): + """ + Workflow that integrates FSL ``topup`` and ``eddy``. + """ + + inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_bvec', 'in_bval', + 'alt_file']), name='inputnode') + + outputnode = pe.Node(niu.IdentityInterface(fields=['out_file', 'out_mask', + 'out_bvec']), name='outputnode') + + def _gen_index(in_file): + import numpy as np + import nibabel as nb + import os + out_file = os.path.abspath('index.txt') + vols = nb.load(in_file).get_data().shape[-1] + np.savetxt(out_file, np.ones((vols,)).T) + return out_file + + avg_b0_0 = pe.Node(niu.Function(input_names=['in_dwi', 'in_bval'], + output_names=['out_file'], function=b0_average), name='b0_avg_pre') + bet_dwi0 = pe.Node(fsl.BET(frac=0.3, mask=True, robust=True), name='bet_dwi_pre') + + sdc = sdc_peb(epi_params=epi_params, altepi_params=altepi_params) + ecc = pe.Node(fsl.Eddy(method='jac'), name='fsl_eddy') + + regrid = pe.Node(fs.MRIConvert(vox_size=(2.0, 2.0, 2.0), out_orientation='RAS'), + name='Reslice') + avg_b0_1 = pe.Node(niu.Function(input_names=['in_dwi', 'in_bval'], + output_names=['out_file'], function=b0_average), name='b0_avg_post') + bet_dwi1 = pe.Node(fsl.BET(frac=0.3, mask=True, robust=True), name='bet_dwi_post') + + wf = pe.Workflow('dMRI_Artifacts_FSL') + wf.connect([ + (inputnode, avg_b0_0, [('in_file', 'in_dwi'), + ('in_bval', 'in_bval')]) + ,(avg_b0_0, bet_dwi0, [('out_file','in_file')]) + ,(bet_dwi0, sdc, [('mask_file', 'inputnode.in_mask')]) + ,(inputnode, sdc, [('in_file', 'inputnode.in_file'), + ('alt_file', 'inputnode.alt_file'), + ('in_bval', 'inputnode.in_bval')]) + + ,(sdc, ecc, [('topup.out_enc_file', 'in_acqp'), + ('topup.out_fieldcoef', 'in_topup_fieldcoef'), + ('topup.out_movpar', 'in_topup_movpar')]) + ,(bet_dwi0, ecc, [('mask_file', 'in_mask')]) + ,(inputnode, ecc, [('in_file', 'in_file'), + (('in_file', _gen_index), 'in_index'), + ('in_bval', 'in_bval'), + ('in_bvec', 'in_bvec')]) + ,(ecc, regrid, [('out_corrected', 'in_file')]) + ,(regrid, outputnode, [('out_file', 'out_file')]) + ,(regrid, avg_b0_1, [('out_file', 'in_dwi')]) + ,(inputnode, avg_b0_1, [('in_bval', 'in_bval')]) + ,(avg_b0_1, bet_dwi1, [('out_file','in_file')]) ,(bet_dwi1, outputnode, [('mask_file', 'out_mask')]) ]) return wf @@ -218,6 +294,7 @@ def hmc_pipeline(name='motion_correct'): ]) return wf + def ecc_pipeline(name='eddy_correct'): """ ECC stands for Eddy currents correction. @@ -326,6 +403,7 @@ def ecc_pipeline(name='eddy_correct'): ]) return wf + def sdc_fmb(name='fmb_correction', fugue_params=dict(smooth3d=2.0), bmap_params=dict(delta_te=2.46e-3), @@ -462,10 +540,12 @@ def sdc_fmb(name='fmb_correction', def sdc_peb(name='peb_correction', epi_params=dict(echospacing=0.77e-3, acc_factor=3, - enc_dir='y-'), + enc_dir='y-', + epi_factor=1), altepi_params=dict(echospacing=0.77e-3, acc_factor=3, - enc_dir='y')): + enc_dir='y', + epi_factor=1)): """ SDC stands for susceptibility distortion correction. PEB stands for phase-encoding-based. @@ -517,25 +597,72 @@ def sdc_peb(name='peb_correction', topup = pe.Node(fsl.TOPUP(), name='topup') topup.inputs.encoding_direction = [epi_params['enc_dir'], altepi_params['enc_dir']] - topup.inputs.readout_times = [epi_params['echospacing']/(1.0*epi_params['acc_factor']), - altepi_params['echospacing']/(1.0*altepi_params['acc_factor'])] + topup.inputs.readout_times = [compute_readout(epi_params), + compute_readout(altepi_params)] + unwarp = pe.Node(fsl.ApplyTOPUP(in_index=[1], method='jac'), name='unwarp') wf = pe.Workflow(name=name) wf.connect([ - (inputnode, b0_ref, [('in_file','in_file'), - (('ref_num', _checkrnum),'t_min')]) - ,(inputnode, b0_alt, [('alt_file','in_file'), - (('ref_num', _checkrnum),'t_min')]) - ,(b0_ref, b0_comb, [('roi_file','in1')]) - ,(b0_alt, b0_comb, [('roi_file','in2')]) + (inputnode, b0_ref, [('in_file', 'in_file'), + (('ref_num', _checkrnum), 't_min')]) + ,(inputnode, b0_alt, [('alt_file', 'in_file'), + (('ref_num', _checkrnum), 't_min')]) + ,(b0_ref, b0_comb, [('roi_file', 'in1')]) + ,(b0_alt, b0_comb, [('roi_file', 'in2')]) ,(b0_comb, b0_merge, [('out', 'in_files')]) - ,(b0_merge, topup, [('merged_file','in_file')]) - ,(topup, unwarp, [('out_fieldcoef','in_topup_fieldcoef'), - ('out_movpar','in_topup_movpar'), - ('out_enc_file','encoding_file')]) - ,(inputnode, unwarp, [('in_file','in_files')]) - ,(unwarp, outputnode, [('out_corrected','out_file')]) + ,(b0_merge, topup, [('merged_file', 'in_file')]) + ,(topup, unwarp, [('out_fieldcoef', 'in_topup_fieldcoef'), + ('out_movpar', 'in_topup_movpar'), + ('out_enc_file', 'encoding_file')]) + ,(inputnode, unwarp, [('in_file', 'in_files')]) + ,(unwarp, outputnode, [('out_corrected', 'out_file')]) + ]) + return wf + + +def remove_bias(name='bias_correct'): + """ + This workflow estimates a single multiplicative bias field from the averaged *b0* + image, as suggested in [Jeurissen2014]_. + + .. admonition:: References + + .. [Jeurissen2014] Jeurissen B. et al., `Multi-tissue constrained spherical deconvolution + for improved analysis of multi-shell diffusion MRI data + `_. NeuroImage (2014). + doi: 10.1016/j.neuroimage.2014.07.061 + + """ + inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_bval', + 'in_mask']), name='inputnode') + + outputnode = pe.Node(niu.IdentityInterface(fields=['out_file']), + name='outputnode') + + avg_b0 = pe.Node(niu.Function(input_names=['in_dwi', 'in_bval'], + output_names=['out_file'], function=b0_average), + name='b0_avg') + n4 = pe.Node(ants.N4BiasFieldCorrection(dimension=3, + save_bias=True, bspline_fitting_distance=600), name='Bias_b0') + split = pe.Node(fsl.Split(dimension='t'), name='SplitDWIs') + mult = pe.MapNode(fsl.MultiImageMaths(op_string='-div %s'), + iterfield=['in_file'], name='RemoveBiasOfDWIs') + thres = pe.MapNode(fsl.Threshold(thresh=0.0), iterfield=['in_file'], + name='RemoveNegative') + merge = pe.Node(fsl.utils.Merge(dimension='t'), name='MergeDWIs') + + wf = pe.Workflow(name=name) + wf.connect([ + (inputnode, avg_b0, [('in_file', 'in_dwi'), + ('in_bval', 'in_bval')]) + ,(avg_b0, n4, [('out_file', 'input_image')]) + ,(inputnode, n4, [('in_mask', 'mask_image')]) + ,(inputnode, split, [('in_file', 'in_file')]) + ,(n4, mult, [('bias_image', 'operand_files')]) + ,(split, mult, [('out_files', 'in_file')]) + ,(mult, thres, [('out_file', 'in_file')]) + ,(thres, merge, [('out_file', 'in_files')]) ]) return wf @@ -546,12 +673,13 @@ def _checkrnum(ref_num): return 0 return ref_num + def _checkinitxfm(in_bval, in_xfms=None): from nipype.interfaces.base import isdefined import numpy as np import os.path as op bvals = np.loadtxt(in_bval) - non_b0 = np.where(bvals!=0)[0].tolist() + non_b0 = np.where(bvals != 0)[0].tolist() init_xfms = [] if (in_xfms is None) or (not isdefined(in_xfms)) or (len(in_xfms)!=len(bvals)): diff --git a/nipype/workflows/dmri/preprocess/utils.py b/nipype/workflows/dmri/preprocess/utils.py index 04247372df..00c8ae46f0 100644 --- a/nipype/workflows/dmri/preprocess/utils.py +++ b/nipype/workflows/dmri/preprocess/utils.py @@ -4,8 +4,8 @@ # vi: set ft=python sts=4 ts=4 sw=4 et: # @Author: oesteban # @Date: 2014-08-30 10:53:13 -# @Last Modified by: oesteban -# @Last Modified time: 2014-08-30 18:54:43 +# @Last Modified by: Oscar Esteban +# @Last Modified time: 2014-09-02 13:17:12 def cleanup_edge_pipeline(name='Cleanup'): """ @@ -159,7 +159,7 @@ def rotate_bvecs(in_bvec, in_matrix): raise RuntimeError('Number of b-vectors and rotation matrices should match.') for bvec, mat in zip(bvecs, in_matrix): - if np.all(bvec==0.0): + if np.all(bvec == 0.0): new_bvecs.append(bvec) else: invrot = np.linalg.inv(np.loadtxt(mat))[:3,:3] @@ -169,6 +169,74 @@ def rotate_bvecs(in_bvec, in_matrix): np.savetxt(out_file, np.array(new_bvecs).T, fmt='%0.15f') return out_file + +def eddy_rotate_bvecs(in_bvec, eddy_params): + """ + Rotates the input bvec file accordingly with a list of parameters sourced + from ``eddy``, as explained `here + `_. + """ + import os + import numpy as np + from math import sin, cos + + name, fext = os.path.splitext(os.path.basename(in_bvec)) + if fext == '.gz': + name, _ = os.path.splitext(name) + out_file = os.path.abspath('%s_rotated.bvec' % name) + bvecs = np.loadtxt(in_bvec).T + new_bvecs = [] + + params = np.loadtxt(eddy_params) + + if len(bvecs) != len(params): + raise RuntimeError('Number of b-vectors and rotation matrices should match.') + + for bvec, row in zip(bvecs, params): + if np.all(bvec == 0.0): + new_bvecs.append(bvec) + else: + ax = row[3] + ay = row[4] + az = row[5] + + Rx = np.array([[1.0, 0.0, 0.0], [0.0, cos(ax), -sin(ax)], [0.0, sin(ax), cos(ax)]]) + Ry = np.array([[cos(ay), 0.0, sin(ay)], [0.0, 1.0, 0.0], [-sin(ay), 0.0, cos(ay)]]) + Rz = np.array([[cos(az), -sin(az), 0.0], [sin(az), cos(az), 0.0], [0.0, 0.0, 1.0]]) + R = Rx.dot(Ry).dot(Rz) + + invrot = np.linalg.inv(R) + newbvec = invrot.dot(bvec) + new_bvecs.append((newbvec/np.linalg.norm(newbvec))) + + np.savetxt(out_file, np.array(new_bvecs).T, fmt='%0.15f') + return out_file + + +def compute_readout(params): + """ + Computes readout time from epi params (see `eddy documentation + `_). + + .. warning:: ``params['echospacing']`` should be in *sec* units. + + + """ + epi_factor = 1.0 + acc_factor = 1.0 + try: + if params['epi_factor'] > 1: + epi_factor = float(params['epi_factor'] - 1) + except: + pass + try: + if params['acc_factor'] > 1: + acc_factor = 1.0 / params['acc_factor'] + except: + pass + return acc_factor * epi_factor * params['echospacing'] + + def siemens2rads(in_file, out_file=None): """ Converts input phase difference map to rads From eedcfcc278ebe0aa42103ee81975f8015cd58ac3 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Tue, 2 Sep 2014 20:08:25 +0200 Subject: [PATCH 13/33] enh:new all_fmb_pipeline with just 1 interpolation --- nipype/workflows/dmri/preprocess/epi.py | 91 ++++++++----- nipype/workflows/dmri/preprocess/utils.py | 156 ++++++++++++++++++++-- 2 files changed, 204 insertions(+), 43 deletions(-) diff --git a/nipype/workflows/dmri/preprocess/epi.py b/nipype/workflows/dmri/preprocess/epi.py index d93cf61627..fcfac87fb8 100644 --- a/nipype/workflows/dmri/preprocess/epi.py +++ b/nipype/workflows/dmri/preprocess/epi.py @@ -17,6 +17,13 @@ def all_fmb_pipeline(name='hmc_sdc_ecc'): Builds a pipeline including three artifact corrections: head-motion correction (HMC), susceptibility-derived distortion correction (SDC), and Eddy currents-derived distortion correction (ECC). + + The displacement fields from each kind of distortions are combined. Thus, + only one interpolation occurs between input data and result. + + .. warning:: this workflow rotates the gradients table (*b*-vectors) [Leemans09]_. + + """ inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_bvec', 'in_bval', 'bmap_pha', 'bmap_mag']), name='inputnode') @@ -35,9 +42,7 @@ def all_fmb_pipeline(name='hmc_sdc_ecc'): hmc = hmc_pipeline() sdc = sdc_fmb() ecc = ecc_pipeline() - - regrid = pe.Node(fs.MRIConvert(vox_size=(2.0, 2.0, 2.0), out_orientation='RAS'), - name='Reslice') + unwarp = apply_all_corrections() wf = pe.Workflow('dMRI_Artifacts') wf.connect([ @@ -45,9 +50,8 @@ def all_fmb_pipeline(name='hmc_sdc_ecc'): ('in_bvec', 'inputnode.in_bvec')]) ,(inputnode, avg_b0_0, [('in_file', 'in_dwi'), ('in_bval', 'in_bval')]) - ,(avg_b0_0, bet_dwi0, [('out_file','in_file')]) + ,(avg_b0_0, bet_dwi0, [('out_file', 'in_file')]) ,(bet_dwi0, hmc, [('mask_file', 'inputnode.in_mask')]) - ,(hmc, sdc, [('outputnode.out_file', 'inputnode.in_file')]) ,(bet_dwi0, sdc, [('mask_file', 'inputnode.in_mask')]) ,(inputnode, sdc, [('in_bval', 'inputnode.in_bval'), @@ -56,12 +60,17 @@ def all_fmb_pipeline(name='hmc_sdc_ecc'): ,(inputnode, ecc, [('in_bval', 'inputnode.in_bval')]) ,(bet_dwi0, ecc, [('mask_file', 'inputnode.in_mask')]) ,(sdc, ecc, [('outputnode.out_file', 'inputnode.in_file')]) - ,(hmc, outputnode, [('outputnode.out_bvec', 'out_bvec')]) - ,(ecc, regrid, [('outputnode.out_file', 'in_file')]) - ,(regrid, outputnode, [('out_file', 'out_file')]) - ,(regrid, avg_b0_1, [('out_file', 'in_dwi')]) + ,(ecc, avg_b0_1, [('outputnode.out_file', 'in_dwi')]) ,(inputnode, avg_b0_1, [('in_bval', 'in_bval')]) - ,(avg_b0_1, bet_dwi1, [('out_file','in_file')]) + ,(avg_b0_1, bet_dwi1, [('out_file', 'in_file')]) + + ,(inputnode, unwarp, [('in_file', 'inputnode.in_dwi')]) + ,(hmc, unwarp, [('outputnode.out_xfms', 'inputnode.in_hmc')]) + ,(ecc, unwarp, [('outputnode.out_xfms', 'inputnode.in_ecc')]) + ,(sdc, unwarp, [('outputnode.out_warp', 'inputnode.in_sdc')]) + + ,(hmc, outputnode, [('outputnode.out_bvec', 'out_bvec')]) + ,(unwarp, outputnode, [('outputnode.out_file', 'out_file')]) ,(bet_dwi1, outputnode, [('mask_file', 'out_mask')]) ]) return wf @@ -80,6 +89,10 @@ def all_peb_pipeline(name='hmc_sdc_ecc', Builds a pipeline including three artifact corrections: head-motion correction (HMC), susceptibility-derived distortion correction (SDC), and Eddy currents-derived distortion correction (ECC). + + .. warning:: this workflow rotates the gradients table (*b*-vectors) [Leemans09]_. + + """ inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_bvec', 'in_bval', 'alt_file']), name='inputnode') @@ -98,13 +111,6 @@ def all_peb_pipeline(name='hmc_sdc_ecc', sdc = sdc_peb(epi_params=epi_params, altepi_params=altepi_params) ecc = ecc_pipeline() - rot_bvec = pe.Node(niu.Function(input_names=['in_bvec', 'eddy_params'], - output_names=['out_file'], function=eddy_rotate_bvecs), - name='Rotate_Bvec') - - regrid = pe.Node(fs.MRIConvert(vox_size=(2.0, 2.0, 2.0), out_orientation='RAS'), - name='Reslice') - wf = pe.Workflow('dMRI_Artifacts') wf.connect([ (inputnode, hmc, [('in_file', 'inputnode.in_file'), @@ -120,16 +126,12 @@ def all_peb_pipeline(name='hmc_sdc_ecc', ,(inputnode, ecc, [('in_bval', 'inputnode.in_bval')]) ,(bet_dwi0, ecc, [('mask_file', 'inputnode.in_mask')]) ,(sdc, ecc, [('outputnode.out_file', 'inputnode.in_file')]) - ,(hmc, outputnode, [('outputnode.out_bvec', 'out_bvec')]) - ,(ecc, regrid, [('outputnode.out_file', 'in_file')]) - ,(regrid, outputnode, [('out_file', 'out_file')]) - ,(regrid, avg_b0_1, [('out_file', 'in_dwi')]) + ,(ecc, avg_b0_1, [('outputnode.out_file', 'in_dwi')]) ,(inputnode, avg_b0_1, [('in_bval', 'in_bval')]) ,(avg_b0_1, bet_dwi1, [('out_file','in_file')]) - ,(inputnode, rot_bvec, [('in_bvec', 'in_bvec')]) - ,(ecc, rot_bvec, [('out_parameter', 'eddy_params')]) + ,(ecc, outputnode, [('outputnode.out_file', 'out_file')]) + ,(hmc, outputnode, [('outputnode.out_bvec', 'out_bvec')]) ,(bet_dwi1, outputnode, [('mask_file', 'out_mask')]) - ,(rot_bvec, outputnode, [('out_file', 'out_bvec')]) ]) return wf @@ -143,6 +145,15 @@ def all_fsl_pipeline(name='fsl_all_correct', enc_dir='y')): """ Workflow that integrates FSL ``topup`` and ``eddy``. + + + .. warning:: this workflow rotates the gradients table (*b*-vectors) [Leemans09]_. + + + .. warning:: this workflow does not perform jacobian modulation of each + *DWI* [Jones10]_. + + """ inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_bvec', 'in_bval', @@ -166,9 +177,9 @@ def _gen_index(in_file): sdc = sdc_peb(epi_params=epi_params, altepi_params=altepi_params) ecc = pe.Node(fsl.Eddy(method='jac'), name='fsl_eddy') - - regrid = pe.Node(fs.MRIConvert(vox_size=(2.0, 2.0, 2.0), out_orientation='RAS'), - name='Reslice') + rot_bvec = pe.Node(niu.Function(input_names=['in_bvec', 'eddy_params'], + output_names=['out_file'], function=eddy_rotate_bvecs), + name='Rotate_Bvec') avg_b0_1 = pe.Node(niu.Function(input_names=['in_dwi', 'in_bval'], output_names=['out_file'], function=b0_average), name='b0_avg_post') bet_dwi1 = pe.Node(fsl.BET(frac=0.3, mask=True, robust=True), name='bet_dwi_post') @@ -191,11 +202,13 @@ def _gen_index(in_file): (('in_file', _gen_index), 'in_index'), ('in_bval', 'in_bval'), ('in_bvec', 'in_bvec')]) - ,(ecc, regrid, [('out_corrected', 'in_file')]) - ,(regrid, outputnode, [('out_file', 'out_file')]) - ,(regrid, avg_b0_1, [('out_file', 'in_dwi')]) + ,(inputnode, rot_bvec, [('in_bvec', 'in_bvec')]) + ,(ecc, rot_bvec, [('out_parameter', 'eddy_params')]) + ,(ecc, avg_b0_1, [('out_corrected', 'in_dwi')]) ,(inputnode, avg_b0_1, [('in_bval', 'in_bval')]) ,(avg_b0_1, bet_dwi1, [('out_file','in_file')]) + ,(ecc, outputnode, [('out_corrected', 'out_file')]) + ,(rot_bvec, outputnode, [('out_file', 'out_bvec')]) ,(bet_dwi1, outputnode, [('mask_file', 'out_mask')]) ]) return wf @@ -444,12 +457,14 @@ def sdc_fmb(name='fmb_correction', `_, MRM 49(1):193-197, 2003, doi: 10.1002/mrm.10354. """ - inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_bval', 'in_mask', - 'bmap_pha', 'bmap_mag']), + inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_bval', + 'in_mask', 'bmap_pha', 'bmap_mag']), name='inputnode') - outputnode = pe.Node(niu.IdentityInterface(fields=['out_file', 'out_vsm']), + outputnode = pe.Node(niu.IdentityInterface(fields=['out_file', 'out_vsm', + 'out_warp']), name='outputnode') + delta_te = epi_params['echospacing'] / (1.0 * epi_params['acc_factor']) firstmag = pe.Node(fsl.ExtractROI(t_min=0, t_size=1), name='GetFirst') n4 = pe.Node(ants.N4BiasFieldCorrection(dimension=3), name='Bias') @@ -487,15 +502,18 @@ def sdc_fmb(name='fmb_correction', vsm = pe.Node(fsl.FUGUE(save_shift=True, **fugue_params), name="ComputeVSM") vsm.inputs.asym_se_time = bmap_params['delta_te'] - vsm.inputs.dwell_time = epi_params['echospacing'] / (1.0 * epi_params['acc_factor']) + vsm.inputs.dwell_time = delta_te split = pe.Node(fsl.Split(dimension='t'), name='SplitDWIs') merge = pe.Node(fsl.Merge(dimension='t'), name='MergeDWIs') unwarp = pe.MapNode(fsl.FUGUE(icorr=True, forward_warping=False), iterfield=['in_file'], name='UnwarpDWIs') - unwarp.inputs.unwarp_direction=epi_params['enc_dir'] + unwarp.inputs.unwarp_direction = epi_params['enc_dir'] thres = pe.MapNode(fsl.Threshold(thresh=0.0), iterfield=['in_file'], name='RemoveNegative') + vsm2dfm = vsm2warp() + vsm2dfm.inputs.inputnode.scaling = 1.0 + vsm2dfm.inputs.inputnode.enc_dir = epi_params['enc_dir'] wf = pe.Workflow(name=name) wf.connect([ @@ -531,8 +549,11 @@ def sdc_fmb(name='fmb_correction', ,(vsm, unwarp, [('shift_out_file', 'shift_in_file')]) ,(unwarp, thres, [('unwarped_file', 'in_file')]) ,(thres, merge, [('out_file', 'in_files')]) + ,(merge, vsm2dfm, [('merged_file', 'inputnode.in_ref')]) + ,(vsm, vsm2dfm, [('shift_out_file', 'inputnode.in_vsm')]) ,(merge, outputnode, [('merged_file', 'out_file')]) ,(vsm, outputnode, [('shift_out_file', 'out_vsm')]) + ,(vsm2dfm, outputnode, [('outputnode.out_warp', 'out_warp')]) ]) return wf diff --git a/nipype/workflows/dmri/preprocess/utils.py b/nipype/workflows/dmri/preprocess/utils.py index 00c8ae46f0..36e2c39a8a 100644 --- a/nipype/workflows/dmri/preprocess/utils.py +++ b/nipype/workflows/dmri/preprocess/utils.py @@ -5,17 +5,16 @@ # @Author: oesteban # @Date: 2014-08-30 10:53:13 # @Last Modified by: Oscar Esteban -# @Last Modified time: 2014-09-02 13:17:12 +# @Last Modified time: 2014-09-02 19:52:57 +import nipype.pipeline.engine as pe +import nipype.interfaces.utility as niu +from nipype.interfaces import fsl def cleanup_edge_pipeline(name='Cleanup'): """ Perform some de-spiking filtering to clean up the edge of the fieldmap (copied from fsl_prepare_fieldmap) """ - import nipype.pipeline.engine as pe - import nipype.interfaces.utility as niu - import nipype.interfaces.fsl as fsl - inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_mask']), name='inputnode') outputnode = pe.Node(niu.IdentityInterface(fields=['out_file']), @@ -44,12 +43,104 @@ def cleanup_edge_pipeline(name='Cleanup'): ,(erode, join, [('out_file', 'in1')]) ,(applymsk, join, [('out_file', 'in2')]) ,(inputnode, addedge, [('in_file', 'in_file')]) - ,(join, addedge, [('out', 'operand_files')]) + ,(join, addedge, [('out', 'operand_files')]) ,(addedge, outputnode, [('out_file', 'out_file')]) ]) return wf +def vsm2warp(name='Shiftmap2Warping'): + """ + Converts a voxel shift map (vsm) to a displacements field (warp). + """ + inputnode = pe.Node(niu.IdentityInterface(fields=['in_vsm', + 'in_ref', 'scaling', 'enc_dir']), name='inputnode') + outputnode = pe.Node(niu.IdentityInterface(fields=['out_warp']), + name='outputnode') + fixhdr = pe.Node(niu.Function(input_names=['in_file', 'in_file_hdr'], + output_names=['out_file'], function=copy_hdr), + name='Fix_hdr') + vsm = pe.Node(fsl.maths.BinaryMaths(operation='mul'), name='ScaleField') + vsm2dfm = pe.Node(fsl.ConvertWarp(relwarp=True, out_relwarp=True), + name='vsm2dfm') + + wf = pe.Workflow(name=name) + wf.connect([ + (inputnode, fixhdr, [('in_vsm', 'in_file'), + ('in_ref', 'in_file_hdr')]) + ,(inputnode, vsm, [('scaling', 'operand_value')]) + ,(fixhdr, vsm, [('out_file', 'in_file')]) + + ,(vsm, vsm2dfm, [('out_file', 'shift_in_file')]) + ,(inputnode, vsm2dfm, [('in_ref', 'reference'), + ('enc_dir', 'shift_direction')]) + ,(vsm2dfm, outputnode, [('out_file', 'out_warp')]) + ]) + return wf + + +def apply_all_corrections(name='UnwarpArtifacts'): + """ + Combines two lists of linear transforms with the deformation field + map obtained typically after the SDC process. + Additionally, computes the corresponding bspline coefficients and + the map of determinants of the jacobian. + """ + + inputnode = pe.Node(niu.IdentityInterface(fields=['in_sdc', + 'in_hmc', 'in_ecc', 'in_dwi']), name='inputnode') + outputnode = pe.Node(niu.IdentityInterface(fields=['out_file', 'out_warp', + 'out_coeff', 'out_jacobian']), name='outputnode') + warps = pe.MapNode(fsl.ConvertWarp(relwarp=True), + iterfield=['premat', 'postmat'], + name='ConvertWarp') + + selref = pe.Node(niu.Select(index=[0]), name='Reference') + + split = pe.Node(fsl.Split(dimension='t'), name='SplitDWIs') + unwarp = pe.MapNode(fsl.ApplyWarp(), iterfield=['in_file', 'field_file'], + name='UnwarpDWIs') + + coeffs = pe.MapNode(fsl.WarpUtils(out_format='spline'), + iterfield=['in_file'], name='CoeffComp') + jacobian = pe.MapNode(fsl.WarpUtils(write_jacobian=True), + iterfield=['in_file'], name='JacobianComp') + jacmult = pe.MapNode(fsl.MultiImageMaths(op_string='-mul %s'), + iterfield=['in_file', 'operand_files'], + name='ModulateDWIs') + + thres = pe.MapNode(fsl.Threshold(thresh=0.0), iterfield=['in_file'], + name='RemoveNegative') + merge = pe.Node(fsl.Merge(dimension='t'), name='MergeDWIs') + + wf = pe.Workflow(name=name) + wf.connect([ + (inputnode, warps, [('in_sdc', 'warp1'), + ('in_hmc', 'premat'), + ('in_ecc', 'postmat'), + ('in_dwi', 'reference')]) + ,(inputnode, split, [('in_dwi', 'in_file')]) + ,(split, selref, [('out_files', 'inlist')]) + ,(warps, unwarp, [('out_file', 'field_file')]) + ,(split, unwarp, [('out_files', 'in_file')]) + ,(selref, unwarp, [('out', 'ref_file')]) + ,(selref, coeffs, [('out', 'reference')]) + ,(warps, coeffs, [('out_file', 'in_file')]) + ,(selref, jacobian, [('out', 'reference')]) + ,(coeffs, jacobian, [('out_file', 'in_file')]) + ,(unwarp, jacmult, [('out_file', 'in_file')]) + ,(jacobian, jacmult, [('out_jacobian', 'operand_files')]) + ,(jacmult, thres, [('out_file', 'in_file')]) + ,(thres, merge, [('out_file', 'in_files')]) + + ,(warps, outputnode, [('out_file', 'out_warp')]) + ,(coeffs, outputnode, [('out_file', 'out_coeff')]) + ,(jacobian, outputnode, [('out_jacobian', 'out_jacobian')]) + ,(merge, outputnode, [('merged_file', 'out_file')]) + ]) + return wf + + def recompose_dwi(in_dwi, in_bval, in_corrected, out_file=None): """ Recompose back the dMRI data accordingly the b-values table after EC correction @@ -79,6 +170,7 @@ def recompose_dwi(in_dwi, in_bval, in_corrected, out_file=None): nb.Nifti1Image(dwidata, im.get_affine(), im.get_header()).to_filename(out_file) return out_file + def recompose_xfm(in_bval, in_xfms): """ Insert identity transformation matrices in b0 volumes to build up a list @@ -97,7 +189,7 @@ def recompose_xfm(in_bval, in_xfms): else: mat = xfms.next() - out_name = 'eccor_%04d.mat' % i + out_name = op.abspath('eccor_%04d.mat' % i) out_files.append(out_name) np.savetxt(out_name, mat) @@ -151,7 +243,7 @@ def rotate_bvecs(in_bvec, in_matrix): name, fext = os.path.splitext(os.path.basename(in_bvec)) if fext == '.gz': name, _ = os.path.splitext(name) - out_file = os.path.abspath('./%s_rotated.bvec' % name) + out_file = os.path.abspath('%s_rotated.bvec' % name) bvecs = np.loadtxt(in_bvec).T new_bvecs = [] @@ -293,6 +385,7 @@ def rads2radsec(in_file, delta_te, out_file=None): im.get_header()).to_filename(out_file) return out_file + def demean_image(in_file, in_mask=None, out_file=None): """ Demean image data inside mask @@ -343,3 +436,50 @@ def add_empty_vol(in_file, out_file=None): im.get_header()) nb.funcs.concat_images([im, zim]).to_filename(out_file) return out_file + + +def reorient_bvecs(in_dwi, old_dwi, in_bvec): + """ + Checks reorientations of ``in_dwi`` w.r.t. ``old_dwi`` and + reorients the in_bvec table accordingly. + """ + import os + import numpy as np + import nibabel as nb + + name, fext = os.path.splitext(os.path.basename(in_bvec)) + if fext == '.gz': + name, _ = os.path.splitext(name) + out_file = os.path.abspath('%s_reorient.bvec' % name) + bvecs = np.loadtxt(in_bvec).T + new_bvecs = [] + + N = nb.load(in_dwi).get_affine() + O = nb.load(old_dwi).get_affine() + RS = N.dot(np.linalg.inv(O))[:3, :3] + sc_idx = np.where((np.abs(RS) != 1) & (RS != 0)) + S = np.ones_like(RS) + S[sc_idx] = RS[sc_idx] + R = RS/S + + new_bvecs = [R.dot(b) for b in bvecs] + np.savetxt(out_file, np.array(new_bvecs).T, fmt='%0.15f') + return out_file + + +def copy_hdr(in_file, in_file_hdr, out_file=None): + import numpy as np + import nibabel as nb + import os.path as op + + if out_file is None: + fname, fext = op.splitext(op.basename(in_file)) + if fext == '.gz': + fname, _ = op.splitext(fname) + out_file = op.abspath('./%s_fixhdr.nii.gz' % fname) + + imref = nb.load(in_file_hdr) + nii = nb.Nifti1Image(nb.load(in_file).get_data(), + imref.get_affine(), imref.get_header()) + nii.to_filename(out_file) + return out_file From 3f4082ca068aa794dcf7613dcc1c24fadc8b0dda Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Wed, 3 Sep 2014 12:26:03 +0200 Subject: [PATCH 14/33] all_fmb_pipeline now accepts parameters --- nipype/workflows/dmri/preprocess/epi.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/nipype/workflows/dmri/preprocess/epi.py b/nipype/workflows/dmri/preprocess/epi.py index fcfac87fb8..da195c3198 100644 --- a/nipype/workflows/dmri/preprocess/epi.py +++ b/nipype/workflows/dmri/preprocess/epi.py @@ -12,7 +12,12 @@ from .utils import * -def all_fmb_pipeline(name='hmc_sdc_ecc'): +def all_fmb_pipeline(name='hmc_sdc_ecc', + fugue_params=dict(smooth3d=2.0), + bmap_params=dict(delta_te=2.46e-3), + epi_params=dict(echospacing=0.77e-3, + acc_factor=3, + enc_dir='y-')): """ Builds a pipeline including three artifact corrections: head-motion correction (HMC), susceptibility-derived distortion correction (SDC), and Eddy currents-derived distortion @@ -40,7 +45,8 @@ def all_fmb_pipeline(name='hmc_sdc_ecc'): bet_dwi1 = pe.Node(fsl.BET(frac=0.3, mask=True, robust=True), name='bet_dwi_post') hmc = hmc_pipeline() - sdc = sdc_fmb() + sdc = sdc_fmb(fugue_params=fugue_params, bmap_params=bmap_params, + epi_params=epi_params) ecc = ecc_pipeline() unwarp = apply_all_corrections() From 3b6961d294ef1e1594084c923e47abf31220421f Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Wed, 3 Sep 2014 16:40:44 +0200 Subject: [PATCH 15/33] added missing connection in remove_bias --- nipype/workflows/dmri/preprocess/epi.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nipype/workflows/dmri/preprocess/epi.py b/nipype/workflows/dmri/preprocess/epi.py index da195c3198..3737a771b9 100644 --- a/nipype/workflows/dmri/preprocess/epi.py +++ b/nipype/workflows/dmri/preprocess/epi.py @@ -690,6 +690,7 @@ def remove_bias(name='bias_correct'): ,(split, mult, [('out_files', 'in_file')]) ,(mult, thres, [('out_file', 'in_file')]) ,(thres, merge, [('out_file', 'in_files')]) + ,(merge, outputnode, [('merged_file', 'out_file')]) ]) return wf From 3874713968637b87ee90c1de5bcbcaddb0e77a0c Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Wed, 3 Sep 2014 18:07:54 +0200 Subject: [PATCH 16/33] histogram equalization before hmc --- nipype/workflows/dmri/preprocess/epi.py | 26 +++++++++----- nipype/workflows/dmri/preprocess/utils.py | 44 ++++++++++++++++++++--- 2 files changed, 57 insertions(+), 13 deletions(-) diff --git a/nipype/workflows/dmri/preprocess/epi.py b/nipype/workflows/dmri/preprocess/epi.py index 3737a771b9..ace60d508c 100644 --- a/nipype/workflows/dmri/preprocess/epi.py +++ b/nipype/workflows/dmri/preprocess/epi.py @@ -229,7 +229,7 @@ def hmc_pipeline(name='motion_correct'): them to one reference image. Finally, the `b`-matrix is rotated accordingly [Leemans09]_ making use of the rotation matrix obtained by FLIRT. - Search angles have been limited to 3.5 degrees, based on results in [Yendiki13]_. + Search angles have been limited to 4 degrees, based on results in [Yendiki13]_. A list of rigid transformation matrices is provided, so that transforms can be chained. This is useful to correct for artifacts with only one interpolation process (as @@ -276,18 +276,24 @@ def hmc_pipeline(name='motion_correct'): outputnode.out_xfms - list of transformation matrices """ - inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'ref_num', 'in_bvec', - 'in_mask']), name='inputnode') + inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'ref_num', + 'in_bvec', 'in_mask']), name='inputnode') split = pe.Node(fsl.Split(dimension='t'), name='SplitDWIs') pick_ref = pe.Node(niu.Select(), name='Pick_b0') + enhb0 = pe.Node(niu.Function(input_names=['in_file'], + output_names=['out_file'], function=enhance), + name='B0Equalize') + enhdw = pe.MapNode(niu.Function(input_names=['in_file'], + output_names=['out_file'], function=enhance), + name='DWEqualize', iterfield=['in_file']) flirt = pe.MapNode(fsl.FLIRT(interp='spline', cost='normmi', - cost_func = 'normmi', dof=6, bins=64, save_log=True, - searchr_x=[-4,4], searchr_y=[-4,4], searchr_z=[-4,4], + cost_func='normmi', dof=6, bins=64, save_log=True, + searchr_x=[-4, 4], searchr_y=[-4, 4], searchr_z=[-4, 4], fine_search=1, coarse_search=10, padding_size=1), name='CoRegistration', iterfield=['in_file']) rot_bvec = pe.Node(niu.Function(input_names=['in_bvec', 'in_matrix'], - output_names=['out_file'], function=rotate_bvecs), - name='Rotate_Bvec') + output_names=['out_file'], function=rotate_bvecs), + name='Rotate_Bvec') thres = pe.MapNode(fsl.Threshold(thresh=0.0), iterfield=['in_file'], name='RemoveNegative') merge = pe.Node(fsl.Merge(dimension='t'), name='MergeDWIs') @@ -301,10 +307,12 @@ def hmc_pipeline(name='motion_correct'): ,(split, pick_ref, [('out_files', 'inlist')]) ,(inputnode, pick_ref, [(('ref_num', _checkrnum), 'index')]) ,(inputnode, flirt, [('in_mask', 'ref_weight')]) - ,(split, flirt, [('out_files', 'in_file')]) + ,(pick_ref, enhb0, [('out', 'in_file')]) + ,(split, enhdw, [('out_files', 'in_file')]) + ,(enhb0, flirt, [('out_file', 'reference')]) + ,(enhdw, flirt, [('out_file', 'in_file')]) ,(inputnode, rot_bvec, [('in_bvec', 'in_bvec')]) ,(flirt, rot_bvec, [('out_matrix_file', 'in_matrix')]) - ,(pick_ref, flirt, [('out', 'reference')]) ,(flirt, thres, [('out_file', 'in_file')]) ,(thres, merge, [('out_file', 'in_files')]) ,(merge, outputnode, [('merged_file', 'out_file')]) diff --git a/nipype/workflows/dmri/preprocess/utils.py b/nipype/workflows/dmri/preprocess/utils.py index 36e2c39a8a..1b489be886 100644 --- a/nipype/workflows/dmri/preprocess/utils.py +++ b/nipype/workflows/dmri/preprocess/utils.py @@ -4,12 +4,13 @@ # vi: set ft=python sts=4 ts=4 sw=4 et: # @Author: oesteban # @Date: 2014-08-30 10:53:13 -# @Last Modified by: Oscar Esteban -# @Last Modified time: 2014-09-02 19:52:57 +# @Last Modified by: oesteban +# @Last Modified time: 2014-09-03 18:03:47 import nipype.pipeline.engine as pe import nipype.interfaces.utility as niu from nipype.interfaces import fsl + def cleanup_edge_pipeline(name='Cleanup'): """ Perform some de-spiking filtering to clean up the edge of the fieldmap @@ -20,8 +21,8 @@ def cleanup_edge_pipeline(name='Cleanup'): outputnode = pe.Node(niu.IdentityInterface(fields=['out_file']), name='outputnode') - fugue = pe.Node(fsl.FUGUE(save_fmap=True, despike_2dfilter=True, despike_threshold=2.1), - name='Despike') + fugue = pe.Node(fsl.FUGUE(save_fmap=True, despike_2dfilter=True, + despike_threshold=2.1), name='Despike') erode = pe.Node(fsl.maths.MathsCommand(nan2zeros=True, args='-kernel 2D -ero'), name='MskErode') newmsk = pe.Node(fsl.MultiImageMaths(op_string='-sub %s -thr 0.5 -bin'), @@ -364,6 +365,7 @@ def siemens2rads(in_file, out_file=None): nb.Nifti1Image(data, im.get_affine(), hdr).to_filename(out_file) return out_file + def rads2radsec(in_file, delta_te, out_file=None): """ Converts input phase difference map to rads @@ -483,3 +485,37 @@ def copy_hdr(in_file, in_file_hdr, out_file=None): imref.get_affine(), imref.get_header()) nii.to_filename(out_file) return out_file + + +def enhance(in_file, clip_limit=0.015, in_mask=None, out_file=None): + import numpy as np + import nibabel as nb + import os.path as op + from skimage import exposure, img_as_int + + if out_file is None: + fname, fext = op.splitext(op.basename(in_file)) + if fext == '.gz': + fname, _ = op.splitext(fname) + out_file = op.abspath('./%s_enh.nii.gz' % fname) + + im = nb.load(in_file) + imdata = im.get_data() + imshape = im.get_shape() + + if in_mask is not None: + msk = nb.load(in_mask).get_data() + msk[msk > 0] = 1 + msk[msk < 1] = 0 + imdata = imdata * msk + + immin = imdata.min() + imdata = (imdata - immin).astype(np.uint16) + + adapted = exposure.equalize_adapthist(imdata.reshape(imshape[0], -1), + clip_limit=clip_limit) + + nb.Nifti1Image(adapted.reshape(imshape), im.get_affine(), + im.get_header()).to_filename(out_file) + + return out_file From e6760fc1ea56e05892cc2500bd3c1fddd8197bf3 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Wed, 3 Sep 2014 20:09:11 +0200 Subject: [PATCH 17/33] removed redundant code in registrations --- nipype/workflows/dmri/preprocess/epi.py | 119 ++++++++----------- nipype/workflows/dmri/preprocess/utils.py | 133 ++++++++++++++++++++-- 2 files changed, 171 insertions(+), 81 deletions(-) diff --git a/nipype/workflows/dmri/preprocess/epi.py b/nipype/workflows/dmri/preprocess/epi.py index ace60d508c..7d381d4deb 100644 --- a/nipype/workflows/dmri/preprocess/epi.py +++ b/nipype/workflows/dmri/preprocess/epi.py @@ -53,7 +53,8 @@ def all_fmb_pipeline(name='hmc_sdc_ecc', wf = pe.Workflow('dMRI_Artifacts') wf.connect([ (inputnode, hmc, [('in_file', 'inputnode.in_file'), - ('in_bvec', 'inputnode.in_bvec')]) + ('in_bvec', 'inputnode.in_bvec'), + ('in_bval', 'inputnode.in_bval')]) ,(inputnode, avg_b0_0, [('in_file', 'in_dwi'), ('in_bval', 'in_bval')]) ,(avg_b0_0, bet_dwi0, [('out_file', 'in_file')]) @@ -120,7 +121,8 @@ def all_peb_pipeline(name='hmc_sdc_ecc', wf = pe.Workflow('dMRI_Artifacts') wf.connect([ (inputnode, hmc, [('in_file', 'inputnode.in_file'), - ('in_bvec', 'inputnode.in_bvec')]) + ('in_bvec', 'inputnode.in_bvec'), + ('in_bval', 'inputnode.in_bval')]) ,(inputnode, avg_b0_0, [('in_file', 'in_dwi'), ('in_bval', 'in_bval')]) ,(avg_b0_0, bet_dwi0, [('out_file','in_file')]) @@ -257,6 +259,7 @@ def hmc_pipeline(name='motion_correct'): >>> hmc = hmc_pipeline() >>> hmc.inputs.inputnode.in_file = 'diffusion.nii' >>> hmc.inputs.inputnode.in_bvec = 'diffusion.bvec' + >>> hmc.inputs.inputnode.in_bval = 'diffusion.bval' >>> hmc.inputs.inputnode.in_mask = 'mask.nii' >>> hmc.run() # doctest: +SKIP @@ -276,48 +279,35 @@ def hmc_pipeline(name='motion_correct'): outputnode.out_xfms - list of transformation matrices """ + params = dict(interp='spline', cost='normmi', + cost_func='normmi', dof=6, bins=64, save_log=True, + searchr_x=[-4, 4], searchr_y=[-4, 4], searchr_z=[-4, 4], + fine_search=1, coarse_search=10, padding_size=1) + inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'ref_num', - 'in_bvec', 'in_mask']), name='inputnode') - split = pe.Node(fsl.Split(dimension='t'), name='SplitDWIs') - pick_ref = pe.Node(niu.Select(), name='Pick_b0') - enhb0 = pe.Node(niu.Function(input_names=['in_file'], - output_names=['out_file'], function=enhance), - name='B0Equalize') - enhdw = pe.MapNode(niu.Function(input_names=['in_file'], - output_names=['out_file'], function=enhance), - name='DWEqualize', iterfield=['in_file']) - flirt = pe.MapNode(fsl.FLIRT(interp='spline', cost='normmi', - cost_func='normmi', dof=6, bins=64, save_log=True, - searchr_x=[-4, 4], searchr_y=[-4, 4], searchr_z=[-4, 4], - fine_search=1, coarse_search=10, padding_size=1), - name='CoRegistration', iterfield=['in_file']) + 'in_bvec', 'in_bval', 'in_mask']), name='inputnode') + pick_ref = pe.Node(fsl.ExtractROI(t_size=1), name='GetB0') + flirt = dwi_flirt(flirt_param=params) rot_bvec = pe.Node(niu.Function(input_names=['in_bvec', 'in_matrix'], output_names=['out_file'], function=rotate_bvecs), name='Rotate_Bvec') - thres = pe.MapNode(fsl.Threshold(thresh=0.0), iterfield=['in_file'], - name='RemoveNegative') - merge = pe.Node(fsl.Merge(dimension='t'), name='MergeDWIs') outputnode = pe.Node(niu.IdentityInterface(fields=['out_file', 'out_bvec', 'out_xfms']), name='outputnode') wf = pe.Workflow(name=name) wf.connect([ - (inputnode, split, [('in_file', 'in_file')]) - ,(split, pick_ref, [('out_files', 'inlist')]) - ,(inputnode, pick_ref, [(('ref_num', _checkrnum), 'index')]) - ,(inputnode, flirt, [('in_mask', 'ref_weight')]) - ,(pick_ref, enhb0, [('out', 'in_file')]) - ,(split, enhdw, [('out_files', 'in_file')]) - ,(enhb0, flirt, [('out_file', 'reference')]) - ,(enhdw, flirt, [('out_file', 'in_file')]) + (inputnode, pick_ref, [('in_file', 'in_file'), + (('ref_num', _checkrnum), 't_min')]) + ,(inputnode, flirt, [('in_file', 'inputnode.in_file'), + ('in_mask', 'inputnode.ref_mask'), + ('in_bval', 'inputnode.in_bval')]) + ,(pick_ref, flirt, [('roi_file', 'inputnode.reference')]) ,(inputnode, rot_bvec, [('in_bvec', 'in_bvec')]) - ,(flirt, rot_bvec, [('out_matrix_file', 'in_matrix')]) - ,(flirt, thres, [('out_file', 'in_file')]) - ,(thres, merge, [('out_file', 'in_files')]) - ,(merge, outputnode, [('merged_file', 'out_file')]) + ,(flirt, rot_bvec, [('outputnode.out_xfms', 'in_matrix')]) ,(rot_bvec, outputnode, [('out_file', 'out_bvec')]) - ,(flirt, outputnode, [('out_matrix_file', 'out_xfms')]) + ,(flirt, outputnode, [('outputnode.out_xfms', 'out_xfms'), + ('outputnode.out_file', 'out_file')]) ]) return wf @@ -376,19 +366,21 @@ def ecc_pipeline(name='eddy_correct'): outputnode.out_file - corrected dwi file outputnode.out_xfms - list of transformation matrices """ + params = dict(no_search=True, interp='spline', cost='normmi', + cost_func='normmi', dof=12, bins=64, + padding_size=1) + inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_bval', 'in_mask', 'in_xfms']), name='inputnode') - split = pe.Node(fsl.Split(dimension='t'), name='SplitDWIs') avg_b0 = pe.Node(niu.Function(input_names=['in_dwi', 'in_bval'], - output_names=['out_file'], function=b0_average), name='b0_avg') - pick_dwi = pe.Node(niu.Select(), name='Pick_DWIs') - flirt = pe.MapNode(fsl.FLIRT(no_search=True, interp='spline', cost='normmi', - cost_func = 'normmi', dof=12, bins=64, save_log=True, - padding_size=1), name='CoRegistration', - iterfield=['in_file', 'in_matrix_file']) - initmat = pe.Node(niu.Function(input_names=['in_bval', 'in_xfms'], - output_names=['init_xfms'], function=_checkinitxfm), - name='InitXforms') + output_names=['out_file'], function=b0_average), + name='b0_avg') + pick_dws = pe.Node(niu.Function(input_names=['in_dwi', 'in_bval', 'b'], + output_names=['out_file'], function=extract_bval), + name='ExtractDWI') + pick_dws.inputs.b = 'diff' + + flirt = dwi_flirt(flirt_param=params, excl_nodiff=True) mult = pe.MapNode(fsl.BinaryMaths(operation='mul'), name='ModulateDWIs', iterfield=['in_file', 'operand_value']) @@ -406,23 +398,22 @@ def ecc_pipeline(name='eddy_correct'): wf = pe.Workflow(name=name) wf.connect([ - (inputnode, split, [('in_file', 'in_file')]) - ,(inputnode, avg_b0, [('in_file', 'in_dwi'), + (inputnode, avg_b0, [('in_file', 'in_dwi'), ('in_bval', 'in_bval')]) - ,(inputnode, merge, [('in_file', 'in_dwi'), + ,(inputnode, pick_dws, [('in_file', 'in_dwi'), ('in_bval', 'in_bval')]) - ,(inputnode, initmat, [('in_xfms', 'in_xfms'), + ,(inputnode, merge, [('in_file', 'in_dwi'), ('in_bval', 'in_bval')]) + ,(inputnode, flirt, [('in_mask', 'inputnode.ref_mask'), + ('in_xfms', 'inputnode.in_xfms'), + ('in_bval', 'inputnode.in_bval')]) ,(inputnode, get_mat, [('in_bval', 'in_bval')]) - ,(split, pick_dwi, [('out_files', 'inlist')]) - ,(inputnode, pick_dwi, [(('in_bval', _nonb0), 'index')]) - ,(inputnode, flirt, [('in_mask', 'ref_weight')]) - ,(avg_b0, flirt, [('out_file', 'reference')]) - ,(pick_dwi, flirt, [('out', 'in_file')]) - ,(initmat, flirt, [('init_xfms', 'in_matrix_file')]) - ,(flirt, get_mat, [('out_matrix_file', 'in_xfms')]) - ,(flirt, mult, [(('out_matrix_file',_xfm_jacobian), 'operand_value')]) - ,(flirt, mult, [('out_file', 'in_file')]) + ,(avg_b0, flirt, [('out_file', 'inputnode.reference')]) + ,(pick_dws, flirt, [('out_file', 'inputnode.in_file')]) + ,(flirt, get_mat, [('outputnode.out_xfms', 'in_xfms')]) + ,(flirt, mult, [(('outputnode.out_xfms',_xfm_jacobian), + 'operand_value')]) + ,(flirt, mult, [('outputnode.out_file', 'in_file')]) ,(mult, thres, [('out_file', 'in_file')]) ,(thres, merge, [('out_file', 'in_corrected')]) ,(get_mat, outputnode, [('out_files', 'out_xfms')]) @@ -710,24 +701,6 @@ def _checkrnum(ref_num): return ref_num -def _checkinitxfm(in_bval, in_xfms=None): - from nipype.interfaces.base import isdefined - import numpy as np - import os.path as op - bvals = np.loadtxt(in_bval) - non_b0 = np.where(bvals != 0)[0].tolist() - - init_xfms = [] - if (in_xfms is None) or (not isdefined(in_xfms)) or (len(in_xfms)!=len(bvals)): - for i in non_b0: - xfm_file = op.abspath('init_%04d.mat' % i) - np.savetxt(xfm_file, np.eye(4)) - init_xfms.append(xfm_file) - else: - for i in non_b0: - init_xfms.append(in_xfms[i]) - return init_xfms - def _nonb0(in_bval): import numpy as np bvals = np.loadtxt(in_bval) diff --git a/nipype/workflows/dmri/preprocess/utils.py b/nipype/workflows/dmri/preprocess/utils.py index 1b489be886..0bbb5de165 100644 --- a/nipype/workflows/dmri/preprocess/utils.py +++ b/nipype/workflows/dmri/preprocess/utils.py @@ -5,10 +5,11 @@ # @Author: oesteban # @Date: 2014-08-30 10:53:13 # @Last Modified by: oesteban -# @Last Modified time: 2014-09-03 18:03:47 +# @Last Modified time: 2014-09-03 20:04:13 import nipype.pipeline.engine as pe import nipype.interfaces.utility as niu from nipype.interfaces import fsl +from nipype.interfaces import ants def cleanup_edge_pipeline(name='Cleanup'): @@ -80,6 +81,59 @@ def vsm2warp(name='Shiftmap2Warping'): return wf +def dwi_flirt(name='DWICoregistration', excl_nodiff=False, + flirt_param={}): + """ + Generates a workflow for linear registration of dwi volumes + """ + inputnode = pe.Node(niu.IdentityInterface(fields=['reference', + 'in_file', 'ref_mask', 'in_xfms', 'in_bval']), + name='inputnode') + + initmat = pe.Node(niu.Function(input_names=['in_bval', 'in_xfms', + 'excl_nodiff'], output_names=['init_xfms'], + function=_checkinitxfm), name='InitXforms') + initmat.inputs.excl_nodiff = excl_nodiff + + split = pe.Node(fsl.Split(dimension='t'), name='SplitDWIs') + pick_ref = pe.Node(niu.Select(), name='Pick_b0') + n4 = pe.Node(ants.N4BiasFieldCorrection(dimension=3), name='Bias') + enhb0 = pe.Node(niu.Function(input_names=['in_file', 'in_mask', + 'clip_limit'], output_names=['out_file'], + function=enhance), name='B0Equalize') + enhb0.inputs.clip_limit = 0.02 + enhdw = pe.MapNode(niu.Function(input_names=['in_file'], + output_names=['out_file'], function=enhance), + name='DWEqualize', iterfield=['in_file']) + flirt = pe.MapNode(fsl.FLIRT(**flirt_param), name='CoRegistration', + iterfield=['in_file', 'in_matrix_file']) + thres = pe.MapNode(fsl.Threshold(thresh=0.0), iterfield=['in_file'], + name='RemoveNegative') + merge = pe.Node(fsl.Merge(dimension='t'), name='MergeDWIs') + outputnode = pe.Node(niu.IdentityInterface(fields=['out_file', + 'out_xfms']), name='outputnode') + wf = pe.Workflow(name=name) + wf.connect([ + (inputnode, split, [('in_file', 'in_file')]) + ,(inputnode, enhb0, [('ref_mask', 'in_mask')]) + ,(inputnode, initmat, [('in_xfms', 'in_xfms'), + ('in_bval', 'in_bval')]) + ,(inputnode, n4, [('reference', 'input_image'), + ('ref_mask', 'mask_image')]) + ,(inputnode, flirt, [('ref_mask', 'ref_weight')]) + ,(n4, enhb0, [('output_image', 'in_file')]) + ,(split, enhdw, [('out_files', 'in_file')]) + ,(enhb0, flirt, [('out_file', 'reference')]) + ,(enhdw, flirt, [('out_file', 'in_file')]) + ,(initmat, flirt, [('init_xfms', 'in_matrix_file')]) + ,(flirt, thres, [('out_file', 'in_file')]) + ,(thres, merge, [('out_file', 'in_files')]) + ,(merge, outputnode, [('merged_file', 'out_file')]) + ,(flirt, outputnode, [('out_matrix_file', 'out_xfms')]) + ]) + return wf + + def apply_all_corrections(name='UnwarpArtifacts'): """ Combines two lists of linear transforms with the deformation field @@ -142,6 +196,41 @@ def apply_all_corrections(name='UnwarpArtifacts'): return wf +def extract_bval(in_dwi, in_bval, b=0, out_file=None): + """ + Writes an image containing only the volumes with b-value specified at + input + """ + import numpy as np + import nibabel as nb + import os.path as op + + if out_file is None: + fname, ext = op.splitext(op.basename(in_dwi)) + if ext == ".gz": + fname, ext2 = op.splitext(fname) + ext = ext2 + ext + out_file = op.abspath("%s_tsoi%s" % (fname, ext)) + + im = nb.load(in_dwi) + dwidata = im.get_data() + bvals = np.loadtxt(in_bval) + + if b == 'diff': + selection = np.where(bvals != 0) + elif b == 'nodiff': + selection = np.where(bvals == 0) + else: + selection = np.where(bvals == b) + + extdata = np.squeeze(dwidata.take(selection, axis=3)) + hdr = im.get_header().copy() + hdr.set_data_shape(extdata.shape) + nb.Nifti1Image(extdata, im.get_affine(), + hdr).to_filename(out_file) + return out_file + + def recompose_dwi(in_dwi, in_bval, in_corrected, out_file=None): """ Recompose back the dMRI data accordingly the b-values table after EC correction @@ -151,24 +240,25 @@ def recompose_dwi(in_dwi, in_bval, in_corrected, out_file=None): import os.path as op if out_file is None: - fname,ext = op.splitext(op.basename(in_dwi)) + fname, ext = op.splitext(op.basename(in_dwi)) if ext == ".gz": - fname,ext2 = op.splitext(fname) + fname, ext2 = op.splitext(fname) ext = ext2 + ext out_file = op.abspath("%s_eccorrect%s" % (fname, ext)) im = nb.load(in_dwi) dwidata = im.get_data() bvals = np.loadtxt(in_bval) - non_b0 = np.where(bvals!=0)[0].tolist() + dwis = np.where(bvals != 0)[0].tolist() - if len(non_b0)!=len(in_corrected): + if len(dwis) != len(in_corrected): raise RuntimeError('Length of DWIs in b-values table and after correction should match') - for bindex, dwi in zip(non_b0, in_corrected): - dwidata[...,bindex] = nb.load(dwi).get_data() + for bindex, dwi in zip(dwis, in_corrected): + dwidata[..., bindex] = nb.load(dwi).get_data() - nb.Nifti1Image(dwidata, im.get_affine(), im.get_header()).to_filename(out_file) + nb.Nifti1Image(dwidata, im.get_affine(), + im.get_header()).to_filename(out_file) return out_file @@ -519,3 +609,30 @@ def enhance(in_file, clip_limit=0.015, in_mask=None, out_file=None): im.get_header()).to_filename(out_file) return out_file + + +def _checkinitxfm(in_bval, excl_nodiff, in_xfms=None): + from nipype.interfaces.base import isdefined + import numpy as np + import os.path as op + bvals = np.loadtxt(in_bval) + + gen_id = ((in_xfms is None) or + (not isdefined(in_xfms)) or + (len(in_xfms) != len(bvals))) + + init_xfms = [] + if excl_nodiff: + dws = np.where(bvals != 0)[0].tolist() + else: + dws = range(len(bvals)) + + if gen_id: + for i in dws: + xfm_file = op.abspath('init_%04d.mat' % i) + np.savetxt(xfm_file, np.eye(4)) + init_xfms.append(xfm_file) + else: + init_xfms = [in_xfms[i] for i in dws] + + return init_xfms From b9b122fbea860503862baad3f92eb6be234792e2 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Wed, 3 Sep 2014 20:31:33 +0200 Subject: [PATCH 18/33] fix missing node --- nipype/workflows/dmri/preprocess/epi.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nipype/workflows/dmri/preprocess/epi.py b/nipype/workflows/dmri/preprocess/epi.py index 7d381d4deb..c7a6cbed7d 100644 --- a/nipype/workflows/dmri/preprocess/epi.py +++ b/nipype/workflows/dmri/preprocess/epi.py @@ -387,6 +387,7 @@ def ecc_pipeline(name='eddy_correct'): thres = pe.MapNode(fsl.Threshold(thresh=0.0), iterfield=['in_file'], name='RemoveNegative') + split = pe.Node(fsl.Split(dimension='t'), name='SplitDWIs') get_mat = pe.Node(niu.Function(input_names=['in_bval', 'in_xfms'], output_names=['out_files'], function=recompose_xfm), name='GatherMatrices') @@ -413,7 +414,8 @@ def ecc_pipeline(name='eddy_correct'): ,(flirt, get_mat, [('outputnode.out_xfms', 'in_xfms')]) ,(flirt, mult, [(('outputnode.out_xfms',_xfm_jacobian), 'operand_value')]) - ,(flirt, mult, [('outputnode.out_file', 'in_file')]) + ,(flirt, split, [('outputnode.out_file', 'in_file')]) + ,(split, mult, [('out_files', 'in_file')]) ,(mult, thres, [('out_file', 'in_file')]) ,(thres, merge, [('out_file', 'in_corrected')]) ,(get_mat, outputnode, [('out_files', 'out_xfms')]) From 45aac9ac80e0901a686bf22d928de5977f355e7e Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Wed, 3 Sep 2014 21:33:18 +0200 Subject: [PATCH 19/33] fixed inconsistency connecting ecc --- nipype/workflows/dmri/preprocess/epi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nipype/workflows/dmri/preprocess/epi.py b/nipype/workflows/dmri/preprocess/epi.py index c7a6cbed7d..da4204335f 100644 --- a/nipype/workflows/dmri/preprocess/epi.py +++ b/nipype/workflows/dmri/preprocess/epi.py @@ -64,9 +64,9 @@ def all_fmb_pipeline(name='hmc_sdc_ecc', ,(inputnode, sdc, [('in_bval', 'inputnode.in_bval'), ('bmap_pha', 'inputnode.bmap_pha'), ('bmap_mag', 'inputnode.bmap_mag')]) - ,(inputnode, ecc, [('in_bval', 'inputnode.in_bval')]) + ,(inputnode, ecc, [('in_file', 'inputnode.in_file'), + ('in_bval', 'inputnode.in_bval')]) ,(bet_dwi0, ecc, [('mask_file', 'inputnode.in_mask')]) - ,(sdc, ecc, [('outputnode.out_file', 'inputnode.in_file')]) ,(ecc, avg_b0_1, [('outputnode.out_file', 'in_dwi')]) ,(inputnode, avg_b0_1, [('in_bval', 'in_bval')]) ,(avg_b0_1, bet_dwi1, [('out_file', 'in_file')]) From 32577f2e5ab85219fe162dbdfeda1fc770f28b34 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Wed, 3 Sep 2014 21:34:45 +0200 Subject: [PATCH 20/33] fixed inconsistency connecting ecc --- nipype/workflows/dmri/preprocess/epi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nipype/workflows/dmri/preprocess/epi.py b/nipype/workflows/dmri/preprocess/epi.py index da4204335f..19d3d82176 100644 --- a/nipype/workflows/dmri/preprocess/epi.py +++ b/nipype/workflows/dmri/preprocess/epi.py @@ -131,9 +131,9 @@ def all_peb_pipeline(name='hmc_sdc_ecc', ,(bet_dwi0, sdc, [('mask_file', 'inputnode.in_mask')]) ,(inputnode, sdc, [('in_bval', 'inputnode.in_bval'), ('alt_file', 'inputnode.alt_file')]) - ,(inputnode, ecc, [('in_bval', 'inputnode.in_bval')]) + ,(inputnode, ecc, [('in_file', 'inputnode.in_file'), + ('in_bval', 'inputnode.in_bval')]) ,(bet_dwi0, ecc, [('mask_file', 'inputnode.in_mask')]) - ,(sdc, ecc, [('outputnode.out_file', 'inputnode.in_file')]) ,(ecc, avg_b0_1, [('outputnode.out_file', 'in_dwi')]) ,(inputnode, avg_b0_1, [('in_bval', 'in_bval')]) ,(avg_b0_1, bet_dwi1, [('out_file','in_file')]) From cf912a58264d7bd92c1f6ad09f838c4de639e72d Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Thu, 4 Sep 2014 10:41:52 +0200 Subject: [PATCH 21/33] all_peb_pipeline with only one interpolation --- nipype/workflows/dmri/preprocess/epi.py | 93 ++++++++++++++++++------- 1 file changed, 68 insertions(+), 25 deletions(-) diff --git a/nipype/workflows/dmri/preprocess/epi.py b/nipype/workflows/dmri/preprocess/epi.py index 19d3d82176..3a78fc39f5 100644 --- a/nipype/workflows/dmri/preprocess/epi.py +++ b/nipype/workflows/dmri/preprocess/epi.py @@ -104,8 +104,8 @@ def all_peb_pipeline(name='hmc_sdc_ecc', inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_bvec', 'in_bval', 'alt_file']), name='inputnode') - outputnode = pe.Node(niu.IdentityInterface(fields=['out_file', 'out_mask', 'out_bvec']), - name='outputnode') + outputnode = pe.Node(niu.IdentityInterface(fields=['out_file', 'out_mask', + 'out_bvec']), name='outputnode') avg_b0_0 = pe.Node(niu.Function(input_names=['in_dwi', 'in_bval'], output_names=['out_file'], function=b0_average), name='b0_avg_pre') @@ -118,6 +118,8 @@ def all_peb_pipeline(name='hmc_sdc_ecc', sdc = sdc_peb(epi_params=epi_params, altepi_params=altepi_params) ecc = ecc_pipeline() + unwarp = apply_all_corrections() + wf = pe.Workflow('dMRI_Artifacts') wf.connect([ (inputnode, hmc, [('in_file', 'inputnode.in_file'), @@ -137,8 +139,15 @@ def all_peb_pipeline(name='hmc_sdc_ecc', ,(ecc, avg_b0_1, [('outputnode.out_file', 'in_dwi')]) ,(inputnode, avg_b0_1, [('in_bval', 'in_bval')]) ,(avg_b0_1, bet_dwi1, [('out_file','in_file')]) - ,(ecc, outputnode, [('outputnode.out_file', 'out_file')]) + + + ,(inputnode, unwarp, [('in_file', 'inputnode.in_dwi')]) + ,(hmc, unwarp, [('outputnode.out_xfms', 'inputnode.in_hmc')]) + ,(ecc, unwarp, [('outputnode.out_xfms', 'inputnode.in_ecc')]) + ,(sdc, unwarp, [('outputnode.out_warp', 'inputnode.in_sdc')]) + ,(hmc, outputnode, [('outputnode.out_bvec', 'out_bvec')]) + ,(unwarp, outputnode, [('outputnode.out_file', 'out_file')]) ,(bet_dwi1, outputnode, [('mask_file', 'out_mask')]) ]) return wf @@ -575,13 +584,16 @@ def sdc_peb(name='peb_correction', enc_dir='y', epi_factor=1)): """ - SDC stands for susceptibility distortion correction. PEB stands for phase-encoding-based. - - The phase-encoding-based (PEB) method implements SDC by acquiring diffusion images with - two different enconding directions [Andersson2003]_. The most typical case is acquiring - with opposed phase-gradient blips (e.g. *A>>>P* and *P>>>A*, or equivalently, *-y* and *y*) - as in [Chiou2000]_, but it is also possible to use orthogonal configurations [Cordes2000]_ - (e.g. *A>>>P* and *L>>>R*, or equivalently *-y* and *x*). + SDC stands for susceptibility distortion correction. PEB stands for + phase-encoding-based. + + The phase-encoding-based (PEB) method implements SDC by acquiring + diffusion images with two different enconding directions [Andersson2003]_. + The most typical case is acquiring with opposed phase-gradient blips + (e.g. *A>>>P* and *P>>>A*, or equivalently, *-y* and *y*) + as in [Chiou2000]_, but it is also possible to use orthogonal + configurations [Cordes2000]_ (e.g. *A>>>P* and *L>>>R*, + or equivalently *-y* and *x*). This workflow uses the implementation of FSL (`TOPUP `_). @@ -597,26 +609,26 @@ def sdc_peb(name='peb_correction', .. admonition:: References - .. [Andersson2003] Andersson JL et al., `How to correct susceptibility distortions in - spin-echo echo-planar images: application to diffusion tensor imaging - `_. + .. [Andersson2003] Andersson JL et al., `How to correct susceptibility + distortions in spin-echo echo-planar images: application to diffusion + tensor imaging `_. Neuroimage. 2003 Oct;20(2):870-88. doi: 10.1016/S1053-8119(03)00336-7 - .. [Cordes2000] Cordes D et al., Geometric distortion correction in EPI using two - images with orthogonal phase-encoding directions, in Proc. ISMRM (8), p.1712, - Denver, US, 2000. + .. [Cordes2000] Cordes D et al., Geometric distortion correction in EPI + using two images with orthogonal phase-encoding directions, in Proc. + ISMRM (8), p.1712, Denver, US, 2000. - .. [Chiou2000] Chiou JY, and Nalcioglu O, A simple method to correct off-resonance - related distortion in echo planar imaging, in Proc. ISMRM (8), p.1712, Denver, US, - 2000. + .. [Chiou2000] Chiou JY, and Nalcioglu O, A simple method to correct + off-resonance related distortion in echo planar imaging, in Proc. + ISMRM (8), p.1712, Denver, US, 2000. """ - inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_bval', 'in_mask', - 'alt_file', 'ref_num']), + inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_bval', + 'in_mask', 'alt_file', 'ref_num']), name='inputnode') - outputnode = pe.Node(niu.IdentityInterface(fields=['out_file', 'out_vsm']), - name='outputnode') + outputnode = pe.Node(niu.IdentityInterface(fields=['out_file', 'out_vsm', + 'out_warp']), name='outputnode') b0_ref = pe.Node(fsl.ExtractROI(t_size=1), name='b0_ref') b0_alt = pe.Node(fsl.ExtractROI(t_size=1), name='b0_alt') @@ -624,12 +636,20 @@ def sdc_peb(name='peb_correction', b0_merge = pe.Node(fsl.Merge(dimension='t'), name='b0_merged') topup = pe.Node(fsl.TOPUP(), name='topup') - topup.inputs.encoding_direction = [epi_params['enc_dir'], altepi_params['enc_dir']] + topup.inputs.encoding_direction = [epi_params['enc_dir'], + altepi_params['enc_dir']] topup.inputs.readout_times = [compute_readout(epi_params), compute_readout(altepi_params)] unwarp = pe.Node(fsl.ApplyTOPUP(in_index=[1], method='jac'), name='unwarp') + scaling = pe.Node(niu.Function(input_names=['in_file', 'enc_dir'], + output_names=['factor'], function=_get_zoom), + name='GetZoom') + scaling.inputs.enc_dir = epi_params['enc_dir'] + vsm2dfm = vsm2warp() + vsm2dfm.inputs.inputnode.enc_dir = epi_params['enc_dir'] + wf = pe.Workflow(name=name) wf.connect([ (inputnode, b0_ref, [('in_file', 'in_file'), @@ -645,6 +665,13 @@ def sdc_peb(name='peb_correction', ('out_enc_file', 'encoding_file')]) ,(inputnode, unwarp, [('in_file', 'in_files')]) ,(unwarp, outputnode, [('out_corrected', 'out_file')]) + + ,(b0_ref, scaling, [('roi_file', 'in_file')]) + ,(b0_ref, vsm2dfm, [('roi_file', 'inputnode.in_ref')]) + ,(scaling, vsm2dfm, [('factor', 'inputnode.scaling')]) + ,(topup, vsm2dfm, [('out_field', 'inputnode.in_vsm')]) + ,(topup, outputnode, [('out_field', 'out_vsm')]) + ,(vsm2dfm, outputnode, [('outputnode.out_warp', 'out_warp')]) ]) return wf @@ -706,9 +733,25 @@ def _checkrnum(ref_num): def _nonb0(in_bval): import numpy as np bvals = np.loadtxt(in_bval) - return np.where(bvals!=0)[0].tolist() + return np.where(bvals != 0)[0].tolist() + def _xfm_jacobian(in_xfm): import numpy as np from math import fabs return [fabs(np.linalg.det(np.loadtxt(xfm))) for xfm in in_xfm] + + +def _get_zoom(in_file, enc_dir): + import nibabel as nb + + zooms = nb.load(in_file).get_zooms() + + if 'y' in enc_dir: + return zooms[1] + elif 'x' in enc_dir: + return zooms[0] + elif 'z' in enc_dir: + return zooms[2] + else: + raise ValueError('Wrong encoding direction string') From 9c98d0ce049509a288288edb894278093cd1aa95 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Thu, 4 Sep 2014 17:04:57 +0200 Subject: [PATCH 22/33] fixed:hmc should not perform registration of ref volume --- examples/dmri_preprocessing.py | 3 +- nipype/workflows/dmri/preprocess/epi.py | 106 ++++++++++++++-------- nipype/workflows/dmri/preprocess/utils.py | 98 +++++++++++++++----- 3 files changed, 144 insertions(+), 63 deletions(-) diff --git a/examples/dmri_preprocessing.py b/examples/dmri_preprocessing.py index 90d06ee68c..64adc0c29b 100644 --- a/examples/dmri_preprocessing.py +++ b/examples/dmri_preprocessing.py @@ -3,7 +3,7 @@ # @Author: oesteban # @Date: 2014-08-31 20:32:22 # @Last Modified by: oesteban -# @Last Modified time: 2014-09-02 13:12:12 +# @Last Modified time: 2014-09-04 10:55:15 """ =================== dMRI: Preprocessing @@ -175,4 +175,3 @@ if __name__ == '__main__': wf.run() wf.write_graph() - diff --git a/nipype/workflows/dmri/preprocess/epi.py b/nipype/workflows/dmri/preprocess/epi.py index 3a78fc39f5..56d76ca457 100644 --- a/nipype/workflows/dmri/preprocess/epi.py +++ b/nipype/workflows/dmri/preprocess/epi.py @@ -127,7 +127,7 @@ def all_peb_pipeline(name='hmc_sdc_ecc', ('in_bval', 'inputnode.in_bval')]) ,(inputnode, avg_b0_0, [('in_file', 'in_dwi'), ('in_bval', 'in_bval')]) - ,(avg_b0_0, bet_dwi0, [('out_file','in_file')]) + ,(avg_b0_0, bet_dwi0, [('out_file', 'in_file')]) ,(bet_dwi0, hmc, [('mask_file', 'inputnode.in_mask')]) ,(hmc, sdc, [('outputnode.out_file', 'inputnode.in_file')]) ,(bet_dwi0, sdc, [('mask_file', 'inputnode.in_mask')]) @@ -138,7 +138,7 @@ def all_peb_pipeline(name='hmc_sdc_ecc', ,(bet_dwi0, ecc, [('mask_file', 'inputnode.in_mask')]) ,(ecc, avg_b0_1, [('outputnode.out_file', 'in_dwi')]) ,(inputnode, avg_b0_1, [('in_bval', 'in_bval')]) - ,(avg_b0_1, bet_dwi1, [('out_file','in_file')]) + ,(avg_b0_1, bet_dwi1, [('out_file', 'in_file')]) ,(inputnode, unwarp, [('in_file', 'inputnode.in_dwi')]) @@ -189,8 +189,10 @@ def _gen_index(in_file): return out_file avg_b0_0 = pe.Node(niu.Function(input_names=['in_dwi', 'in_bval'], - output_names=['out_file'], function=b0_average), name='b0_avg_pre') - bet_dwi0 = pe.Node(fsl.BET(frac=0.3, mask=True, robust=True), name='bet_dwi_pre') + output_names=['out_file'], function=b0_average), + name='b0_avg_pre') + bet_dwi0 = pe.Node(fsl.BET(frac=0.3, mask=True, robust=True), + name='bet_dwi_pre') sdc = sdc_peb(epi_params=epi_params, altepi_params=altepi_params) ecc = pe.Node(fsl.Eddy(method='jac'), name='fsl_eddy') @@ -198,8 +200,10 @@ def _gen_index(in_file): output_names=['out_file'], function=eddy_rotate_bvecs), name='Rotate_Bvec') avg_b0_1 = pe.Node(niu.Function(input_names=['in_dwi', 'in_bval'], - output_names=['out_file'], function=b0_average), name='b0_avg_post') - bet_dwi1 = pe.Node(fsl.BET(frac=0.3, mask=True, robust=True), name='bet_dwi_post') + output_names=['out_file'], function=b0_average), + name='b0_avg_post') + bet_dwi1 = pe.Node(fsl.BET(frac=0.3, mask=True, robust=True), + name='bet_dwi_post') wf = pe.Workflow('dMRI_Artifacts_FSL') wf.connect([ @@ -235,30 +239,37 @@ def hmc_pipeline(name='motion_correct'): """ HMC stands for head-motion correction. - Creates a pipeline that corrects for head motion artifacts in dMRI sequences. + Creates a pipeline that corrects for head motion artifacts in dMRI + sequences. It takes a series of diffusion weighted images and rigidly co-registers - them to one reference image. Finally, the `b`-matrix is rotated accordingly [Leemans09]_ - making use of the rotation matrix obtained by FLIRT. + them to one reference image. Finally, the `b`-matrix is rotated accordingly + [Leemans09]_ making use of the rotation matrix obtained by FLIRT. - Search angles have been limited to 4 degrees, based on results in [Yendiki13]_. + Search angles have been limited to 4 degrees, based on results in + [Yendiki13]_. - A list of rigid transformation matrices is provided, so that transforms can be - chained. This is useful to correct for artifacts with only one interpolation process (as - previously discussed `here `_), + A list of rigid transformation matrices is provided, so that transforms + can be chained. + This is useful to correct for artifacts with only one interpolation process + (as previously discussed `here + `_), and also to compute nuisance regressors as proposed by [Yendiki13]_. .. warning:: This workflow rotates the `b`-vectors, so please be advised - that not all the dicom converters ensure the consistency between the resulting - nifti orientation and the gradients table (e.g. dcm2nii checks it). + that not all the dicom converters ensure the consistency between the + resulting nifti orientation and the gradients table (e.g. dcm2nii + checks it). .. admonition:: References - .. [Leemans09] Leemans A, and Jones DK, `The B-matrix must be rotated when correcting - for subject motion in DTI data `_, + .. [Leemans09] Leemans A, and Jones DK, `The B-matrix must be rotated + when correcting for subject motion in DTI data + `_, Magn Reson Med. 61(6):1336-49. 2009. doi: 10.1002/mrm.21890. - .. [Yendiki13] Yendiki A et al., `Spurious group differences due to head motion in - a diffusion MRI study `_. + .. [Yendiki13] Yendiki A et al., `Spurious group differences due to head + motion in a diffusion MRI study + `_. Neuroimage. 21(88C):79-90. 2013. doi: 10.1016/j.neuroimage.2013.11.027 Example @@ -288,15 +299,23 @@ def hmc_pipeline(name='motion_correct'): outputnode.out_xfms - list of transformation matrices """ - params = dict(interp='spline', cost='normmi', - cost_func='normmi', dof=6, bins=64, save_log=True, - searchr_x=[-4, 4], searchr_y=[-4, 4], searchr_z=[-4, 4], - fine_search=1, coarse_search=10, padding_size=1) + params = dict(dof=6, interp='spline', padding_size=10, save_log=True, + searchr_x=[-3, 3], searchr_y=[-3, 3], searchr_z=[-3, 3], + fine_search=1, coarse_search=2) + # cost='normmi', cost_func='normmi', bins=64, inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'ref_num', 'in_bvec', 'in_bval', 'in_mask']), name='inputnode') - pick_ref = pe.Node(fsl.ExtractROI(t_size=1), name='GetB0') + refid = pe.Node(niu.IdentityInterface(fields=['ref_num']), + name='RefVolume') + pick_ref = pe.Node(fsl.ExtractROI(t_size=1), name='GetRef') + pick_mov = pe.Node(niu.Function(input_names=['in_file', 'volid'], + output_names=['out_file'], function=remove_comp), + name='GetMovingDWs') flirt = dwi_flirt(flirt_param=params) + insmat = pe.Node(niu.Function(input_names=['inlist', 'volid'], + output_names=['out'], function=insert_mat), + name='InsertRefmat') rot_bvec = pe.Node(niu.Function(input_names=['in_bvec', 'in_matrix'], output_names=['out_file'], function=rotate_bvecs), name='Rotate_Bvec') @@ -306,17 +325,22 @@ def hmc_pipeline(name='motion_correct'): wf = pe.Workflow(name=name) wf.connect([ - (inputnode, pick_ref, [('in_file', 'in_file'), - (('ref_num', _checkrnum), 't_min')]) + (inputnode, refid, [(('ref_num', _checkrnum), 'ref_num')]) + ,(inputnode, pick_ref, [('in_file', 'in_file')]) + ,(refid, pick_ref, [('ref_num', 't_min')]) + ,(inputnode, pick_mov, [('in_file', 'in_file')]) + ,(refid, pick_mov, [('ref_num', 'volid')]) ,(inputnode, flirt, [('in_file', 'inputnode.in_file'), ('in_mask', 'inputnode.ref_mask'), ('in_bval', 'inputnode.in_bval')]) ,(pick_ref, flirt, [('roi_file', 'inputnode.reference')]) + ,(flirt, insmat, [('outputnode.out_xfms', 'inlist')]) + ,(refid, insmat, [('ref_num', 'volid')]) ,(inputnode, rot_bvec, [('in_bvec', 'in_bvec')]) - ,(flirt, rot_bvec, [('outputnode.out_xfms', 'in_matrix')]) + ,(insmat, rot_bvec, [('out', 'in_matrix')]) ,(rot_bvec, outputnode, [('out_file', 'out_bvec')]) - ,(flirt, outputnode, [('outputnode.out_xfms', 'out_xfms'), - ('outputnode.out_file', 'out_file')]) + ,(flirt, outputnode, [('outputnode.out_file', 'out_file')]) + ,(insmat, outputnode, [('out', 'out_xfms')]) ]) return wf @@ -375,9 +399,8 @@ def ecc_pipeline(name='eddy_correct'): outputnode.out_file - corrected dwi file outputnode.out_xfms - list of transformation matrices """ - params = dict(no_search=True, interp='spline', cost='normmi', - cost_func='normmi', dof=12, bins=64, - padding_size=1) + params = dict(dof=12, no_search=True, interp='spline', padding_size=1, save_log=True) + # cost='normmi', cost_func='normmi', bins=64, inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_bval', 'in_mask', 'in_xfms']), name='inputnode') @@ -638,17 +661,20 @@ def sdc_peb(name='peb_correction', topup = pe.Node(fsl.TOPUP(), name='topup') topup.inputs.encoding_direction = [epi_params['enc_dir'], altepi_params['enc_dir']] - topup.inputs.readout_times = [compute_readout(epi_params), + + readout = compute_readout(epi_params) + topup.inputs.readout_times = [readout, compute_readout(altepi_params)] unwarp = pe.Node(fsl.ApplyTOPUP(in_index=[1], method='jac'), name='unwarp') - scaling = pe.Node(niu.Function(input_names=['in_file', 'enc_dir'], - output_names=['factor'], function=_get_zoom), - name='GetZoom') - scaling.inputs.enc_dir = epi_params['enc_dir'] + #scaling = pe.Node(niu.Function(input_names=['in_file', 'enc_dir'], + # output_names=['factor'], function=_get_zoom), + # name='GetZoom') + #scaling.inputs.enc_dir = epi_params['enc_dir'] vsm2dfm = vsm2warp() vsm2dfm.inputs.inputnode.enc_dir = epi_params['enc_dir'] + vsm2dfm.inputs.inputnode.scaling = readout wf = pe.Workflow(name=name) wf.connect([ @@ -666,9 +692,9 @@ def sdc_peb(name='peb_correction', ,(inputnode, unwarp, [('in_file', 'in_files')]) ,(unwarp, outputnode, [('out_corrected', 'out_file')]) - ,(b0_ref, scaling, [('roi_file', 'in_file')]) + #,(b0_ref, scaling, [('roi_file', 'in_file')]) + #,(scaling, vsm2dfm, [('factor', 'inputnode.scaling')]) ,(b0_ref, vsm2dfm, [('roi_file', 'inputnode.in_ref')]) - ,(scaling, vsm2dfm, [('factor', 'inputnode.scaling')]) ,(topup, vsm2dfm, [('out_field', 'inputnode.in_vsm')]) ,(topup, outputnode, [('out_field', 'out_vsm')]) ,(vsm2dfm, outputnode, [('outputnode.out_warp', 'out_warp')]) @@ -745,7 +771,7 @@ def _xfm_jacobian(in_xfm): def _get_zoom(in_file, enc_dir): import nibabel as nb - zooms = nb.load(in_file).get_zooms() + zooms = nb.load(in_file).get_header().get_zooms() if 'y' in enc_dir: return zooms[1] diff --git a/nipype/workflows/dmri/preprocess/utils.py b/nipype/workflows/dmri/preprocess/utils.py index 0bbb5de165..1f82d7c04a 100644 --- a/nipype/workflows/dmri/preprocess/utils.py +++ b/nipype/workflows/dmri/preprocess/utils.py @@ -5,7 +5,7 @@ # @Author: oesteban # @Date: 2014-08-30 10:53:13 # @Last Modified by: oesteban -# @Last Modified time: 2014-09-03 20:04:13 +# @Last Modified time: 2014-09-04 16:44:28 import nipype.pipeline.engine as pe import nipype.interfaces.utility as niu from nipype.interfaces import fsl @@ -101,7 +101,7 @@ def dwi_flirt(name='DWICoregistration', excl_nodiff=False, enhb0 = pe.Node(niu.Function(input_names=['in_file', 'in_mask', 'clip_limit'], output_names=['out_file'], function=enhance), name='B0Equalize') - enhb0.inputs.clip_limit = 0.02 + enhb0.inputs.clip_limit = 0.015 enhdw = pe.MapNode(niu.Function(input_names=['in_file'], output_names=['out_file'], function=enhance), name='DWEqualize', iterfield=['in_file']) @@ -231,6 +231,47 @@ def extract_bval(in_dwi, in_bval, b=0, out_file=None): return out_file +def remove_comp(in_file, volid=0, out_file=None): + """ + Removes the volume ``volid`` from the 4D nifti file + """ + import numpy as np + import nibabel as nb + import os.path as op + + if out_file is None: + fname, ext = op.splitext(op.basename(in_file)) + if ext == ".gz": + fname, ext2 = op.splitext(fname) + ext = ext2 + ext + out_file = op.abspath("%s_extract%s" % (fname, ext)) + + im = nb.load(in_file) + data = im.get_data() + hdr = im.get_header().copy() + + if volid == 0: + data = data[..., 1:] + elif volid == (data.shape[-1] - 1): + data = data[..., :-1] + else: + data = np.concatenate((data[..., :volid], data[..., (volid + 1):]), + axis=3) + hdr.set_data_shape(data.shape) + nb.Nifti1Image(data, im.get_affine(), hdr).to_filename(out_file) + return out_file + + +def insert_mat(inlist, volid=0): + import numpy as np + import os.path as op + idfname = op.abspath('identity.mat') + out = inlist + np.savetxt(idfname, np.eye(4)) + out.insert(volid, idfname) + return out + + def recompose_dwi(in_dwi, in_bval, in_corrected, out_file=None): """ Recompose back the dMRI data accordingly the b-values table after EC correction @@ -323,9 +364,10 @@ def rotate_bvecs(in_bvec, in_matrix): """ Rotates the input bvec file accordingly with a list of matrices. - .. note:: the input affine matrix transforms points in the destination image to their \ - corresponding coordinates in the original image. Therefore, this matrix should be inverted \ - first, as we want to know the target position of :math:`\\vec{r}`. + .. note:: the input affine matrix transforms points in the destination + image to their corresponding coordinates in the original image. + Therefore, this matrix should be inverted first, as we want to know + the target position of :math:`\\vec{r}`. """ import os @@ -339,13 +381,15 @@ def rotate_bvecs(in_bvec, in_matrix): new_bvecs = [] if len(bvecs) != len(in_matrix): - raise RuntimeError('Number of b-vectors and rotation matrices should match.') + raise RuntimeError(('Number of b-vectors (%d) and rotation ' + 'matrices (%d) should match.') % (len(bvecs), + len(in_matrix))) for bvec, mat in zip(bvecs, in_matrix): if np.all(bvec == 0.0): new_bvecs.append(bvec) else: - invrot = np.linalg.inv(np.loadtxt(mat))[:3,:3] + invrot = np.linalg.inv(np.loadtxt(mat))[:3, :3] newbvec = invrot.dot(bvec) new_bvecs.append((newbvec/np.linalg.norm(newbvec))) @@ -373,7 +417,8 @@ def eddy_rotate_bvecs(in_bvec, eddy_params): params = np.loadtxt(eddy_params) if len(bvecs) != len(params): - raise RuntimeError('Number of b-vectors and rotation matrices should match.') + raise RuntimeError(('Number of b-vectors and rotation ' + 'matrices should match.')) for bvec, row in zip(bvecs, params): if np.all(bvec == 0.0): @@ -383,9 +428,15 @@ def eddy_rotate_bvecs(in_bvec, eddy_params): ay = row[4] az = row[5] - Rx = np.array([[1.0, 0.0, 0.0], [0.0, cos(ax), -sin(ax)], [0.0, sin(ax), cos(ax)]]) - Ry = np.array([[cos(ay), 0.0, sin(ay)], [0.0, 1.0, 0.0], [-sin(ay), 0.0, cos(ay)]]) - Rz = np.array([[cos(az), -sin(az), 0.0], [sin(az), cos(az), 0.0], [0.0, 0.0, 1.0]]) + Rx = np.array([[1.0, 0.0, 0.0], + [0.0, cos(ax), -sin(ax)], + [0.0, sin(ax), cos(ax)]]) + Ry = np.array([[cos(ay), 0.0, sin(ay)], + [0.0, 1.0, 0.0], + [-sin(ay), 0.0, cos(ay)]]) + Rz = np.array([[cos(az), -sin(az), 0.0], + [sin(az), cos(az), 0.0], + [0.0, 0.0, 1.0]]) R = Rx.dot(Ry).dot(Rz) invrot = np.linalg.inv(R) @@ -443,7 +494,7 @@ def siemens2rads(in_file, out_file=None): if len(in_file) == 2: data = nb.load(in_file[1]).get_data().astype(np.float32) - data elif (data.ndim == 4) and (data.shape[-1] == 2): - data = np.squeeze(data[...,1] - data[...,0]) + data = np.squeeze(data[..., 1] - data[..., 0]) hdr.set_data_shape(data.shape[:3]) imin = data.min() @@ -497,14 +548,15 @@ def demean_image(in_file, in_mask=None, out_file=None): data = im.get_data().astype(np.float32) msk = np.ones_like(data) - if not in_mask is None: + if in_mask is not None: msk = nb.load(in_mask).get_data().astype(np.float32) - msk[msk>0] = 1.0 - msk[msk<1] = 0.0 + msk[msk > 0] = 1.0 + msk[msk < 1] = 0.0 - mean = np.median(data[msk==1].reshape(-1)) - data[msk==1] = data[msk==1] - mean - nb.Nifti1Image(data, im.get_affine(), im.get_header()).to_filename(out_file) + mean = np.median(data[msk == 1].reshape(-1)) + data[msk == 1] = data[msk == 1] - mean + nb.Nifti1Image(data, im.get_affine(), + im.get_header()).to_filename(out_file) return out_file @@ -571,13 +623,17 @@ def copy_hdr(in_file, in_file_hdr, out_file=None): out_file = op.abspath('./%s_fixhdr.nii.gz' % fname) imref = nb.load(in_file_hdr) - nii = nb.Nifti1Image(nb.load(in_file).get_data(), - imref.get_affine(), imref.get_header()) + hdr = imref.get_header().copy() + hdr.set_data_dtype(np.float32) + vsm = nb.load(in_file).get_data().astype(np.float32) + hdr.set_data_shape(vsm.shape) + hdr.set_xyzt_units('mm') + nii = nb.Nifti1Image(vsm, imref.get_affine(), hdr) nii.to_filename(out_file) return out_file -def enhance(in_file, clip_limit=0.015, in_mask=None, out_file=None): +def enhance(in_file, clip_limit=0.010, in_mask=None, out_file=None): import numpy as np import nibabel as nb import os.path as op From 924cd45419cb21af0ae68386ef1b786565ac28ab Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Thu, 4 Sep 2014 17:53:49 +0200 Subject: [PATCH 23/33] tunning hmc registration --- nipype/workflows/dmri/preprocess/epi.py | 22 ++++++++++++---------- nipype/workflows/dmri/preprocess/utils.py | 15 +++++++++++---- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/nipype/workflows/dmri/preprocess/epi.py b/nipype/workflows/dmri/preprocess/epi.py index 56d76ca457..7ed85e2e93 100644 --- a/nipype/workflows/dmri/preprocess/epi.py +++ b/nipype/workflows/dmri/preprocess/epi.py @@ -299,19 +299,19 @@ def hmc_pipeline(name='motion_correct'): outputnode.out_xfms - list of transformation matrices """ - params = dict(dof=6, interp='spline', padding_size=10, save_log=True, + params = dict(dof=6, padding_size=10, save_log=True, searchr_x=[-3, 3], searchr_y=[-3, 3], searchr_z=[-3, 3], - fine_search=1, coarse_search=2) - # cost='normmi', cost_func='normmi', bins=64, + fine_search=1, coarse_search=2, + cost='mutualinfo', cost_func='mutualinfo', bins=64) inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'ref_num', 'in_bvec', 'in_bval', 'in_mask']), name='inputnode') refid = pe.Node(niu.IdentityInterface(fields=['ref_num']), name='RefVolume') pick_ref = pe.Node(fsl.ExtractROI(t_size=1), name='GetRef') - pick_mov = pe.Node(niu.Function(input_names=['in_file', 'volid'], - output_names=['out_file'], function=remove_comp), - name='GetMovingDWs') + pick_mov = pe.Node(niu.Function(input_names=['in_file', 'in_bval', 'volid'], + output_names=['out_file', 'out_bval'], + function=remove_comp), name='GetMovingDWs') flirt = dwi_flirt(flirt_param=params) insmat = pe.Node(niu.Function(input_names=['inlist', 'volid'], output_names=['out'], function=insert_mat), @@ -327,12 +327,14 @@ def hmc_pipeline(name='motion_correct'): wf.connect([ (inputnode, refid, [(('ref_num', _checkrnum), 'ref_num')]) ,(inputnode, pick_ref, [('in_file', 'in_file')]) + ,(inputnode, pick_mov, [('in_file', 'in_file'), + ('in_bval', 'in_bval')]) + ,(inputnode, flirt, [('in_mask', 'inputnode.ref_mask')]) ,(refid, pick_ref, [('ref_num', 't_min')]) - ,(inputnode, pick_mov, [('in_file', 'in_file')]) ,(refid, pick_mov, [('ref_num', 'volid')]) - ,(inputnode, flirt, [('in_file', 'inputnode.in_file'), - ('in_mask', 'inputnode.ref_mask'), - ('in_bval', 'inputnode.in_bval')]) + ,(pick_mov, flirt, [('out_file', 'inputnode.in_file'), + ('out_bval', 'inputnode.in_bval')]) + ,(pick_ref, flirt, [('roi_file', 'inputnode.reference')]) ,(flirt, insmat, [('outputnode.out_xfms', 'inlist')]) ,(refid, insmat, [('ref_num', 'volid')]) diff --git a/nipype/workflows/dmri/preprocess/utils.py b/nipype/workflows/dmri/preprocess/utils.py index 1f82d7c04a..2528275a85 100644 --- a/nipype/workflows/dmri/preprocess/utils.py +++ b/nipype/workflows/dmri/preprocess/utils.py @@ -4,8 +4,8 @@ # vi: set ft=python sts=4 ts=4 sw=4 et: # @Author: oesteban # @Date: 2014-08-30 10:53:13 -# @Last Modified by: oesteban -# @Last Modified time: 2014-09-04 16:44:28 +# @Last Modified by: Oscar Esteban +# @Last Modified time: 2014-09-04 17:21:33 import nipype.pipeline.engine as pe import nipype.interfaces.utility as niu from nipype.interfaces import fsl @@ -231,7 +231,7 @@ def extract_bval(in_dwi, in_bval, b=0, out_file=None): return out_file -def remove_comp(in_file, volid=0, out_file=None): +def remove_comp(in_file, in_bval, volid=0, out_file=None): """ Removes the volume ``volid`` from the 4D nifti file """ @@ -249,17 +249,24 @@ def remove_comp(in_file, volid=0, out_file=None): im = nb.load(in_file) data = im.get_data() hdr = im.get_header().copy() + bval = np.loadtxt(in_bval) if volid == 0: data = data[..., 1:] + bval = bval[1:] elif volid == (data.shape[-1] - 1): data = data[..., :-1] + bval = bval[:-1] else: data = np.concatenate((data[..., :volid], data[..., (volid + 1):]), axis=3) + bval = np.hstack((bval[:volid], bval[(volid + 1):])) hdr.set_data_shape(data.shape) nb.Nifti1Image(data, im.get_affine(), hdr).to_filename(out_file) - return out_file + + out_bval = op.abspath('bval_extract.txt') + np.savetxt(out_bval, bval) + return out_file, out_bval def insert_mat(inlist, volid=0): From b76c0e5fc1f4da4d961ef1fc85ece9f249c1ae56 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Thu, 4 Sep 2014 18:06:53 +0200 Subject: [PATCH 24/33] add bgvalue new argument --- nipype/workflows/dmri/preprocess/epi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nipype/workflows/dmri/preprocess/epi.py b/nipype/workflows/dmri/preprocess/epi.py index 7ed85e2e93..0756cbfa15 100644 --- a/nipype/workflows/dmri/preprocess/epi.py +++ b/nipype/workflows/dmri/preprocess/epi.py @@ -299,7 +299,7 @@ def hmc_pipeline(name='motion_correct'): outputnode.out_xfms - list of transformation matrices """ - params = dict(dof=6, padding_size=10, save_log=True, + params = dict(dof=6, bgvalue=0, save_log=True, searchr_x=[-3, 3], searchr_y=[-3, 3], searchr_z=[-3, 3], fine_search=1, coarse_search=2, cost='mutualinfo', cost_func='mutualinfo', bins=64) @@ -401,7 +401,7 @@ def ecc_pipeline(name='eddy_correct'): outputnode.out_file - corrected dwi file outputnode.out_xfms - list of transformation matrices """ - params = dict(dof=12, no_search=True, interp='spline', padding_size=1, save_log=True) + params = dict(dof=12, no_search=True, interp='spline', bgvalue=0, save_log=True) # cost='normmi', cost_func='normmi', bins=64, inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_bval', From 2fa95f01db91d8b944db4bd1dab29c3b38a45fd6 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Fri, 5 Sep 2014 12:35:02 +0200 Subject: [PATCH 25/33] added schedules for FLIRT --- nipype/workflows/data/__init__.py | 18 ++++++ nipype/workflows/data/ecc.sch | 67 +++++++++++++++++++ nipype/workflows/data/hmc.sch | 64 +++++++++++++++++++ nipype/workflows/dmri/preprocess/epi.py | 78 ++++++++++++++--------- nipype/workflows/dmri/preprocess/utils.py | 18 ++++-- 5 files changed, 207 insertions(+), 38 deletions(-) create mode 100644 nipype/workflows/data/__init__.py create mode 100644 nipype/workflows/data/ecc.sch create mode 100644 nipype/workflows/data/hmc.sch diff --git a/nipype/workflows/data/__init__.py b/nipype/workflows/data/__init__.py new file mode 100644 index 0000000000..b33f21e824 --- /dev/null +++ b/nipype/workflows/data/__init__.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Author: oesteban +# @Date: 2014-09-05 11:23:48 +# @Last Modified by: oesteban +# @Last Modified time: 2014-09-05 11:33:27 +import os.path as op + + +def get_flirt_schedule(name): + if name == 'ecc': + return op.abspath(op.join(op.dirname(__file__), + 'ecc.sch')) + elif name == 'hmc': + return op.abspath(op.join(op.dirname(__file__), + 'hmc.sch')) + else: + raise RuntimeError('Requested file does not exist.') diff --git a/nipype/workflows/data/ecc.sch b/nipype/workflows/data/ecc.sch new file mode 100644 index 0000000000..a7de1f2b0b --- /dev/null +++ b/nipype/workflows/data/ecc.sch @@ -0,0 +1,67 @@ +# 4mm scale +setscale 4 +setoption smoothing 6 +setoption paramsubset 1 0 0 0 0 0 0 1 1 1 1 1 1 +clear U +clear UA +clear UB +clear US +clear UP +# try the identity transform as a starting point at this resolution +clear UQ +setrow UQ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 +optimise 7 UQ 0.0 0.0 0.0 0.0 0.0 0.0 0.0 rel 4 +sort U +copy U UA +# select best 4 optimised solutions and try perturbations of these +clear U +copy UA:1-4 U +optimise 7 UA:1-4 1.0 0.0 0.0 0.0 0.0 0.0 0.0 rel 4 +optimise 7 UA:1-4 -1.0 0.0 0.0 0.0 0.0 0.0 0.0 abs 4 +optimise 7 UA:1-4 0.0 1.0 0.0 0.0 0.0 0.0 0.0 abs 4 +optimise 7 UA:1-4 0.0 -1.0 0.0 0.0 0.0 0.0 0.0 abs 4 +optimise 7 UA:1-4 0.0 0.0 1.0 0.0 0.0 0.0 0.0 abs 4 +optimise 7 UA:1-4 0.0 0.0 -1.0 0.0 0.0 0.0 0.0 abs 4 +optimise 7 UA:1-4 0.0 0.0 0.0 0.0 0.0 0.0 0.1 abs 4 +optimise 7 UA:1-4 0.0 0.0 0.0 0.0 0.0 0.0 -0.1 abs 4 +optimise 7 UA:1-4 0.0 0.0 0.0 0.0 0.0 0.0 0.2 abs 4 +optimise 7 UA:1-4 0.0 0.0 0.0 0.0 0.0 0.0 -0.2 abs 4 +sort U +copy U UB +# 2mm scale +setscale 2 +setoption smoothing 4 +setoption paramsubset 1 0 0 0 0 0 0 1 1 1 1 1 1 +clear U +clear UC +clear UD +clear UE +clear UF +# remeasure costs at this scale +measurecost 7 UB 0 0 0 0 0 0 rel +sort U +copy U UC +clear U +optimise 7 UC:1-3 0.0 0.0 0.0 0.0 0.0 0.0 0.0 abs 2 +copy U UD +sort U +copy U UF +# also try the identity transform as a starting point at this resolution +sort U +clear U UG +clear U +setrow UG 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 +optimise 7 UG 0.0 0.0 0.0 0.0 0.0 0.0 0.0 abs 2 +sort U +copy U UG +# 1mm scale +setscale 1 +setoption smoothing 2 +setoption boundguess 1 +setoption paramsubset 1 0 0 0 0 0 0 1 1 1 1 1 1 +clear U +#also try the identity transform as a starting point at this resolution +setrow UK 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 +optimise 12 UK:1-2 0.0 0.0 0.0 0.0 0.0 0.0 0.0 abs 1 +sort U + diff --git a/nipype/workflows/data/hmc.sch b/nipype/workflows/data/hmc.sch new file mode 100644 index 0000000000..08f3e76e85 --- /dev/null +++ b/nipype/workflows/data/hmc.sch @@ -0,0 +1,64 @@ +# 4mm scale +setscale 4 +setoption smoothing 6 +clear U +clear UA +clear UB +clear US +clear UP +# try the identity transform as a starting point at this resolution +clear UQ +setrow UQ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 +optimise 7 UQ 0.0 0.0 0.0 0.0 0.0 0.0 0.0 rel 4 +sort U +copy U UA +# select best 4 optimised solutions and try perturbations of these +clear U +copy UA:1-4 U +optimise 7 UA:1-4 1.0 0.0 0.0 0.0 0.0 0.0 0.0 rel 4 +optimise 7 UA:1-4 -1.0 0.0 0.0 0.0 0.0 0.0 0.0 abs 4 +optimise 7 UA:1-4 0.0 1.0 0.0 0.0 0.0 0.0 0.0 abs 4 +optimise 7 UA:1-4 0.0 -1.0 0.0 0.0 0.0 0.0 0.0 abs 4 +optimise 7 UA:1-4 0.0 0.0 1.0 0.0 0.0 0.0 0.0 abs 4 +optimise 7 UA:1-4 0.0 0.0 -1.0 0.0 0.0 0.0 0.0 abs 4 +optimise 7 UA:1-4 0.0 0.0 0.0 0.0 0.0 0.0 0.1 abs 4 +optimise 7 UA:1-4 0.0 0.0 0.0 0.0 0.0 0.0 -0.1 abs 4 +optimise 7 UA:1-4 0.0 0.0 0.0 0.0 0.0 0.0 0.2 abs 4 +optimise 7 UA:1-4 0.0 0.0 0.0 0.0 0.0 0.0 -0.2 abs 4 +sort U +copy U UB +# 2mm scale +setscale 2 +setoption smoothing 4 +clear U +clear UC +clear UD +clear UE +clear UF +# remeasure costs at this scale +measurecost 7 UB 0 0 0 0 0 0 rel +sort U +copy U UC +clear U +optimise 7 UC:1-3 0.0 0.0 0.0 0.0 0.0 0.0 0.0 abs 2 +copy U UD +sort U +copy U UF +# also try the identity transform as a starting point at this resolution +sort U +clear U UG +clear U +setrow UG 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 +optimise 7 UG 0.0 0.0 0.0 0.0 0.0 0.0 0.0 abs 2 +sort U +copy U UG +# 1mm scale +setscale 1 +setoption smoothing 2 +setoption boundguess 1 +clear U +#also try the identity transform as a starting point at this resolution +setrow UK 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 +optimise 12 UK:1-2 0.0 0.0 0.0 0.0 0.0 0.0 0.0 abs 1 +sort U + diff --git a/nipype/workflows/dmri/preprocess/epi.py b/nipype/workflows/dmri/preprocess/epi.py index 0756cbfa15..f7dd5ae99e 100644 --- a/nipype/workflows/dmri/preprocess/epi.py +++ b/nipype/workflows/dmri/preprocess/epi.py @@ -19,30 +19,33 @@ def all_fmb_pipeline(name='hmc_sdc_ecc', acc_factor=3, enc_dir='y-')): """ - Builds a pipeline including three artifact corrections: head-motion correction (HMC), - susceptibility-derived distortion correction (SDC), and Eddy currents-derived distortion - correction (ECC). + Builds a pipeline including three artifact corrections: head-motion + correction (HMC), susceptibility-derived distortion correction (SDC), + and Eddy currents-derived distortion correction (ECC). The displacement fields from each kind of distortions are combined. Thus, only one interpolation occurs between input data and result. - .. warning:: this workflow rotates the gradients table (*b*-vectors) [Leemans09]_. + .. warning:: this workflow rotates the gradients table (*b*-vectors) + [Leemans09]_. """ - inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_bvec', 'in_bval', - 'bmap_pha', 'bmap_mag']), name='inputnode') - - outputnode = pe.Node(niu.IdentityInterface(fields=['out_file', 'out_mask', 'out_bvec']), - name='outputnode') - + inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_bvec', + 'in_bval', 'bmap_pha', 'bmap_mag']), name='inputnode') + outputnode = pe.Node(niu.IdentityInterface(fields=['out_file', 'out_mask', + 'out_bvec']), name='outputnode') avg_b0_0 = pe.Node(niu.Function(input_names=['in_dwi', 'in_bval'], - output_names=['out_file'], function=b0_average), name='b0_avg_pre') + output_names=['out_file'], function=b0_average), + name='b0_avg_pre') avg_b0_1 = pe.Node(niu.Function(input_names=['in_dwi', 'in_bval'], - output_names=['out_file'], function=b0_average), name='b0_avg_post') - bet_dwi0 = pe.Node(fsl.BET(frac=0.3, mask=True, robust=True), name='bet_dwi_pre') - bet_dwi1 = pe.Node(fsl.BET(frac=0.3, mask=True, robust=True), name='bet_dwi_post') + output_names=['out_file'], function=b0_average), + name='b0_avg_post') + bet_dwi0 = pe.Node(fsl.BET(frac=0.3, mask=True, robust=True), + name='bet_dwi_pre') + bet_dwi1 = pe.Node(fsl.BET(frac=0.3, mask=True, robust=True), + name='bet_dwi_post') hmc = hmc_pipeline() sdc = sdc_fmb(fugue_params=fugue_params, bmap_params=bmap_params, @@ -50,7 +53,7 @@ def all_fmb_pipeline(name='hmc_sdc_ecc', ecc = ecc_pipeline() unwarp = apply_all_corrections() - wf = pe.Workflow('dMRI_Artifacts') + wf = pe.Workflow(name=name) wf.connect([ (inputnode, hmc, [('in_file', 'inputnode.in_file'), ('in_bvec', 'inputnode.in_bvec'), @@ -64,6 +67,7 @@ def all_fmb_pipeline(name='hmc_sdc_ecc', ,(inputnode, sdc, [('in_bval', 'inputnode.in_bval'), ('bmap_pha', 'inputnode.bmap_pha'), ('bmap_mag', 'inputnode.bmap_mag')]) + ,(hmc, ecc, [('outputnode.out_xfms', 'inputnode.in_xfms')]) ,(inputnode, ecc, [('in_file', 'inputnode.in_file'), ('in_bval', 'inputnode.in_bval')]) ,(bet_dwi0, ecc, [('mask_file', 'inputnode.in_mask')]) @@ -93,26 +97,31 @@ def all_peb_pipeline(name='hmc_sdc_ecc', enc_dir='y', epi_factor=1)): """ - Builds a pipeline including three artifact corrections: head-motion correction (HMC), - susceptibility-derived distortion correction (SDC), and Eddy currents-derived distortion - correction (ECC). + Builds a pipeline including three artifact corrections: head-motion + correction (HMC), susceptibility-derived distortion correction (SDC), + and Eddy currents-derived distortion correction (ECC). - .. warning:: this workflow rotates the gradients table (*b*-vectors) [Leemans09]_. + .. warning:: this workflow rotates the gradients table (*b*-vectors) + [Leemans09]_. """ - inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_bvec', 'in_bval', - 'alt_file']), name='inputnode') + inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_bvec', + 'in_bval', 'alt_file']), name='inputnode') outputnode = pe.Node(niu.IdentityInterface(fields=['out_file', 'out_mask', 'out_bvec']), name='outputnode') avg_b0_0 = pe.Node(niu.Function(input_names=['in_dwi', 'in_bval'], - output_names=['out_file'], function=b0_average), name='b0_avg_pre') + output_names=['out_file'], function=b0_average), + name='b0_avg_pre') avg_b0_1 = pe.Node(niu.Function(input_names=['in_dwi', 'in_bval'], - output_names=['out_file'], function=b0_average), name='b0_avg_post') - bet_dwi0 = pe.Node(fsl.BET(frac=0.3, mask=True, robust=True), name='bet_dwi_pre') - bet_dwi1 = pe.Node(fsl.BET(frac=0.3, mask=True, robust=True), name='bet_dwi_post') + output_names=['out_file'], function=b0_average), + name='b0_avg_post') + bet_dwi0 = pe.Node(fsl.BET(frac=0.3, mask=True, robust=True), + name='bet_dwi_pre') + bet_dwi1 = pe.Node(fsl.BET(frac=0.3, mask=True, robust=True), + name='bet_dwi_post') hmc = hmc_pipeline() sdc = sdc_peb(epi_params=epi_params, altepi_params=altepi_params) @@ -120,7 +129,7 @@ def all_peb_pipeline(name='hmc_sdc_ecc', unwarp = apply_all_corrections() - wf = pe.Workflow('dMRI_Artifacts') + wf = pe.Workflow(name=name) wf.connect([ (inputnode, hmc, [('in_file', 'inputnode.in_file'), ('in_bvec', 'inputnode.in_bvec'), @@ -136,6 +145,7 @@ def all_peb_pipeline(name='hmc_sdc_ecc', ,(inputnode, ecc, [('in_file', 'inputnode.in_file'), ('in_bval', 'inputnode.in_bval')]) ,(bet_dwi0, ecc, [('mask_file', 'inputnode.in_mask')]) + ,(hmc, ecc, [('outputnode.out_xfms', 'inputnode.in_xfms')]) ,(ecc, avg_b0_1, [('outputnode.out_file', 'in_dwi')]) ,(inputnode, avg_b0_1, [('in_bval', 'in_bval')]) ,(avg_b0_1, bet_dwi1, [('out_file', 'in_file')]) @@ -173,8 +183,8 @@ def all_fsl_pipeline(name='fsl_all_correct', """ - inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_bvec', 'in_bval', - 'alt_file']), name='inputnode') + inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_bvec', + 'in_bval', 'alt_file']), name='inputnode') outputnode = pe.Node(niu.IdentityInterface(fields=['out_file', 'out_mask', 'out_bvec']), name='outputnode') @@ -205,7 +215,7 @@ def _gen_index(in_file): bet_dwi1 = pe.Node(fsl.BET(frac=0.3, mask=True, robust=True), name='bet_dwi_post') - wf = pe.Workflow('dMRI_Artifacts_FSL') + wf = pe.Workflow(name=name) wf.connect([ (inputnode, avg_b0_0, [('in_file', 'in_dwi'), ('in_bval', 'in_bval')]) @@ -299,10 +309,13 @@ def hmc_pipeline(name='motion_correct'): outputnode.out_xfms - list of transformation matrices """ + from nipype.workflows.data import get_flirt_schedule + params = dict(dof=6, bgvalue=0, save_log=True, searchr_x=[-3, 3], searchr_y=[-3, 3], searchr_z=[-3, 3], fine_search=1, coarse_search=2, - cost='mutualinfo', cost_func='mutualinfo', bins=64) + #cost='mutualinfo', cost_func='mutualinfo', bins=64, + schedule=get_flirt_schedule('hmc')) inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'ref_num', 'in_bvec', 'in_bval', 'in_mask']), name='inputnode') @@ -401,7 +414,10 @@ def ecc_pipeline(name='eddy_correct'): outputnode.out_file - corrected dwi file outputnode.out_xfms - list of transformation matrices """ - params = dict(dof=12, no_search=True, interp='spline', bgvalue=0, save_log=True) + + from nipype.workflows.data import get_flirt_schedule + params = dict(dof=12, no_search=True, interp='spline', bgvalue=0, + schedule=get_flirt_schedule('ecc')) # cost='normmi', cost_func='normmi', bins=64, inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_bval', diff --git a/nipype/workflows/dmri/preprocess/utils.py b/nipype/workflows/dmri/preprocess/utils.py index 2528275a85..256fa7667a 100644 --- a/nipype/workflows/dmri/preprocess/utils.py +++ b/nipype/workflows/dmri/preprocess/utils.py @@ -4,8 +4,8 @@ # vi: set ft=python sts=4 ts=4 sw=4 et: # @Author: oesteban # @Date: 2014-08-30 10:53:13 -# @Last Modified by: Oscar Esteban -# @Last Modified time: 2014-09-04 17:21:33 +# @Last Modified by: oesteban +# @Last Modified time: 2014-09-05 11:31:30 import nipype.pipeline.engine as pe import nipype.interfaces.utility as niu from nipype.interfaces import fsl @@ -92,17 +92,18 @@ def dwi_flirt(name='DWICoregistration', excl_nodiff=False, initmat = pe.Node(niu.Function(input_names=['in_bval', 'in_xfms', 'excl_nodiff'], output_names=['init_xfms'], - function=_checkinitxfm), name='InitXforms') + function=_checkinitxfm), name='InitXforms') initmat.inputs.excl_nodiff = excl_nodiff - + dilate = pe.Node(fsl.maths.MathsCommand(nan2zeros=True, + args='-kernel sphere 5 -dilM'), name='MskDilate') split = pe.Node(fsl.Split(dimension='t'), name='SplitDWIs') pick_ref = pe.Node(niu.Select(), name='Pick_b0') n4 = pe.Node(ants.N4BiasFieldCorrection(dimension=3), name='Bias') enhb0 = pe.Node(niu.Function(input_names=['in_file', 'in_mask', 'clip_limit'], output_names=['out_file'], - function=enhance), name='B0Equalize') + function=enhance), name='B0Equalize') enhb0.inputs.clip_limit = 0.015 - enhdw = pe.MapNode(niu.Function(input_names=['in_file'], + enhdw = pe.MapNode(niu.Function(input_names=['in_file', 'in_mask'], output_names=['out_file'], function=enhance), name='DWEqualize', iterfield=['in_file']) flirt = pe.MapNode(fsl.FLIRT(**flirt_param), name='CoRegistration', @@ -115,14 +116,17 @@ def dwi_flirt(name='DWICoregistration', excl_nodiff=False, wf = pe.Workflow(name=name) wf.connect([ (inputnode, split, [('in_file', 'in_file')]) + ,(inputnode, dilate, [('ref_mask', 'in_file')]) ,(inputnode, enhb0, [('ref_mask', 'in_mask')]) ,(inputnode, initmat, [('in_xfms', 'in_xfms'), ('in_bval', 'in_bval')]) ,(inputnode, n4, [('reference', 'input_image'), ('ref_mask', 'mask_image')]) - ,(inputnode, flirt, [('ref_mask', 'ref_weight')]) + ,(dilate, flirt, [('out_file', 'ref_weight'), + ('out_file', 'in_weight')]) ,(n4, enhb0, [('output_image', 'in_file')]) ,(split, enhdw, [('out_files', 'in_file')]) + ,(dilate, enhdw, [('out_file', 'in_mask')]) ,(enhb0, flirt, [('out_file', 'reference')]) ,(enhdw, flirt, [('out_file', 'in_file')]) ,(initmat, flirt, [('init_xfms', 'in_matrix_file')]) From 5f7feb6e43c31e28aaada421700fb033f68269f1 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Sat, 6 Sep 2014 11:46:27 +0200 Subject: [PATCH 26/33] remove my signature everywhere --- examples/dmri_preprocessing.py | 10 ++++------ nipype/algorithms/tests/test_normalize_tpms.py | 8 +------- nipype/interfaces/elastix/__init__.py | 9 ++------- nipype/interfaces/elastix/base.py | 9 ++------- nipype/interfaces/elastix/registration.py | 9 ++------- nipype/interfaces/elastix/utils.py | 9 ++------- nipype/workflows/data/__init__.py | 9 +++------ nipype/workflows/dmri/preprocess/utils.py | 8 ++------ 8 files changed, 18 insertions(+), 53 deletions(-) diff --git a/examples/dmri_preprocessing.py b/examples/dmri_preprocessing.py index 64adc0c29b..823bb2c4c3 100644 --- a/examples/dmri_preprocessing.py +++ b/examples/dmri_preprocessing.py @@ -1,9 +1,7 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# @Author: oesteban -# @Date: 2014-08-31 20:32:22 -# @Last Modified by: oesteban -# @Last Modified time: 2014-09-04 10:55:15 +# 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: + """ =================== dMRI: Preprocessing diff --git a/nipype/algorithms/tests/test_normalize_tpms.py b/nipype/algorithms/tests/test_normalize_tpms.py index 0f4e3e4d3c..1484874751 100644 --- a/nipype/algorithms/tests/test_normalize_tpms.py +++ b/nipype/algorithms/tests/test_normalize_tpms.py @@ -1,12 +1,6 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +# 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 - code@oscaresteban.es -# @Date: 2014-05-28 17:57:20 -# @Last Modified by: oesteban -# @Last Modified time: 2014-05-29 13:43:09 import os from shutil import rmtree diff --git a/nipype/interfaces/elastix/__init__.py b/nipype/interfaces/elastix/__init__.py index f8b31c5e50..66819f95db 100644 --- a/nipype/interfaces/elastix/__init__.py +++ b/nipype/interfaces/elastix/__init__.py @@ -1,12 +1,7 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +# 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 - code@oscaresteban.es -# @Date: 2014-06-02 12:06:07 -# @Last Modified by: oesteban -# @Last Modified time: 2014-06-17 10:59:20 + """Top-level namespace for elastix.""" from registration import Registration, ApplyWarp, AnalyzeWarp, PointsWarp diff --git a/nipype/interfaces/elastix/base.py b/nipype/interfaces/elastix/base.py index c837706159..2cbc5326f5 100644 --- a/nipype/interfaces/elastix/base.py +++ b/nipype/interfaces/elastix/base.py @@ -1,12 +1,7 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +# 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 - code@oscaresteban.es -# @Date: 2014-06-03 13:42:46 -# @Last Modified by: oesteban -# @Last Modified time: 2014-06-17 10:17:43 + """The :py:mod:`nipype.interfaces.elastix` provides the interface to the elastix registration software. diff --git a/nipype/interfaces/elastix/registration.py b/nipype/interfaces/elastix/registration.py index 8e304935e6..6c4c67aaa1 100644 --- a/nipype/interfaces/elastix/registration.py +++ b/nipype/interfaces/elastix/registration.py @@ -1,12 +1,7 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +# 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 - code@oscaresteban.es -# @Date: 2014-06-02 12:06:50 -# @Last Modified by: oesteban -# @Last Modified time: 2014-09-01 21:03:57 + """ Interfaces to perform image registrations and to apply the resulting displacement maps to images and points. diff --git a/nipype/interfaces/elastix/utils.py b/nipype/interfaces/elastix/utils.py index 2f361996a0..09d46adaf9 100644 --- a/nipype/interfaces/elastix/utils.py +++ b/nipype/interfaces/elastix/utils.py @@ -1,12 +1,7 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +# 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 - code@oscaresteban.es -# @Date: 2014-06-17 10:17:07 -# @Last Modified by: oesteban -# @Last Modified time: 2014-09-01 21:05:33 + """ Generic interfaces to manipulate registration parameters files, including transform files (to configure warpings) diff --git a/nipype/workflows/data/__init__.py b/nipype/workflows/data/__init__.py index b33f21e824..3f0dd77db1 100644 --- a/nipype/workflows/data/__init__.py +++ b/nipype/workflows/data/__init__.py @@ -1,9 +1,6 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# @Author: oesteban -# @Date: 2014-09-05 11:23:48 -# @Last Modified by: oesteban -# @Last Modified time: 2014-09-05 11:33:27 +# 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: import os.path as op diff --git a/nipype/workflows/dmri/preprocess/utils.py b/nipype/workflows/dmri/preprocess/utils.py index 256fa7667a..4b5561aa12 100644 --- a/nipype/workflows/dmri/preprocess/utils.py +++ b/nipype/workflows/dmri/preprocess/utils.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +# 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: 2014-08-30 10:53:13 -# @Last Modified by: oesteban -# @Last Modified time: 2014-09-05 11:31:30 + import nipype.pipeline.engine as pe import nipype.interfaces.utility as niu from nipype.interfaces import fsl From 805815d7d65634fde737ca57f4b91ee911d7eeb1 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Sat, 6 Sep 2014 11:59:32 +0200 Subject: [PATCH 27/33] back to old naming convention --- examples/dmri_preprocessing.py | 2 +- nipype/workflows/dmri/fsl/__init__.py | 4 ++++ nipype/workflows/dmri/{preprocess/epi.py => fsl/artifacts.py} | 0 nipype/workflows/dmri/{preprocess => fsl}/utils.py | 0 4 files changed, 5 insertions(+), 1 deletion(-) rename nipype/workflows/dmri/{preprocess/epi.py => fsl/artifacts.py} (100%) rename nipype/workflows/dmri/{preprocess => fsl}/utils.py (100%) diff --git a/examples/dmri_preprocessing.py b/examples/dmri_preprocessing.py index 823bb2c4c3..373ae64994 100644 --- a/examples/dmri_preprocessing.py +++ b/examples/dmri_preprocessing.py @@ -41,7 +41,7 @@ that is *A>>>P* or *-y* (in RAS systems). """ -from nipype.workflows.dmri.preprocess.epi import all_fsl_pipeline, remove_bias +from nipype.workflows.dmri.fsl.artifacts import all_fsl_pipeline, remove_bias """ Map field names into individual subject runs diff --git a/nipype/workflows/dmri/fsl/__init__.py b/nipype/workflows/dmri/fsl/__init__.py index 1b9e80b03d..c336329fb5 100644 --- a/nipype/workflows/dmri/fsl/__init__.py +++ b/nipype/workflows/dmri/fsl/__init__.py @@ -1,5 +1,9 @@ from dti import create_bedpostx_pipeline +from artifacts import (all_fmb_pipeline, all_peb_pipeline, all_fsl_pipeline, + hmc_pipeline, ecc_pipeline, sdc_fmb, sdc_peb, + remove_bias) + from epi import (fieldmap_correction, topup_correction, create_eddy_correct_pipeline, create_epidewarp_pipeline, create_dmri_preprocessing) diff --git a/nipype/workflows/dmri/preprocess/epi.py b/nipype/workflows/dmri/fsl/artifacts.py similarity index 100% rename from nipype/workflows/dmri/preprocess/epi.py rename to nipype/workflows/dmri/fsl/artifacts.py diff --git a/nipype/workflows/dmri/preprocess/utils.py b/nipype/workflows/dmri/fsl/utils.py similarity index 100% rename from nipype/workflows/dmri/preprocess/utils.py rename to nipype/workflows/dmri/fsl/utils.py From 326ea89301bc1f88722766d1aae09fa98352c547 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Sat, 6 Sep 2014 12:12:27 +0200 Subject: [PATCH 28/33] some leftovers changing names-convention --- nipype/workflows/dmri/__init__.py | 2 +- nipype/workflows/dmri/setup.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nipype/workflows/dmri/__init__.py b/nipype/workflows/dmri/__init__.py index 01b3d9746f..3d5a0590cb 100644 --- a/nipype/workflows/dmri/__init__.py +++ b/nipype/workflows/dmri/__init__.py @@ -1 +1 @@ -import camino, mrtrix, fsl, preprocess +import camino, mrtrix, fsl diff --git a/nipype/workflows/dmri/setup.py b/nipype/workflows/dmri/setup.py index f4112813ca..66156592e1 100644 --- a/nipype/workflows/dmri/setup.py +++ b/nipype/workflows/dmri/setup.py @@ -1,6 +1,7 @@ # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: -def configuration(parent_package='',top_path=None): + +def configuration(parent_package='', top_path=None): from numpy.distutils.misc_util import Configuration config = Configuration('dmri', parent_package, top_path) @@ -9,7 +10,6 @@ def configuration(parent_package='',top_path=None): config.add_subpackage('mrtrix') config.add_subpackage('fsl') config.add_subpackage('connectivity') - config.add_subpackage('preprocess') return config From a6acd1ca0c44f91cbc42aa79d979dd7e709984b5 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Sat, 6 Sep 2014 12:13:21 +0200 Subject: [PATCH 29/33] remove preprocess directory --- nipype/workflows/dmri/preprocess/__init__.py | 3 -- .../dmri/preprocess/tests/__init__.py | 2 - .../dmri/preprocess/tests/test_epi.py | 44 ------------------- nipype/workflows/dmri/setup.py | 1 + 4 files changed, 1 insertion(+), 49 deletions(-) delete mode 100644 nipype/workflows/dmri/preprocess/__init__.py delete mode 100644 nipype/workflows/dmri/preprocess/tests/__init__.py delete mode 100644 nipype/workflows/dmri/preprocess/tests/test_epi.py diff --git a/nipype/workflows/dmri/preprocess/__init__.py b/nipype/workflows/dmri/preprocess/__init__.py deleted file mode 100644 index 63d61376a4..0000000000 --- a/nipype/workflows/dmri/preprocess/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from epi import (all_fmb_pipeline, all_peb_pipeline, all_fsl_pipeline, - hmc_pipeline, ecc_pipeline, sdc_fmb, sdc_peb, - remove_bias) diff --git a/nipype/workflows/dmri/preprocess/tests/__init__.py b/nipype/workflows/dmri/preprocess/tests/__init__.py deleted file mode 100644 index 349937997e..0000000000 --- a/nipype/workflows/dmri/preprocess/tests/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: diff --git a/nipype/workflows/dmri/preprocess/tests/test_epi.py b/nipype/workflows/dmri/preprocess/tests/test_epi.py deleted file mode 100644 index a5bc06c761..0000000000 --- a/nipype/workflows/dmri/preprocess/tests/test_epi.py +++ /dev/null @@ -1,44 +0,0 @@ -#import os -# -#from nipype.testing import (skipif) -#import nipype.workflows.fmri.fsl as fsl_wf -#import nipype.interfaces.fsl as fsl -#import nipype.interfaces.utility as util -#from nipype.interfaces.fsl import no_fsl, no_fsl_course_data -# -#import nipype.pipeline.engine as pe -#import warnings -#import tempfile -#import shutil -#from nipype.workflows.dmri.preprocess.epi import eddy_correct -# -# -#@skipif(no_fsl) -#@skipif(no_fsl_course_data) -#def test_eddy_correct(): -# fsl_course_dir = os.path.abspath('fsl_course_data') -# dwi_file = os.path.join(fsl_course_dir, "fdt/subj1/data.nii.gz") -# bval_file = os.path.join(fsl_course_dir, "fdt/subj1/bval.txt") -# -# ecc = eddy_correct() -# ecc.inputs.inputnode.in_file = dwi_file -# ecc.inputs.inputnode.in_bval = bval_file -# -# with warnings.catch_warnings(): -# warnings.simplefilter("ignore") -# original_eddycorrect = pe.Node(interface=fsl.EddyCorrect(), name="original_eddycorrect") -# original_eddycorrect.inputs.in_file = dwi_file -# original_eddycorrect.inputs.ref_num = 0 -# -# test = pe.Node(util.AssertEqual(), name="eddy_corrected_dwi_test") -# -# pipeline = pe.Workflow(name="test_eddycorrect") -# pipeline.base_dir = tempfile.mkdtemp(prefix="nipype_test_eddycorrect_") -# -# pipeline.connect([(nipype_eddycorrect, test, [("outputnode.eddy_corrected", "volume1")]), -# (original_eddycorrect, test, [("eddy_corrected", "volume2")]), -# ]) -# -# pipeline.run(plugin='Linear') -# shutil.rmtree(pipeline.base_dir) - diff --git a/nipype/workflows/dmri/setup.py b/nipype/workflows/dmri/setup.py index 66156592e1..66d5fce20b 100644 --- a/nipype/workflows/dmri/setup.py +++ b/nipype/workflows/dmri/setup.py @@ -1,6 +1,7 @@ # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: + def configuration(parent_package='', top_path=None): from numpy.distutils.misc_util import Configuration From 36a7f05678970c1f33d2c74a56f72b3cf23874f8 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Sat, 6 Sep 2014 12:32:02 +0200 Subject: [PATCH 30/33] doctests: some fixed some added --- nipype/workflows/dmri/fsl/artifacts.py | 34 ++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/nipype/workflows/dmri/fsl/artifacts.py b/nipype/workflows/dmri/fsl/artifacts.py index f7dd5ae99e..9494b3dc75 100644 --- a/nipype/workflows/dmri/fsl/artifacts.py +++ b/nipype/workflows/dmri/fsl/artifacts.py @@ -30,6 +30,18 @@ def all_fmb_pipeline(name='hmc_sdc_ecc', [Leemans09]_. + Examples + -------- + + >>> from nipype.workflows.dmri.fsl.artifacts import all_fmb_pipeline + >>> allcorr = all_fmb_pipeline() + >>> allcorr.inputs.inputnode.in_file = 'epi.nii' + >>> allcorr.inputs.inputnode.in_bval = 'diffusion.bval' + >>> allcorr.inputs.inputnode.in_bvec = 'diffusion.bvec' + >>> allcorr.inputs.inputnode.bmap_mag = 'magnitude.nii' + >>> allcorr.inputs.inputnode.bmap_pha = 'phase.nii' + >>> allcorr.run() # doctest: +SKIP + """ inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_bvec', 'in_bval', 'bmap_pha', 'bmap_mag']), name='inputnode') @@ -105,6 +117,17 @@ def all_peb_pipeline(name='hmc_sdc_ecc', [Leemans09]_. + Examples + -------- + + >>> from nipype.workflows.dmri.fsl.artifacts import all_peb_pipeline + >>> allcorr = all_peb_pipeline() + >>> allcorr.inputs.inputnode.in_file = 'epi.nii' + >>> allcorr.inputs.inputnode.alt_file = 'epi_rev.nii' + >>> allcorr.inputs.inputnode.in_bval = 'diffusion.bval' + >>> allcorr.inputs.inputnode.in_bvec = 'diffusion.bvec' + >>> allcorr.run() # doctest: +SKIP + """ inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_bvec', 'in_bval', 'alt_file']), name='inputnode') @@ -285,7 +308,7 @@ def hmc_pipeline(name='motion_correct'): Example ------- - >>> from nipype.workflows.dmri.preprocess.epi import hmc_pipeline + >>> from nipype.workflows.dmri.fsl.artifacts import hmc_pipeline >>> hmc = hmc_pipeline() >>> hmc.inputs.inputnode.in_file = 'diffusion.nii' >>> hmc.inputs.inputnode.in_bvec = 'diffusion.bvec' @@ -394,7 +417,7 @@ def ecc_pipeline(name='eddy_correct'): Example ------- - >>> from nipype.workflows.dmri.preprocess.epi import ecc_pipeline + >>> from nipype.workflows.dmri.fsl.artifacts import ecc_pipeline >>> ecc = ecc_pipeline() >>> ecc.inputs.inputnode.in_file = 'diffusion.nii' >>> ecc.inputs.inputnode.in_bval = 'diffusion.bval' @@ -493,7 +516,7 @@ def sdc_fmb(name='fmb_correction', Example ------- - >>> from nipype.workflows.dmri.preprocess.epi import sdc_fmb + >>> from nipype.workflows.dmri.fsl.artifacts import sdc_fmb >>> fmb = sdc_fmb() >>> fmb.inputs.inputnode.in_file = 'diffusion.nii' >>> fmb.inputs.inputnode.in_bval = 'diffusion.bval' @@ -641,9 +664,10 @@ def sdc_peb(name='peb_correction', Example ------- - >>> from nipype.workflows.dmri.preprocess.epi import sdc_peb + >>> from nipype.workflows.dmri.fsl.artifacts import sdc_peb >>> peb = sdc_peb() - >>> peb.inputs.inputnode.in_file = 'diffusion.nii' + >>> peb.inputs.inputnode.in_file = 'epi.nii' + >>> peb.inputs.inputnode.alt_file = 'epi_rev.nii' >>> peb.inputs.inputnode.in_bval = 'diffusion.bval' >>> peb.inputs.inputnode.in_mask = 'mask.nii' >>> peb.run() # doctest: +SKIP From a166a7673e78881a28dfc3251311abd9401abb95 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Sat, 6 Sep 2014 12:41:11 +0200 Subject: [PATCH 31/33] fixed docstrings, added doctests --- nipype/workflows/dmri/fsl/artifacts.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/nipype/workflows/dmri/fsl/artifacts.py b/nipype/workflows/dmri/fsl/artifacts.py index 9494b3dc75..b7b502dfd3 100644 --- a/nipype/workflows/dmri/fsl/artifacts.py +++ b/nipype/workflows/dmri/fsl/artifacts.py @@ -204,6 +204,17 @@ def all_fsl_pipeline(name='fsl_all_correct', *DWI* [Jones10]_. + Examples + -------- + + >>> from nipype.workflows.dmri.fsl.artifacts import all_fsl_pipeline + >>> allcorr = all_fsl_pipeline() + >>> allcorr.inputs.inputnode.in_file = 'epi.nii' + >>> allcorr.inputs.inputnode.alt_file = 'epi_rev.nii' + >>> allcorr.inputs.inputnode.in_bval = 'diffusion.bval' + >>> allcorr.inputs.inputnode.in_bvec = 'diffusion.bvec' + >>> allcorr.run() # doctest: +SKIP + """ inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_bvec', @@ -285,7 +296,7 @@ def hmc_pipeline(name='motion_correct'): can be chained. This is useful to correct for artifacts with only one interpolation process (as previously discussed `here - `_), + `_), and also to compute nuisance regressors as proposed by [Yendiki13]_. .. warning:: This workflow rotates the `b`-vectors, so please be advised @@ -657,7 +668,7 @@ def sdc_peb(name='peb_correction', (e.g. *A>>>P* and *P>>>A*, or equivalently, *-y* and *y*) as in [Chiou2000]_, but it is also possible to use orthogonal configurations [Cordes2000]_ (e.g. *A>>>P* and *L>>>R*, - or equivalently *-y* and *x*). + or equivalently *-y* and *x*). This workflow uses the implementation of FSL (`TOPUP `_). @@ -756,6 +767,17 @@ def remove_bias(name='bias_correct'): `_. NeuroImage (2014). doi: 10.1016/j.neuroimage.2014.07.061 + + Example + ------- + + >>> from nipype.workflows.dmri.fsl.artifacts import remove_bias + >>> bias = remove_bias() + >>> bias.inputs.inputnode.in_file = 'epi.nii' + >>> bias.inputs.inputnode.in_bval = 'diffusion.bval' + >>> bias.inputs.inputnode.in_mask = 'mask.nii' + >>> bias.run() # doctest: +SKIP + """ inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_bval', 'in_mask']), name='inputnode') From f1e26c93bd6e233ea717d4c98339e717c58ffa65 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Sat, 6 Sep 2014 19:35:04 +0200 Subject: [PATCH 32/33] add nosearch to FLIRT in hmc --- nipype/workflows/dmri/fsl/artifacts.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/nipype/workflows/dmri/fsl/artifacts.py b/nipype/workflows/dmri/fsl/artifacts.py index b7b502dfd3..029977ef3b 100644 --- a/nipype/workflows/dmri/fsl/artifacts.py +++ b/nipype/workflows/dmri/fsl/artifacts.py @@ -345,9 +345,7 @@ def hmc_pipeline(name='motion_correct'): """ from nipype.workflows.data import get_flirt_schedule - params = dict(dof=6, bgvalue=0, save_log=True, - searchr_x=[-3, 3], searchr_y=[-3, 3], searchr_z=[-3, 3], - fine_search=1, coarse_search=2, + params = dict(dof=6, bgvalue=0, save_log=True, nosearch=True, #cost='mutualinfo', cost_func='mutualinfo', bins=64, schedule=get_flirt_schedule('hmc')) From 541b6aa1c930f1164a1f7a888cf7b801f73103a5 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Sat, 6 Sep 2014 19:57:49 +0200 Subject: [PATCH 33/33] fix bug (typo in argument name) --- nipype/workflows/dmri/fsl/artifacts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nipype/workflows/dmri/fsl/artifacts.py b/nipype/workflows/dmri/fsl/artifacts.py index 029977ef3b..54be28f914 100644 --- a/nipype/workflows/dmri/fsl/artifacts.py +++ b/nipype/workflows/dmri/fsl/artifacts.py @@ -345,7 +345,7 @@ def hmc_pipeline(name='motion_correct'): """ from nipype.workflows.data import get_flirt_schedule - params = dict(dof=6, bgvalue=0, save_log=True, nosearch=True, + params = dict(dof=6, bgvalue=0, save_log=True, no_search=True, #cost='mutualinfo', cost_func='mutualinfo', bins=64, schedule=get_flirt_schedule('hmc'))