|
| 1 | +#!/usr/bin/env python |
| 2 | +# -*- coding: utf-8 -*- |
| 3 | +# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- |
| 4 | +# vi: set ft=python sts=4 ts=4 sw=4 et: |
| 5 | +# @Author: oesteban |
| 6 | +# @Date: 2014-08-30 10:53:13 |
| 7 | +# @Last Modified by: oesteban |
| 8 | +# @Last Modified time: 2014-08-30 16:06:15 |
| 9 | + |
| 10 | +def cleanup_edge_pipeline(name='Cleanup'): |
| 11 | + """ |
| 12 | + Perform some de-spiking filtering to clean up the edge of the fieldmap |
| 13 | + (copied from fsl_prepare_fieldmap) |
| 14 | + """ |
| 15 | + import nipype.pipeline.engine as pe |
| 16 | + import nipype.interfaces.utility as niu |
| 17 | + import nipype.interfaces.fsl as fsl |
| 18 | + |
| 19 | + inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_mask']), |
| 20 | + name='inputnode') |
| 21 | + outputnode = pe.Node(niu.IdentityInterface(fields=['out_file']), |
| 22 | + name='outputnode') |
| 23 | + |
| 24 | + fugue = pe.Node(fsl.FUGUE(save_fmap=True, despike_2dfilter=True, despike_threshold=2.1), |
| 25 | + name='Despike') |
| 26 | + erode = pe.Node(fsl.maths.MathsCommand(nan2zeros=True, |
| 27 | + args='-kernel 2D -ero'), name='MskErode') |
| 28 | + newmsk = pe.Node(fsl.MultiImageMaths(op_string='-sub %s -thr 0.5 -bin'), |
| 29 | + name='NewMask') |
| 30 | + applymsk = pe.Node(fsl.ApplyMask(nan2zeros=True), name='ApplyMask') |
| 31 | + join = pe.Node(niu.Merge(2), name='Merge') |
| 32 | + addedge = pe.Node(fsl.MultiImageMaths(op_string='-mas %s -add %s'), |
| 33 | + name='AddEdge') |
| 34 | + |
| 35 | + wf = pe.Workflow(name=name) |
| 36 | + wf.connect([ |
| 37 | + (inputnode, fugue, [('in_file', 'fmap_in_file'), |
| 38 | + ('in_mask', 'mask_file')]) |
| 39 | + ,(inputnode, erode, [('in_mask', 'in_file')]) |
| 40 | + ,(inputnode, newmsk, [('in_mask', 'in_file')]) |
| 41 | + ,(erode, newmsk, [('out_file', 'operand_files')]) |
| 42 | + ,(fugue, applymsk, [('fmap_out_file', 'in_file')]) |
| 43 | + ,(newmsk, applymsk, [('out_file', 'mask_file')]) |
| 44 | + ,(erode, join, [('out_file', 'in1')]) |
| 45 | + ,(applymsk, join, [('out_file', 'in2')]) |
| 46 | + ,(inputnode, addedge, [('in_file', 'in_file')]) |
| 47 | + ,(join, addedge, [('out', 'operand_files')]) |
| 48 | + ,(addedge, outputnode, [('out_file', 'out_file')]) |
| 49 | + ]) |
| 50 | + return wf |
| 51 | + |
| 52 | + |
| 53 | +def recompose_dwi(in_dwi, in_bval, in_corrected, out_file=None): |
| 54 | + """ |
| 55 | + Recompose back the dMRI data accordingly the b-values table after EC correction |
| 56 | + """ |
| 57 | + import numpy as np |
| 58 | + import nibabel as nb |
| 59 | + import os.path as op |
| 60 | + |
| 61 | + if out_file is None: |
| 62 | + fname,ext = op.splitext(op.basename(in_dwi)) |
| 63 | + if ext == ".gz": |
| 64 | + fname,ext2 = op.splitext(fname) |
| 65 | + ext = ext2 + ext |
| 66 | + out_file = op.abspath("%s_eccorrect%s" % (fname, ext)) |
| 67 | + |
| 68 | + im = nb.load(in_dwi) |
| 69 | + dwidata = im.get_data() |
| 70 | + bvals = np.loadtxt(in_bval) |
| 71 | + non_b0 = np.where(bvals!=0)[0].tolist() |
| 72 | + |
| 73 | + if len(non_b0)!=len(in_corrected): |
| 74 | + raise RuntimeError('Length of DWIs in b-values table and after correction should match') |
| 75 | + |
| 76 | + for bindex, dwi in zip(non_b0, in_corrected): |
| 77 | + dwidata[...,bindex] = nb.load(dwi).get_data() |
| 78 | + |
| 79 | + nb.Nifti1Image(dwidata, im.get_affine(), im.get_header()).to_filename(out_file) |
| 80 | + return out_file |
| 81 | + |
| 82 | +def recompose_xfm(in_bval, in_xfms): |
| 83 | + """ |
| 84 | + Insert identity transformation matrices in b0 volumes to build up a list |
| 85 | + """ |
| 86 | + import numpy as np |
| 87 | + import os.path as op |
| 88 | + |
| 89 | + bvals = np.loadtxt(in_bval) |
| 90 | + out_matrix = np.array([np.eye(4)] * len(bvals)) |
| 91 | + xfms = iter([np.loadtxt(xfm) for xfm in in_xfms]) |
| 92 | + out_files = [] |
| 93 | + |
| 94 | + for i, b in enumerate(bvals): |
| 95 | + if b == 0.0: |
| 96 | + mat = np.eye(4) |
| 97 | + else: |
| 98 | + mat = xfms.next() |
| 99 | + |
| 100 | + out_name = 'eccor_%04d.mat' % i |
| 101 | + out_files.append(out_name) |
| 102 | + np.savetxt(out_name, mat) |
| 103 | + |
| 104 | + return out_files |
| 105 | + |
| 106 | + |
| 107 | +def b0_average(in_dwi, in_bval, out_file=None): |
| 108 | + """ |
| 109 | + A function that averages the *b0* volumes from a DWI dataset. |
| 110 | +
|
| 111 | + .. warning:: *b0* should be already registered (head motion artifact should |
| 112 | + be corrected). |
| 113 | +
|
| 114 | + """ |
| 115 | + import numpy as np |
| 116 | + import nibabel as nb |
| 117 | + import os.path as op |
| 118 | + |
| 119 | + if out_file is None: |
| 120 | + fname,ext = op.splitext(op.basename(in_dwi)) |
| 121 | + if ext == ".gz": |
| 122 | + fname,ext2 = op.splitext(fname) |
| 123 | + ext = ext2 + ext |
| 124 | + out_file = op.abspath("%s_avg_b0%s" % (fname, ext)) |
| 125 | + |
| 126 | + imgs = np.array(nb.four_to_three(nb.load(in_dwi))) |
| 127 | + bval = np.loadtxt(in_bval) |
| 128 | + b0s = [im.get_data().astype(np.float32) for im in imgs[np.where(bval==0)]] |
| 129 | + b0 = np.average(np.array(b0s), axis=0) |
| 130 | + |
| 131 | + hdr = imgs[0].get_header().copy() |
| 132 | + hdr.set_data_shape(b0.shape) |
| 133 | + hdr.set_xyzt_units('mm') |
| 134 | + hdr.set_data_dtype(np.float32) |
| 135 | + nb.Nifti1Image(b0, imgs[0].get_affine(), hdr).to_filename(out_file) |
| 136 | + return out_file |
| 137 | + |
| 138 | + |
| 139 | +def rotate_bvecs(in_bvec, in_matrix): |
| 140 | + """ |
| 141 | + Rotates the input bvec file accordingly with a list of matrices. |
| 142 | +
|
| 143 | + .. note:: the input affine matrix transforms points in the destination image to their \ |
| 144 | + corresponding coordinates in the original image. Therefore, this matrix should be inverted \ |
| 145 | + first, as we want to know the target position of :math:`\\vec{r}`. |
| 146 | +
|
| 147 | + """ |
| 148 | + import os |
| 149 | + import numpy as np |
| 150 | + |
| 151 | + name, fext = os.path.splitext(os.path.basename(in_bvec)) |
| 152 | + if fext == '.gz': |
| 153 | + name, _ = os.path.splitext(name) |
| 154 | + out_file = os.path.abspath('./%s_rotated.bvec' % name) |
| 155 | + bvecs = np.loadtxt(in_bvec).T |
| 156 | + new_bvecs = [] |
| 157 | + |
| 158 | + if len(bvecs) != len(in_matrix): |
| 159 | + raise RuntimeError('Number of b-vectors and rotation matrices should match.') |
| 160 | + |
| 161 | + for bvec, mat in zip(bvecs, in_matrix): |
| 162 | + if np.all(bvec==0.0): |
| 163 | + new_bvecs.append(bvec) |
| 164 | + else: |
| 165 | + invrot = np.linalg.inv(np.loadtxt(mat))[:3,:3] |
| 166 | + newbvec = invrot.dot(bvec) |
| 167 | + new_bvecs.append((newbvec/np.linalg.norm(newbvec))) |
| 168 | + |
| 169 | + np.savetxt(out_file, np.array(new_bvecs).T, fmt='%0.15f') |
| 170 | + return out_file |
| 171 | + |
| 172 | +def siemens2rads(in_file, out_file=None): |
| 173 | + """ |
| 174 | + Converts input phase difference map to rads |
| 175 | + """ |
| 176 | + import numpy as np |
| 177 | + import nibabel as nb |
| 178 | + import os.path as op |
| 179 | + import math |
| 180 | + |
| 181 | + if out_file is None: |
| 182 | + fname, fext = op.splitext(op.basename(in_file)) |
| 183 | + if fext == '.gz': |
| 184 | + fname, _ = op.splitext(fname) |
| 185 | + out_file = op.abspath('./%s_rads.nii.gz' % fname) |
| 186 | + |
| 187 | + in_file = np.atleast_1d(in_file).tolist() |
| 188 | + im = nb.load(in_file[0]) |
| 189 | + data = im.get_data().astype(np.float32) |
| 190 | + hdr = im.get_header().copy() |
| 191 | + |
| 192 | + if len(in_file) == 2: |
| 193 | + data = data - nb.load(in_file[1]).get_data().astype(np.float32) |
| 194 | + elif (data.ndim == 4) and (data.shape[-1] == 2): |
| 195 | + data = np.squeeze(data[...,0] - data[...,1]) |
| 196 | + hdr.set_data_shape(data.shape[:3]) |
| 197 | + |
| 198 | + imin = data.min() |
| 199 | + imax = data.max() |
| 200 | + data = (2.0 * math.pi * (data - imin)/(imax-imin)) - math.pi |
| 201 | + hdr.set_data_dtype(np.float32) |
| 202 | + hdr.set_xyzt_units('mm') |
| 203 | + hdr['datatype'] = 16 |
| 204 | + nb.Nifti1Image(data, im.get_affine(), hdr).to_filename(out_file) |
| 205 | + return out_file |
| 206 | + |
| 207 | +def rads2radsec(in_file, delta_te, out_file=None): |
| 208 | + """ |
| 209 | + Converts input phase difference map to rads |
| 210 | + """ |
| 211 | + import numpy as np |
| 212 | + import nibabel as nb |
| 213 | + import os.path as op |
| 214 | + import math |
| 215 | + |
| 216 | + if out_file is None: |
| 217 | + fname, fext = op.splitext(op.basename(in_file)) |
| 218 | + if fext == '.gz': |
| 219 | + fname, _ = op.splitext(fname) |
| 220 | + out_file = op.abspath('./%s_radsec.nii.gz' % fname) |
| 221 | + |
| 222 | + im = nb.load(in_file) |
| 223 | + data = im.get_data().astype(np.float32) * (1.0/delta_te) |
| 224 | + nb.Nifti1Image(data, im.get_affine(), |
| 225 | + im.get_header()).to_filename(out_file) |
| 226 | + return out_file |
| 227 | + |
| 228 | +def demean_image(in_file, in_mask=None, out_file=None): |
| 229 | + """ |
| 230 | + Demean image data inside mask |
| 231 | + """ |
| 232 | + import numpy as np |
| 233 | + import nibabel as nb |
| 234 | + import os.path as op |
| 235 | + import math |
| 236 | + |
| 237 | + if out_file is None: |
| 238 | + fname, fext = op.splitext(op.basename(in_file)) |
| 239 | + if fext == '.gz': |
| 240 | + fname, _ = op.splitext(fname) |
| 241 | + out_file = op.abspath('./%s_demean.nii.gz' % fname) |
| 242 | + |
| 243 | + im = nb.load(in_file) |
| 244 | + data = im.get_data().astype(np.float32) |
| 245 | + msk = np.ones_like(data) |
| 246 | + |
| 247 | + if not in_mask is None: |
| 248 | + msk = nb.load(in_mask).get_data().astype(np.float32) |
| 249 | + msk[msk>0] = 1.0 |
| 250 | + msk[msk<1] = 0.0 |
| 251 | + |
| 252 | + mean = np.median(data[msk==1].reshape(-1)) |
| 253 | + data[msk==1] = data[msk==1] - mean |
| 254 | + nb.Nifti1Image(data, im.get_affine(), im.get_header()).to_filename(out_file) |
| 255 | + return out_file |
0 commit comments