File: dwigradcheck

package info (click to toggle)
mrtrix3 3.0~rc3%2Bgit135-g2b8e7d0c2-5
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 34,548 kB
  • sloc: cpp: 117,101; python: 6,472; sh: 638; makefile: 231; xml: 39; ansic: 20
file content (215 lines) | stat: -rwxr-xr-x 9,109 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
#!/usr/bin/python3

# Script for checking the orientation of the diffusion gradient table

# Make the corresponding MRtrix3 Python libraries available
import inspect, os, sys
lib_folder = os.path.realpath(os.path.join(os.path.dirname(os.path.realpath(inspect.getfile(inspect.currentframe()))), os.pardir, 'lib'))
if not os.path.isdir(lib_folder):
  sys.stderr.write('Unable to locate MRtrix3 Python libraries')
  sys.exit(1)
sys.path.insert(0, lib_folder)

import copy, numbers, shutil
from mrtrix3 import app, image, run, path

app.init('Robert E. Smith (robert.smith@florey.edu.au)', 'Check the orientation of the diffusion gradient table')
app.cmdline.addCitation('','Jeurissen, B.; Leemans, A.; Sijbers, J. Automated correction of improperly rotated diffusion gradient orientations in diffusion weighted MRI. Medical Image Analysis, 2014, 18(7), 953-962', False)
app.cmdline.add_argument('input', help='The input DWI series to be checked')

grad_export = app.cmdline.add_argument_group('Options for exporting the estimated best gradient table')
grad_export.add_argument('-export_grad_mrtrix', metavar='grad', help='Export the final gradient table in MRtrix format')
grad_export.add_argument('-export_grad_fsl', nargs=2, metavar=('bvecs', 'bvals'), help='Export the final gradient table in FSL bvecs/bvals format')

grad_import = app.cmdline.add_argument_group('Options for importing the gradient table')
grad_import.add_argument('-grad', help='Provide a gradient table in MRtrix format')
grad_import.add_argument('-fslgrad', nargs=2, metavar=('bvecs', 'bvals'), help='Provide a gradient table in FSL bvecs/bvals format')

options = app.cmdline.add_argument_group('Options for the dwigradcheck script')
options.add_argument('-mask', help='Provide a brain mask image')
options.add_argument('-number', type=int, default=10000, help='Set the number of tracks to generate for each test')

app.cmdline.flagMutuallyExclusiveOptions( ['grad', 'fslgrad' ])
app.cmdline.flagMutuallyExclusiveOptions( ['export_grad_mrtrix', 'export_grad_fsl' ])
app.parse()

image_dimensions = image.Header(path.fromUser(app.args.input, False)).size()
if len(image_dimensions) != 4:
  app.error('Input image must be a 4D image')
if min(image_dimensions) == 1:
  app.error('Cannot perform tractography on an image with a unity dimension')
num_volumes = image_dimensions[3]

app.makeTempDir()

# Make sure the image data can be memory-mapped
run.command('mrconvert ' + app.args.input + ' ' + path.toTemp('data.mif', True) + ' -strides 0,0,0,1 -datatype float32')

if app.args.grad:
  shutil.copy(path.fromUser(app.args.grad, False), path.toTemp('grad.b', False))
elif app.args.fslgrad:
  shutil.copy(path.fromUser(app.args.fslgrad[0], False), path.toTemp('bvecs', False))
  shutil.copy(path.fromUser(app.args.fslgrad[1], False), path.toTemp('bvals', False))
if app.args.mask:
  run.command('mrconvert ' + path.fromUser(app.args.mask, True) + ' ' + path.toTemp('mask.mif', True) + ' -datatype bit')

app.gotoTempDir()



# Make sure we have gradient table stored externally to header in both MRtrix and FSL formats
if not os.path.isfile('grad.b'):
  if os.path.isfile('bvecs'):
    run.command('mrinfo data.mif -fslgrad bvecs bvals -export_grad_mrtrix grad.b')
  else:
    run.command('mrinfo data.mif -export_grad_mrtrix grad.b')

if not os.path.isfile('bvecs'):
  if os.path.isfile('grad.b'):
    run.command('mrinfo data.mif -grad grad.b -export_grad_fsl bvecs bvals')
  else:
    run.command('mrinfo data.mif -export_grad_fsl bvecs bvals')

# Import both of these into local memory
with open('grad.b', 'r') as f:
  grad_mrtrix = f.read().split('\n')
with open('bvecs', 'r') as f:
  grad_fsl = f.read().split('\n')
# Erase the empty last line if necessary
if len(grad_mrtrix[-1]) <= 1:
  grad_mrtrix.pop()
if len(grad_fsl[-1]) <= 1:
  grad_fsl.pop()
# Turn into float matrices
grad_mrtrix = [ [ float(f) for f in line.split(' ') ] for line in grad_mrtrix ]
grad_fsl = [ [ float(f) for f in line.split() ] for line in grad_fsl ]
# Is our gradient table of the correct length?
if not len(grad_mrtrix) == num_volumes:
  app.error('Number of entries in gradient table does not match number of DWI volumes')
if not len(grad_fsl) == 3 or not len(grad_fsl[0]) == num_volumes:
  app.error('Internal error (inconsistent gradient table storage)')


# Generate a brain mask if we weren't provided with one
# Note that gradient table must be explicitly loaded, since there may not
#   be one in the image header (user may be relying on -grad or -fslgrad input options)
if not os.path.exists('mask.mif'):
  run.command('dwi2mask data.mif mask.mif -grad grad.b')

# How many tracks are we going to generate?
number_option = ' -select ' + str(app.args.number)


#  What variations of gradient errors can we conceive?

# Done:
# * Has an axis been flipped? (none, 0, 1, 2)
# * Have axes been swapped? (012 021 102 120 201 210)
# * For both flips & swaps, it could occur in either scanner or image space...

# To do:
# * Have the gradients been defined with respect to image space rather than scanner space?
# * After conversion to gradients in image space, are they _then_ defined with respect to scanner space?
#   (should the above two be tested independently from the axis flips / permutations?)


axis_flips = [ 'none', 0, 1, 2 ]
axis_permutations = [ ( 0, 1, 2 ), (0, 2, 1), (1, 0, 2), (1, 2, 0), (2, 0, 1), (2, 1, 0) ]
grad_basis = [ 'scanner', 'image' ]
total_tests = len(axis_flips) * len(axis_permutations) * len(grad_basis)


# List where the first element is the mean length
lengths = [ ]

progress = app.progressBar('Testing gradient table alterations (0 of ' + str(total_tests) + ')', total_tests)

for flip in axis_flips:
  for permutation in axis_permutations:
    for basis in grad_basis:

      suffix = '_flip' + str(flip) + '_perm' + ''.join(str(item) for item in permutation) + '_' + basis

      if basis == 'scanner':

        grad = copy.copy(grad_mrtrix)

        # Don't do anything if there aren't any axis flips occurring (flip == 'none')
        if isinstance(flip, numbers.Number):
          multiplier = [ 1.0, 1.0, 1.0, 1.0 ]
          multiplier[flip] = -1.0
          grad = [ [ r*m for r,m in zip(row, multiplier) ] for row in grad ]

        grad = [ [ row[permutation[0]], row[permutation[1]], row[permutation[2]], row[3] ] for row in grad ]

        # Create the gradient table file
        grad_path = 'grad' + suffix + '.b'
        with open(grad_path, 'w') as f:
          for line in grad:
            f.write (','.join([str(v) for v in line]) + '\n')

        grad_option = ' -grad ' + grad_path

      elif basis == 'image':

        grad = copy.copy(grad_fsl)

        if isinstance(flip, numbers.Number):
          grad[flip] = [ -v for v in grad[flip] ]

        grad = [ grad[permutation[0]], grad[permutation[1]], grad[permutation[2]] ]

        grad_path = 'bvecs' + suffix
        with open(grad_path, 'w') as f:
          for line in grad:
            f.write (' '.join([str(v) for v in line]) + '\n')

        grad_option = ' -fslgrad ' + grad_path + ' bvals'

      # Run the tracking experiment
      run.command('tckgen -algorithm tensor_det data.mif' + grad_option + ' -seed_image mask.mif -mask mask.mif' + number_option + ' -minlength 0 -downsample 5 tracks' + suffix + '.tck')

      # Get the mean track length
      meanlength=float(run.command('tckstats tracks' + suffix + '.tck -output mean -ignorezero')[0])

      # Add to the database
      lengths.append([meanlength,flip,permutation,basis])

      # Increament the progress bar
      progress.increment('Testing gradient table alterations (' + str(len(lengths)) + ' of ' + str(total_tests) + ')')

progress.done()

# Sort the list to find the best gradient configuration(s)
lengths.sort()
lengths.reverse()


# Provide a printout of the mean streamline length of each gradient table manipulation
sys.stderr.write('Mean length     Axis flipped    Axis permutations    Axis basis\n')
for line in lengths:
  if isinstance(line[1], numbers.Number):
    flip_str = "{:4d}".format(line[1])
  else:
    flip_str = line[1]
  sys.stderr.write("{:5.2f}".format(line[0]) + '         ' + flip_str + '                ' + str(line[2]) + '           ' + line[3] + '\n')


# If requested, extract what has been detected as the best gradient table, and
#   export it in the format requested by the user
grad_export_option = ''
if app.args.export_grad_mrtrix:
  grad_export_option = ' -export_grad_mrtrix ' + path.fromUser(app.args.export_grad_mrtrix, True)
elif app.args.export_grad_fsl:
  grad_export_option = ' -export_grad_fsl ' + path.fromUser(app.args.export_grad_fsl[0], True) + ' ' + path.fromUser(app.args.export_grad_fsl[1], True)
if grad_export_option:
  best = lengths[0]
  suffix = '_flip' + str(best[1]) + '_perm' + ''.join(str(item) for item in best[2]) + '_' + best[3]
  if best[3] == 'scanner':
    grad_import_option = ' -grad grad' + suffix + '.b'
  elif best[3] == 'image':
    grad_import_option = ' -fslgrad bvecs' + suffix + ' bvals'
  run.command('mrinfo data.mif' + grad_import_option + grad_export_option)


app.complete()