File: labelsgmfix

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 (178 lines) | stat: -rwxr-xr-x 9,725 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
#!/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/.

# Script for 'repairing' a FreeSurfer parcellation image
# FreeSurfer's sub-cortical structure segmentation has been observed to be highly variable
#   under scan-rescan conditions. This introduces unwanted variability into the connectome,
#   as the parcellations don't overlap with the sub-cortical segmentations provided by
#   FIRST for the sake of Anatomically-Constrained Tractography. This script determines the
#   node indices that correspond to these structures, and replaces them with estimates
#   derived from FIRST.


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


import math, os



def usage(cmdline): #pylint: disable=unused-variable
  cmdline.set_author('Robert E. Smith (robert.smith@florey.edu.au)')
  cmdline.set_synopsis('In a FreeSurfer parcellation image, replace the sub-cortical grey matter structure delineations using FSL FIRST')
  cmdline.add_citation('Patenaude, B.; Smith, S. M.; Kennedy, D. N. & Jenkinson, M. A Bayesian model of shape and appearance for subcortical brain segmentation. NeuroImage, 2011, 56, 907-922', is_external=True)
  cmdline.add_citation('Smith, S. M.; Jenkinson, M.; Woolrich, M. W.; Beckmann, C. F.; Behrens, T. E.; Johansen-Berg, H.; Bannister, P. R.; De Luca, M.; Drobnjak, I.; Flitney, D. E.; Niazy, R. K.; Saunders, J.; Vickers, J.; Zhang, Y.; De Stefano, N.; Brady, J. M. & Matthews, P. M. Advances in functional and structural MR image analysis and implementation as FSL. NeuroImage, 2004, 23, S208-S219', is_external=True)
  cmdline.add_citation('Smith, R. E.; Tournier, J.-D.; Calamante, F. & Connelly, A. The effects of SIFT on the reproducibility and biological accuracy of the structural connectome. NeuroImage, 2015, 104, 253-265')
  cmdline.add_argument('parc',   help='The input FreeSurfer parcellation image')
  cmdline.add_argument('t1',     help='The T1 image to be provided to FIRST')
  cmdline.add_argument('lut',    help='The lookup table file that the parcellated image is based on')
  cmdline.add_argument('output', help='The output parcellation image')
  cmdline.add_argument('-premasked', action='store_true', default=False, help='Indicate that brain masking has been applied to the T1 input image')
  cmdline.add_argument('-sgm_amyg_hipp', action='store_true', default=False, help='Consider the amygdalae and hippocampi as sub-cortical grey matter structures, and also replace their estimates with those from FIRST')






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

  if utils.is_windows():
    raise MRtrixError('Script cannot run on Windows due to FSL dependency')

  app.check_output_path(path.from_user(app.ARGS.output, False))
  image.check_3d_nonunity(path.from_user(app.ARGS.t1, False))

  fsl_path = os.environ.get('FSLDIR', '')
  if not fsl_path:
    raise MRtrixError('Environment variable FSLDIR is not set; please run appropriate FSL configuration script')

  first_cmd = fsl.exe_name('run_first_all')

  first_atlas_path = os.path.join(fsl_path, 'data', 'first', 'models_336_bin')
  if not os.path.isdir(first_atlas_path):
    raise MRtrixError('Atlases required for FSL\'s FIRST program not installed;\nPlease install fsl-first-data using your relevant package manager')

  # Want a mapping between FreeSurfer node names and FIRST structure names
  # Just deal with the 5 that are used in ACT; FreeSurfer's hippocampus / amygdala segmentations look good enough.
  structure_map = { 'L_Accu':'Left-Accumbens-area',  'R_Accu':'Right-Accumbens-area',
                    'L_Caud':'Left-Caudate',         'R_Caud':'Right-Caudate',
                    'L_Pall':'Left-Pallidum',        'R_Pall':'Right-Pallidum',
                    'L_Puta':'Left-Putamen',         'R_Puta':'Right-Putamen',
                    'L_Thal':'Left-Thalamus-Proper', 'R_Thal':'Right-Thalamus-Proper' }
  if app.ARGS.sgm_amyg_hipp:
    structure_map.update({ 'L_Amyg':'Left-Amygdala',    'R_Amyg':'Right-Amygdala',
                           'L_Hipp':'Left-Hippocampus', 'R_Hipp':'Right-Hippocampus' })

  t1_spacing = image.Header(path.from_user(app.ARGS.t1, False)).spacing()
  upsample_for_first = False
  # If voxel size is 1.25mm or larger, make a guess that the user has erroneously re-gridded their data
  if math.pow(t1_spacing[0] * t1_spacing[1] * t1_spacing[2], 1.0/3.0) > 1.225:
    app.warn('Voxel size of input T1 image larger than expected for T1-weighted images (' + str(t1_spacing) + '); '
             'image will be resampled to 1mm isotropic in order to maximise chance of '
             'FSL FIRST script succeeding')
    upsample_for_first = True

  app.make_scratch_dir()

  # Get the parcellation and T1 images into the scratch directory, with conversion of the T1 into the correct format for FSL
  run.command('mrconvert ' + path.from_user(app.ARGS.parc) + ' ' + path.to_scratch('parc.mif'))
  if upsample_for_first:
    run.command('mrgrid ' + path.from_user(app.ARGS.t1) + ' regrid - -voxel 1.0 -interp sinc | mrcalc - 0.0 -max - | mrconvert - ' + path.to_scratch('T1.nii') + ' -strides -1,+2,+3')
  else:
    run.command('mrconvert ' + path.from_user(app.ARGS.t1) + ' ' + path.to_scratch('T1.nii') + ' -strides -1,+2,+3')

  app.goto_scratch_dir()

  # Run FIRST
  first_input_is_brain_extracted = ''
  if app.ARGS.premasked:
    first_input_is_brain_extracted = ' -b'
  first_stdout = run.command(first_cmd + ' -m none -s ' + ','.join(structure_map.keys()) + ' -i T1.nii' + first_input_is_brain_extracted + ' -o first').stdout
  fsl.check_first('first', structures=structure_map.keys(), first_stdout=first_stdout)

  # Generate an empty image that will be used to construct the new SGM nodes
  run.command('mrcalc parc.mif 0 -min sgm.mif')

  # Read the local connectome LUT file
  # This will map a structure name to an index
  sgm_lut = {}
  sgm_lut_file_name = 'FreeSurferSGM.txt'
  sgm_lut_file_path = os.path.join(path.shared_data_path(), path.script_subdir_name(), sgm_lut_file_name)
  with open(sgm_lut_file_path) as sgm_lut_file:
    for line in sgm_lut_file:
      line = line.rstrip()
      if line and line[0]!='#':
        line = line.split()
        sgm_lut[line[1]] = line[0] # This can remain as a string

  # Convert FIRST meshes to node masks
  # In this use case, don't want the PVE images; want to threshold at 0.5
  mask_list = [ ]
  progress = app.ProgressBar('Generating mask images for SGM structures', len(structure_map))
  for key, value in structure_map.items():
    image_path = key + '_mask.mif'
    mask_list.append(image_path)
    vtk_in_path = 'first-' + key + '_first.vtk'
    run.command('meshconvert ' + vtk_in_path + ' first-' + key + '_transformed.vtk -transform first2real T1.nii')
    run.command('mesh2voxel first-' + key + '_transformed.vtk parc.mif - | mrthreshold - ' + image_path + ' -abs 0.5')
    # Add to the SGM image; don't worry about overlap for now
    node_index = sgm_lut[value]
    run.command('mrcalc ' + image_path + ' ' + node_index + ' sgm.mif -if sgm_new.mif')
    if not app.CONTINUE_OPTION:
      run.function(os.remove, 'sgm.mif')
      run.function(os.rename, 'sgm_new.mif', 'sgm.mif')
    progress.increment()
  progress.done()

  # Detect any overlapping voxels between the SGM masks, and set to zero
  run.command(['mrmath', mask_list, 'sum', '-', '|', \
               'mrcalc', '-', '1', '-gt', 'sgm_overlap_mask.mif'])
  run.command('mrcalc sgm_overlap_mask.mif 0 sgm.mif -if sgm_masked.mif')

  # Convert the SGM label image to the indices that are required based on the user-provided LUT file
  run.command('labelconvert sgm_masked.mif ' + sgm_lut_file_path + ' ' + path.from_user(app.ARGS.lut) + ' sgm_new_labels.mif')

  # For each SGM structure:
  # * Figure out what index the structure has been mapped to; this can only be done using mrstats
  # * Strip that index from the parcellation image
  # * Insert the new delineation of that structure
  progress = app.ProgressBar('Replacing SGM parcellations', len(structure_map))
  for struct in structure_map:
    image_path = struct + '_mask.mif'
    index = int(image.statistics('sgm_new_labels.mif', mask=image_path).median)
    run.command('mrcalc parc.mif ' + str(index) + ' -eq 0 parc.mif -if parc_removed.mif')
    run.function(os.remove, 'parc.mif')
    run.function(os.rename, 'parc_removed.mif', 'parc.mif')
    progress.increment()
  progress.done()

  # Insert the new delineations of all SGM structures in a single call
  # Enforce unsigned integer datatype of output image
  run.command('mrcalc sgm_new_labels.mif 0.5 -gt sgm_new_labels.mif parc.mif -if result.mif -datatype uint32')
  run.command('mrconvert result.mif ' + path.from_user(app.ARGS.output), mrconvert_keyval=path.from_user(app.ARGS.parc, False), force=app.FORCE_OVERWRITE)





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