File: dwigradcheck

package info (click to toggle)
mrtrix3 3.0.8-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 15,300 kB
  • sloc: cpp: 130,470; python: 9,603; sh: 597; makefile: 62; xml: 47
file content (215 lines) | stat: -rwxr-xr-x 9,021 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

# Copyright (c) 2008-2025 the MRtrix3 contributors.
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# Covered Software is provided under this License on an "as is"
# basis, without warranty of any kind, either expressed, implied, or
# statutory, including, without limitation, warranties that the
# Covered Software is free of defects, merchantable, fit for a
# particular purpose or non-infringing.
# See the Mozilla Public License v. 2.0 for more details.
#
# For more details, see http://www.mrtrix.org/.

# note: deal with these warnings properly when we drop support for Python 2:
# pylint: disable=unspecified-encoding,consider-using-f-string

import copy, numbers, os, shutil, sys



def usage(cmdline): #pylint: disable=unused-variable
  from mrtrix3 import app #pylint: disable=no-name-in-module, import-outside-toplevel
  cmdline.set_author('Robert E. Smith (robert.smith@florey.edu.au)')
  cmdline.set_synopsis('Check the orientation of the diffusion gradient table')
  cmdline.add_description('Note that the corrected gradient table can be output using the -export_grad_{mrtrix,fsl} option.')
  cmdline.add_citation('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')
  cmdline.add_argument('input', help='The input DWI series to be checked')
  cmdline.add_argument('-mask', metavar='image', help='Provide a brain mask image')
  cmdline.add_argument('-number', type=int, default=10000, help='Set the number of tracks to generate for each test')

  app.add_dwgrad_export_options(cmdline)
  app.add_dwgrad_import_options(cmdline)




def execute(): #pylint: disable=unused-variable
  from mrtrix3 import MRtrixError #pylint: disable=no-name-in-module, import-outside-toplevel
  from mrtrix3 import app, image, matrix, path, run #pylint: disable=no-name-in-module, import-outside-toplevel

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

  app.make_scratch_dir()

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

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

  app.goto_scratch_dir()

  # 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
  grad_mrtrix = matrix.load_matrix('grad.b')
  grad_fsl = matrix.load_matrix('bvecs')
  # Is our gradient table of the correct length?
  if not len(grad_mrtrix) == num_volumes:
    raise MRtrixError('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:
    raise MRtrixError('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 grad_file:
            for line in grad:
              grad_file.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 bvecs_file:
            for line in grad:
              bvecs_file.write (' '.join([str(v) for v in line]) + '\n')

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

        else:
          assert False

        # 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').stdout)

        # 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 = app.read_dwgrad_export_options()
  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'
    else:
      assert False
    run.command('mrinfo data.mif' + grad_import_option + grad_export_option, force=app.FORCE_OVERWRITE)


# Execute the script
import mrtrix3
mrtrix3.execute() #pylint: disable=no-member