Example #1
0
def getInputFiles():
  import os
  import lib.app
  from lib.runCommand  import runCommand
  from lib.warnMessage import warnMessage
  mask_path = os.path.join(lib.app.tempDir, 'mask.mif')
  if os.path.exists(mask_path):
    warnMessage('-mask option is ignored by algorithm \'manual\'')
    os.remove(mask_path)
  runCommand('mrconvert ' + lib.app.args.in_voxels + ' ' + os.path.join(lib.app.tempDir, 'in_voxels.mif'))
  if lib.app.args.dirs:
    runCommand('mrconvert ' + lib.app.args.dirs + ' ' + os.path.join(lib.app.tempDir, 'dirs.mif') + ' -stride 0,0,0,1')
Example #2
0
def runCommand(cmd, exitOnError=True):

  import lib.app, os, sys
  from lib.errorMessage import errorMessage
  from lib.warnMessage  import warnMessage
  import distutils
  from distutils.spawn import find_executable
  global mrtrix_bin_list
  global mrtrix_bin_path
  
  if not mrtrix_bin_list:
    mrtrix_bin_path = os.path.join(os.path.abspath(os.path.dirname(os.path.realpath(__file__))), os.pardir, os.pardir, 'release', 'bin');
    mrtrix_bin_list = os.listdir(mrtrix_bin_path)
    
  if lib.app.lastFile:
    # Check to see if the last file produced is produced by this command;
    #   if it is, this will be the last called command that gets skipped
    if lib.app.lastFile in cmd:
      lib.app.lastFile = ''
    if lib.app.verbosity:
      sys.stdout.write('Skipping command: ' + cmd + '\n')
      sys.stdout.flush()
    return

  binary_name = cmd.split()[0]

  # Automatically add the relevant flags to any mrtrix command calls, including filling them in around the pipes
  if binary_name in mrtrix_bin_list:
    cmdsplit = cmd.split()
    binary_in_path = find_executable(binary_name)
    if not binary_in_path or not os.path.samefile(binary_in_path, os.path.join(mrtrix_bin_path, binary_name)):
      cmdsplit[0] =  os.path.join(mrtrix_bin_path, binary_name)
    for index, item in enumerate(cmdsplit):
      if item == '|':
        if lib.app.mrtrixNThreads:
          cmdsplit[index] = lib.app.mrtrixNThreads + ' |'
        if lib.app.mrtrixQuiet:
          cmdsplit[index] = lib.app.mrtrixQuiet + ' ' + cmdsplit[index]
    cmdsplit.append(lib.app.mrtrixNThreads)
    cmdsplit.append(lib.app.mrtrixQuiet)
    cmd = ' '.join(cmdsplit)
    
  if lib.app.verbosity:
    sys.stdout.write(lib.app.colourConsole + 'Command:' + lib.app.colourClear + ' ' + cmd + '\n')
    sys.stdout.flush()

  if (os.system(cmd)):
    if exitOnError:
      errorMessage('Command failed: ' + cmd)
    else:
      warnMessage('Command failed: ' + cmd)
Example #3
0
def runCommand(cmd, exitOnError=True):

    import lib.app, os, sys
    from lib.errorMessage import errorMessage
    from lib.warnMessage import warnMessage
    global mrtrix_bin_list

    if not mrtrix_bin_list:
        mrtrix_bin_path = os.path.join(
            os.path.abspath(os.path.dirname(os.path.realpath(sys.argv[0]))),
            os.pardir, 'bin')
        mrtrix_bin_list = [
            f for f in os.listdir(mrtrix_bin_path) if not "__" in f
        ]

    if lib.app.lastFile:
        # Check to see if the last file produced is produced by this command;
        #   if it is, this will be the last called command that gets skipped
        if lib.app.lastFile in cmd:
            lib.app.lastFile = ''
        if lib.app.verbosity:
            sys.stdout.write('Skipping command: ' + cmd + '\n')
            sys.stdout.flush()
        return

    binary_name = cmd.split()[0]

    # Automatically add the relevant flags to any mrtrix command calls, including filling them in around the pipes
    if binary_name in mrtrix_bin_list:
        cmdsplit = cmd.split()
        for index, item in enumerate(cmdsplit):
            if item == '|':
                if lib.app.mrtrixNThreads:
                    cmdsplit[index] = lib.app.mrtrixNThreads + ' |'
                if lib.app.mrtrixQuiet:
                    cmdsplit[
                        index] = lib.app.mrtrixQuiet + ' ' + cmdsplit[index]
        cmdsplit.append(lib.app.mrtrixNThreads)
        cmdsplit.append(lib.app.mrtrixQuiet)
        cmd = ' '.join(cmdsplit)

    if lib.app.verbosity:
        sys.stdout.write(lib.app.colourConsole + 'Command: ' +
                         lib.app.colourClear + cmd + '\n')
        sys.stdout.flush()

    if (os.system(cmd)):
        if exitOnError:
            errorMessage('Command failed: ' + cmd)
        else:
            warnMessage('Command failed: ' + cmd)
Example #4
0
def checkOutputFile(path):
  import os
  from lib.errorMessage import errorMessage
  from lib.warnMessage  import warnMessage
  global args, mrtrixForce
  if not path:
    return
  if os.path.exists(path):
    if args.force:
      warnMessage('Output file ' + os.path.basename(path) + ' already exists; will be overwritten at script completion')
      mrtrixForce = ' -force'
    else:
      errorMessage('Output file ' + path + ' already exists (use -force to override)')
      sys.exit(1)
Example #5
0
def getInputFiles():
    import os
    import lib.app
    from lib.runCommand import runCommand
    from lib.warnMessage import warnMessage
    mask_path = os.path.join(lib.app.tempDir, 'mask.mif')
    if os.path.exists(mask_path):
        warnMessage('-mask option is ignored by algorithm \'manual\'')
        os.remove(mask_path)
    runCommand('mrconvert ' + lib.app.args.in_voxels + ' ' +
               os.path.join(lib.app.tempDir, 'in_voxels.mif'))
    if lib.app.args.dirs:
        runCommand('mrconvert ' + lib.app.args.dirs + ' ' +
                   os.path.join(lib.app.tempDir, 'dirs.mif') +
                   ' -stride 0,0,0,1')
Example #6
0
def getFSLSuffix():
  import os, sys
  from lib.warnMessage import warnMessage
  fsl_output_type = os.environ.get('FSLOUTPUTTYPE', '')
  if fsl_output_type == 'NIFTI':
    return '.nii'
  if fsl_output_type == 'NIFTI_GZ':
    return '.nii.gz'
  if fsl_output_type == 'NIFTI_PAIR':
    return '.img'
  if fsl_output_type == 'NIFTI_PAIR_GZ':
    sys.stderr.write('MRtrix does not support compressed NIFTI pairs; please set FSLOUTPUTTYPE to something else\n')
    exit(1)
  warnMessage('Environment variable FSLOUTPUTTYPE not set; FSL commands may fail\n')
  return '.nii.gz'
Example #7
0
def checkOutputFile(path):
    import os
    from lib.errorMessage import errorMessage
    from lib.warnMessage import warnMessage
    global args, mrtrixForce
    if not path:
        return
    if os.path.exists(path):
        if args.force:
            warnMessage(
                'Output file ' + os.path.basename(path) +
                ' already exists; will be overwritten at script completion')
            mrtrixForce = ' -force'
        else:
            errorMessage('Output file ' + os.path.basename(path) +
                         ' already exists (use -force to override)')
            sys.exit(1)
Example #8
0
def getFSLEddyPath(cuda):
  import os
  from lib.binaryInPath import binaryInPath
  from lib.errorMessage import errorMessage
  from lib.warnMessage  import warnMessage
  if cuda:
    if binaryInPath('eddy_cuda'):
      return 'eddy_cuda'
    else:
      warnMessage('CUDA version of eddy not found; running standard version')
  if binaryInPath('eddy_openmp'):
    return 'eddy_openmp'
  if binaryInPath('eddy'):
    return 'eddy'
  if binaryInPath('fsl5.0-eddy'):
    return 'fsl5.0-eddy'
  errorMessage('Could not find FSL program eddy; please verify FSL install')
Example #9
0
def getFSLEddyPath(cuda):
    import os
    from lib.binaryInPath import binaryInPath
    from lib.errorMessage import errorMessage
    from lib.warnMessage import warnMessage
    if cuda:
        if binaryInPath('eddy_cuda'):
            return 'eddy_cuda'
        else:
            warnMessage(
                'CUDA version of eddy not found; running standard version')
    if binaryInPath('eddy_openmp'):
        return 'eddy_openmp'
    if binaryInPath('eddy'):
        return 'eddy'
    if binaryInPath('fsl5.0-eddy'):
        return 'fsl5.0-eddy'
    errorMessage('Could not find FSL program eddy; please verify FSL install')
Example #10
0
def execute():
  import math, os, shutil
  import lib.app
  from lib.getHeaderInfo import getHeaderInfo
  from lib.getImageStat  import getImageStat
  from lib.getUserPath   import getUserPath
  from lib.printMessage  import printMessage
  from lib.runCommand    import runCommand
  from lib.warnMessage   import warnMessage
  from lib.errorMessage  import errorMessage
  
  # Ideally want to use the oversampling-based regridding of the 5TT image from the SIFT model, not mrtransform
  # May need to commit 5ttregrid...

  # Verify input 5tt image
  sizes = [ int(x) for x in getHeaderInfo('5tt.mif', 'size').split() ]
  datatype = getHeaderInfo('5tt.mif', 'datatype')
  if not len(sizes) == 4 or not sizes[3] == 5 or not datatype.startswith('Float'):
    errorMessage('Imported anatomical image ' + os.path.basename(lib.app.args.in_5tt) + ' is not in the 5TT format')

  # Get shell information
  shells = [ int(round(float(x))) for x in getHeaderInfo('dwi.mif', 'shells').split() ]
  if len(shells) < 3:
    warnMessage('Less than three b-value shells; response functions will not be applicable in MSMT-CSD algorithm')

  # Get lmax information (if provided)
  wm_lmax = [ ]
  if lib.app.args.lmax:
    wm_lmax = [ int(x.strip()) for x in lib.app.args.lmax.split(',') ]
    if not len(wm_lmax) == len(shells):
      errorMessage('Number of manually-defined lmax\'s (' + str(len(wm_lmax)) + ') does not match number of b-value shells (' + str(len(shells)) + ')')
    for l in wm_lmax:
      if l%2:
        errorMessage('Values for lmax must be even')
      if l<0:
        errorMessage('Values for lmax must be non-negative')

  runCommand('dwi2tensor dwi.mif - -mask mask.mif | tensor2metric - -fa fa.mif -vector vector.mif')
  if not os.path.exists('dirs.mif'):
    shutil.copy('vector.mif', 'dirs.mif')
  runCommand('mrtransform 5tt.mif 5tt_regrid.mif -template fa.mif -interp linear')

  # Basic tissue masks
  runCommand('mrconvert 5tt_regrid.mif - -coord 3 2 -axes 0,1,2 | mrcalc - ' + str(lib.app.args.pvf) + ' -gt mask.mif -mult wm_mask.mif')
  runCommand('mrconvert 5tt_regrid.mif - -coord 3 0 -axes 0,1,2 | mrcalc - ' + str(lib.app.args.pvf) + ' -gt fa.mif ' + str(lib.app.args.fa) + ' -lt -mult mask.mif -mult gm_mask.mif')
  runCommand('mrconvert 5tt_regrid.mif - -coord 3 3 -axes 0,1,2 | mrcalc - ' + str(lib.app.args.pvf) + ' -gt fa.mif ' + str(lib.app.args.fa) + ' -lt -mult mask.mif -mult csf_mask.mif')

  # Revise WM mask to only include single-fibre voxels
  printMessage('Calling dwi2response recursively to select WM single-fibre voxels using \'' + lib.app.args.wm_algo + '\' algorithm')
  recursive_cleanup_option=''
  if not lib.app.cleanup:
    recursive_cleanup_option = ' -nocleanup'
  runCommand('dwi2response -quiet -tempdir ' + lib.app.tempDir + recursive_cleanup_option + ' ' + lib.app.args.wm_algo + ' dwi.mif wm_ss_response.txt -mask wm_mask.mif -voxels wm_sf_mask.mif')

  # Check for empty masks
  wm_voxels  = int(getImageStat('wm_sf_mask.mif', 'count', 'wm_sf_mask.mif'))
  gm_voxels  = int(getImageStat('gm_mask.mif',    'count', 'gm_mask.mif'))
  csf_voxels = int(getImageStat('csf_mask.mif',   'count', 'csf_mask.mif'))
  empty_masks = [ ]
  if not wm_voxels:
    empty_masks.append('WM')
  if not gm_voxels:
    empty_masks.append('GM')
  if not csf_voxels:
    empty_masks.append('CSF')
  if empty_masks:
    message = ','.join(empty_masks)
    message += ' tissue mask'
    if len(empty_masks) > 1:
      message += 's'
    message += ' empty; cannot estimate response function'
    if len(empty_masks) > 1:
      message += 's'
    errorMessage(message)

  # For each of the three tissues, generate a multi-shell response
  # Since here we're guaranteeing that GM and CSF will be isotropic in all shells, let's use mrstats rather than sh2response (seems a bit weird passing a directions file to sh2response with lmax=0...)

  wm_responses  = [ ]
  gm_responses  = [ ]
  csf_responses = [ ]
  max_length = 0

  for index, b in enumerate(shells):
    dwi_path = 'dwi_b' + str(b) + '.mif'
    # dwiextract will yield a 4D image, even if there's only a single volume in a shell
    runCommand('dwiextract dwi.mif -shell ' + str(b) + ' ' + dwi_path)
    this_b_lmax_option = ''
    if wm_lmax:
      this_b_lmax_option = ' -lmax ' + str(wm_lmax[index])
    runCommand('amp2sh ' + dwi_path + ' - | sh2response - wm_sf_mask.mif dirs.mif wm_response_b' + str(b) + '.txt' + this_b_lmax_option)
    wm_response = open('wm_response_b' + str(b) + '.txt', 'r').read().split()
    wm_responses.append(wm_response)
    max_length = max(max_length, len(wm_response))
    mean_dwi_path = 'dwi_b' + str(b) + '_mean.mif'
    runCommand('mrmath ' + dwi_path + ' mean ' + mean_dwi_path + ' -axis 3')
    gm_mean  = float(getImageStat(mean_dwi_path, 'mean', 'gm_mask.mif'))
    csf_mean = float(getImageStat(mean_dwi_path, 'mean', 'csf_mask.mif'))
    gm_responses .append( str(gm_mean  * math.sqrt(4.0 * math.pi)) )
    csf_responses.append( str(csf_mean * math.sqrt(4.0 * math.pi)) )

  with open('wm.txt', 'w') as f:
    for line in wm_responses:
      line += ['0'] * (max_length - len(line))
      f.write(' '.join(line) + '\n')
  with open('gm.txt', 'w') as f:
    for line in gm_responses:
      f.write(line + '\n')
  with open('csf.txt', 'w') as f:
    for line in csf_responses:
      f.write(line + '\n')

  shutil.copyfile('wm.txt',  getUserPath(lib.app.args.out_wm,  False))
  shutil.copyfile('gm.txt',  getUserPath(lib.app.args.out_gm,  False))
  shutil.copyfile('csf.txt', getUserPath(lib.app.args.out_csf, False))

  # Generate output 4D binary image with voxel selections; RGB as in MSMT-CSD paper
  runCommand('mrcat csf_mask.mif gm_mask.mif wm_sf_mask.mif voxels.mif -axis 3')
Example #11
0
def runCommand(cmd, exitOnError=True):

  import lib.app, os, subprocess, sys
  from lib.errorMessage import errorMessage
  from lib.isWindows    import isWindows
  from lib.printMessage import printMessage
  from lib.warnMessage  import warnMessage
  import distutils
  from distutils.spawn import find_executable
  global mrtrix_bin_list
  global mrtrix_bin_path

  if not mrtrix_bin_list:
    mrtrix_bin_path = os.path.abspath(os.path.join(os.path.abspath(os.path.dirname(os.path.realpath(__file__))), os.pardir, os.pardir, 'release', 'bin'));
    # On Windows, strip the .exe's
    mrtrix_bin_list = [ os.path.splitext(name)[0] for name in os.listdir(mrtrix_bin_path) ]

  if lib.app.lastFile:
    # Check to see if the last file produced is produced by this command;
    #   if it is, this will be the last called command that gets skipped
    if lib.app.lastFile in cmd:
      lib.app.lastFile = ''
    if lib.app.verbosity:
      sys.stdout.write('Skipping command: ' + cmd + '\n')
      sys.stdout.flush()
    return

  # Vectorise the command string, preserving anything encased within quotation marks
  # This will eventually allow the use of subprocess rather than os.system()
  # TODO Use shlex.split()?
  quotation_split = cmd.split('\"')
  if not len(quotation_split)%2:
    errorMessage('Malformed command \"' + cmd + '\": odd number of quotation marks')
  cmdsplit = [ ]
  if len(quotation_split) == 1:
    cmdsplit = cmd.split()
  else:
    for index, item in enumerate(quotation_split):
      if index%2:
        cmdsplit.append(item)
      else:
        cmdsplit.extend(item.split())

  # For any MRtrix commands, need to insert the nthreads and quiet calls
  new_cmdsplit = [ ]
  is_mrtrix_binary = False
  next_is_binary = True
  for item in cmdsplit:
    if next_is_binary:
      is_mrtrix_binary = item in mrtrix_bin_list
      # Make sure we're not accidentally running an MRtrix command from a different installation to the script
      if is_mrtrix_binary:
        binary_sys = find_executable(item)
        binary_manual = os.path.join(mrtrix_bin_path, item)
        if (isWindows()):
          binary_manual = binary_manual + '.exe'
        use_manual_binary_path = not binary_sys
        if not use_manual_binary_path:
          # os.path.samefile() not supported on all platforms / Python versions
          if hasattr(os.path, 'samefile'):
            use_manual_binary_path = not os.path.samefile(binary_sys, binary_manual)
          else:
            # Hack equivalent of samefile(); not perfect, but should be adequate for use here
            use_manual_binary_path = not os.path.normcase(os.path.normpath(binary_sys)) == os.path.normcase(os.path.normpath(binary_manual))
        if use_manual_binary_path:
          item = binary_manual
      next_is_binary = False
    if item == '|':
      if is_mrtrix_binary:
        if lib.app.mrtrixNThreads:
          new_cmdsplit.extend(lib.app.mrtrixNThreads.strip().split())
        if lib.app.mrtrixQuiet:
          new_cmdsplit.append(lib.app.mrtrixQuiet.strip())
      next_is_binary = True
    new_cmdsplit.append(item)
  if is_mrtrix_binary:
    if lib.app.mrtrixNThreads:
      new_cmdsplit.extend(lib.app.mrtrixNThreads.strip().split())
    if lib.app.mrtrixQuiet:
      new_cmdsplit.append(lib.app.mrtrixQuiet.strip())
  cmdsplit = new_cmdsplit

  # If the piping symbol appears anywhere, we need to split this into multiple commands and execute them separately
  # If no piping symbols, the entire command should just appear as a single row in cmdstack
  cmdstack = [ ]
  prev = 0
  for index, item in enumerate(cmdsplit):
    if item == '|':
      cmdstack.append(cmdsplit[prev:index])
      prev = index + 1
  cmdstack.append(cmdsplit[prev:])

  if lib.app.verbosity:
    sys.stdout.write(lib.app.colourConsole + 'Command:' + lib.app.colourClear + ' ' + cmd + '\n')
    sys.stdout.flush()

  error = False
  error_text = ''
  # TODO If script is running in verbose mode, ideally want to duplicate stderr output in the terminal
  if len(cmdstack) == 1:
    process = subprocess.Popen(cmdstack[0], stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    (stdoutdata, stderrdata) = process.communicate()
    if process.returncode:
      error = True
      error_text = stdoutdata.decode('utf-8') + stderrdata.decode('utf-8')
  else:
    processes = [ ]
    for index, command in enumerate(cmdstack):
      if index > 0:
        proc_in = processes[index-1].stdout
      else:
        proc_in = None
      process = subprocess.Popen (command, stdin=proc_in, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
      processes.append(process)

    # Wait for all commands to complete
    for index, process in enumerate(processes):
      if index < len(cmdstack)-1:
        # Only capture the output if the command failed; otherwise, let it pipe to the next command
        process.wait()
        if process.returncode:
          error = True
          (stdoutdata, stderrdata) = process.communicate()
          error_text = error_text + stdoutdata.decode('utf-8') + stderrdata.decode('utf-8')
      else:
        (stdoutdata, stderrdata) = process.communicate()
        if process.returncode:
          error = True
          error_text = error_text + stdoutdata.decode('utf-8') + stderrdata.decode('utf-8')


  if (error):
    if exitOnError:
      printMessage('')
      sys.stderr.write(os.path.basename(sys.argv[0]) + ': ' + lib.app.colourError + '[ERROR] Command failed: ' + cmd + lib.app.colourClear + '\n')
      sys.stderr.write(os.path.basename(sys.argv[0]) + ': ' + lib.app.colourPrint + 'Output of failed command:' + lib.app.colourClear + '\n')
      sys.stderr.write(error_text)
      if not lib.app.cleanup and lib.app.tempDir:
        with open(os.path.join(lib.app.tempDir, 'error.txt'), 'w') as outfile:
          outfile.write(cmd + '\n\n' + error_text + '\n')
      lib.app.complete()
      exit(1)
    else:
      warnMessage('Command failed: ' + cmd)

  # Only now do we append to the script log, since the command has completed successfully
  # Note: Writing the command as it was formed as the input to runCommand():
  #   other flags may potentially change if this file is eventually used to resume the script
  if lib.app.tempDir:
    with open(os.path.join(lib.app.tempDir, 'log.txt'), 'a') as outfile:
      outfile.write(cmd + '\n')
Example #12
0
def execute():
  import os
  import lib.app
  from lib.binaryInPath  import binaryInPath
  from lib.errorMessage  import errorMessage
  from lib.getFSLSuffix  import getFSLSuffix
  from lib.getHeaderInfo import getHeaderInfo
  from lib.imagesMatch   import imagesMatch
  from lib.isWindows     import isWindows
  from lib.runCommand    import runCommand
  from lib.warnMessage   import warnMessage
  
  if isWindows():
    errorMessage('\'fsl\' algorithm of 5ttgen script cannot be run on Windows: FSL not available on Windows')

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

  ssroi_cmd = 'standard_space_roi'
  if not binaryInPath(ssroi_cmd):
    ssroi_cmd = 'fsl5.0-standard_space_roi'
    if not binaryInPath(ssroi_cmd):
      errorMessage('Could not find FSL program standard_space_roi; please verify FSL install')

  bet_cmd = 'bet'
  if not binaryInPath(bet_cmd):
    bet_cmd = 'fsl5.0-bet'
    if not binaryInPath(bet_cmd):
      errorMessage('Could not find FSL program bet; please verify FSL install')

  fast_cmd = 'fast'
  if not binaryInPath(fast_cmd):
    fast_cmd = 'fsl5.0-fast'
    if not binaryInPath(fast_cmd):
      errorMessage('Could not find FSL program fast; please verify FSL install')

  first_cmd = 'run_first_all'
  if not binaryInPath(first_cmd):
    first_cmd = "fsl5.0-run_first_all"
    if not binaryInPath(first_cmd):
      errorMessage('Could not find FSL program run_first_all; please verify FSL install')

  first_atlas_path = os.path.join(fsl_path, 'data', 'first', 'models_336_bin')

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

  fsl_suffix = getFSLSuffix()

  sgm_structures = [ 'L_Accu', 'R_Accu', 'L_Caud', 'R_Caud', 'L_Pall', 'R_Pall', 'L_Puta', 'R_Puta', 'L_Thal', 'R_Thal' ]
  if lib.app.args.sgm_amyg_hipp:
    sgm_structures.extend([ 'L_Amyg', 'R_Amyg', 'L_Hipp', 'R_Hipp' ])
  
  runCommand('mrconvert input.mif T1.nii -stride -1,+2,+3')

  fast_t1_input = 'T1.nii'
  fast_t2_input = ''

  # Decide whether or not we're going to do any brain masking
  if os.path.exists('mask.mif'):

    fast_t1_input = 'T1_masked' + fsl_suffix
    
    # Check to see if the mask matches the T1 image
    if imagesMatch('T1.nii', 'mask.mif'):
      runCommand('mrcalc T1.nii mask.mif -mult ' + fast_t1_input)
      mask_path = 'mask.mif'
    else:
      warnMessage('Mask image does not match input image - re-gridding')
      runCommand('mrtransform mask.mif mask_regrid.mif -template T1.nii')
      runCommand('mrcalc T1.nii mask_regrid.mif ' + fast_t1_input)
      mask_path = 'mask_regrid.mif'

    if os.path.exists('T2.nii'):
      fast_t2_input = 'T2_masked' + fsl_suffix
      runCommand('mrcalc T2.nii ' + mask_path + ' -mult ' + fast_t2_input)
      
  elif lib.app.args.premasked:
  
    fast_t1_input = 'T1.nii'
    if os.path.exists('T2.nii'):
      fast_t2_input = 'T2.nii'
    
  else:

    # Use FSL command standard_space_roi to do an initial masking of the image before BET
    # Also reduce the FoV of the image
    # Using MNI 1mm dilated brain mask rather than the -b option in standard_space_roi (which uses the 2mm mask); the latter looks 'buggy' to me... Unfortunately even with the 1mm 'dilated' mask, it can still cut into some brain areas, hence the explicit dilation
    mni_mask_path = os.path.join(fsl_path, 'data', 'standard', 'MNI152_T1_1mm_brain_mask_dil.nii.gz')
    mni_mask_dilation = 0;
    if os.path.exists (mni_mask_path):
      mni_mask_dilation = 4;
    else:
      mni_mask_path = os.path.join(fsl_path, 'data', 'standard', 'MNI152_T1_2mm_brain_mask_dil.nii.gz')
      if os.path.exists (mni_mask_path):
        mni_mask_dilation = 2;

    # standard_space_roi can sometimes crash; if this happens, try to allow the script to continue
    if mni_mask_dilation:
      runCommand('maskfilter ' + mni_mask_path + ' dilate mni_mask.nii -npass ' + str(mni_mask_dilation))
      if lib.app.args.nocrop:
        ssroi_roi_option = ' -roiNONE'
      else:
        ssroi_roi_option = ' -roiFOV'
      runCommand(ssroi_cmd + ' T1.nii T1_preBET' + fsl_suffix + ' -maskMASK mni_mask.nii' + ssroi_roi_option, False)
    else:
      runCommand(ssroi_cmd + ' T1.nii T1_preBET' + fsl_suffix + ' -b', False)

    if not os.path.exists('T1_preBET' + fsl_suffix):
      warnMessage('FSL command ' + ssroi_cmd + ' appears to have failed; passing T1 directly to BET')
      runCommand('mrconvert input.mif T1_preBET' + fsl_suffix + ' -stride -1,+2,+3')

    # BET
    fast_t1_input = 'T1_BET' + fsl_suffix
    runCommand(bet_cmd + ' T1_preBET' + fsl_suffix + ' ' + fast_t1_input + ' -f 0.15 -R')

    if os.path.exists('T2.nii'):
      if lib.app.args.nocrop:
        fast_t2_input = 'T2.nii'
      else:
        # Just a reduction of FoV, no sub-voxel interpolation going on
        runCommand('mrtransform T2.nii T2_cropped.nii -template ' + fast_t1_input + ' -interp nearest')
        fast_t2_input = 'T2_cropped.nii'

  # Finish branching based on brain masking

  # FAST
  if fast_t2_input:
    runCommand(fast_cmd + ' -S 2 ' + fast_t2_input + ' ' + fast_t1_input)
  else:
    runCommand(fast_cmd + ' ' + fast_t1_input)
  fast_output_prefix = fast_t1_input.split('.')[0]

  # FIRST
  first_input_is_brain_extracted = ''
  if lib.app.args.premasked:
    first_input_is_brain_extracted = ' -b'
  runCommand(first_cmd + ' -s ' + ','.join(sgm_structures) + ' -i T1.nii -o first' + first_input_is_brain_extracted)

  # Convert FIRST meshes to partial volume images
  pve_image_list = [ ]
  for struct in sgm_structures:
    pve_image_path = 'mesh2pve_' + struct + '.mif'
    vtk_in_path = 'first-' + struct + '_first.vtk'
    vtk_temp_path = struct + '.vtk'
    if not os.path.exists(vtk_in_path):
      errorMessage('Missing .vtk file for structure ' + struct + '; run_first_all must have failed')
    runCommand('meshconvert ' + vtk_in_path + ' ' + vtk_temp_path + ' -transform_first2real T1.nii')
    runCommand('mesh2pve ' + vtk_temp_path + ' ' + fast_t1_input + ' ' + pve_image_path)
    pve_image_list.append(pve_image_path)
  pve_cat = ' '.join(pve_image_list)
  runCommand('mrmath ' + pve_cat + ' sum - | mrcalc - 1.0 -min all_sgms.mif')

  # Looks like FAST in 5.0 ignores FSLOUTPUTTYPE when writing the PVE images
  # Will have to wait and see whether this changes, and update the script accordingly
  if fast_cmd == 'fast':
    fast_suffix = fsl_suffix
  else:
    fast_suffix = '.nii.gz'

  # Combine the tissue images into the 5TT format within the script itself
  # Step 1: Run LCC on the WM image
  runCommand('mrthreshold ' + fast_output_prefix + '_pve_2' + fast_suffix + ' - -abs 0.001 | maskfilter - connect wm_mask.mif -largest')
  # Step 2: Generate the images in the same fashion as the 5ttgen command
  runCommand('mrconvert ' + fast_output_prefix + '_pve_0' + fast_suffix + ' csf.mif')
  runCommand('mrcalc 1.0 csf.mif -sub all_sgms.mif -min sgm.mif')
  runCommand('mrcalc 1.0 csf.mif sgm.mif -add -sub ' + fast_output_prefix + '_pve_1' + fast_suffix + ' ' + fast_output_prefix + '_pve_2' + fast_suffix + ' -add -div multiplier.mif')
  runCommand('mrcalc multiplier.mif -finite multiplier.mif 0.0 -if multiplier_noNAN.mif')
  runCommand('mrcalc ' + fast_output_prefix + '_pve_1' + fast_suffix + ' multiplier_noNAN.mif -mult cgm.mif')
  runCommand('mrcalc ' + fast_output_prefix + '_pve_2' + fast_suffix + ' multiplier_noNAN.mif -mult wm_mask.mif -mult wm.mif')
  runCommand('mrcalc 0 wm.mif -min path.mif')
  runCommand('mrcat cgm.mif sgm.mif wm.mif csf.mif path.mif - -axis 3 | mrconvert - combined_precrop.mif -stride +2,+3,+4,+1')

  # Use mrcrop to reduce file size (improves caching of image data during tracking)
  if lib.app.args.nocrop:
    runCommand('mrconvert combined_precrop.mif result.mif')
  else:
    runCommand('mrmath combined_precrop.mif sum - -axis 3 | mrthreshold - - -abs 0.5 | mrcrop combined_precrop.mif result.mif -mask -')
Example #13
0
def execute():
  import os
  import lib.app
  from lib.binaryInPath  import binaryInPath
  from lib.errorMessage  import errorMessage
  from lib.getFSLSuffix  import getFSLSuffix
  from lib.getHeaderInfo import getHeaderInfo
  from lib.isWindows     import isWindows
  from lib.runCommand    import runCommand
  from lib.warnMessage   import warnMessage
  
  if isWindows():
    errorMessage('Script cannot run in FSL mode on Windows')

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

  ssroi_cmd = 'standard_space_roi'
  if not binaryInPath(ssroi_cmd):
    ssroi_cmd = 'fsl5.0-standard_space_roi'
    if not binaryInPath(ssroi_cmd):
      errorMessage('Could not find FSL program standard_space_roi; please verify FSL install')

  bet_cmd = 'bet'
  if not binaryInPath(bet_cmd):
    bet_cmd = 'fsl5.0-bet'
    if not binaryInPath(bet_cmd):
      errorMessage('Could not find FSL program bet; please verify FSL install')

  fast_cmd = 'fast'
  if not binaryInPath(fast_cmd):
    fast_cmd = 'fsl5.0-fast'
    if not binaryInPath(fast_cmd):
      errorMessage('Could not find FSL program fast; please verify FSL install')

  first_cmd = 'run_first_all'
  if not binaryInPath(first_cmd):
    first_cmd = "fsl5.0-run_first_all"
    if not binaryInPath(first_cmd):
      errorMessage('Could not find FSL program run_first_all; please verify FSL install')

  first_atlas_path = os.path.join(fsl_path, 'data', 'first', 'models_336_bin')

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

  fsl_suffix = getFSLSuffix()

  sgm_structures = [ 'L_Accu', 'R_Accu', 'L_Caud', 'R_Caud', 'L_Pall', 'R_Pall', 'L_Puta', 'R_Puta', 'L_Thal', 'R_Thal' ]
  
  runCommand('mrconvert input.mif T1.nii')  

  # Decide whether or not we're going to do any brain masking
  if os.path.exists('mask.mif'):
    
    # Check to see if the dimensions match the T1 image
    T1_size = getHeaderInfo('input.mif', 'size')
    mask_size = getHeaderInfo('mask.mif', 'size')
    if mask_size == T1_size:
      runCommand('mrcalc input.mif mask.mif -mult T1_masked.nii')
    else:
      runCommand('mrtransform mask.mif mask_regrid.mif -template input.mif')
      runCommand('mrcalc input.mif mask_regrid.mif -mult T1_masked.nii')
      
  elif lib.app.args.premasked:
  
    runCommand('mrconvert input.mif T1_masked.nii')
    
  else:

    # Use FSL command standard_space_roi to do an initial masking of the image before BET
    # Also reduce the FoV of the image
    # Using MNI 1mm dilated brain mask rather than the -b option in standard_space_roi (which uses the 2mm mask); the latter looks 'buggy' to me... Unfortunately even with the 1mm 'dilated' mask, it can still cut into some brain areas, hence the explicit dilation
    mni_mask_path = os.path.join(fsl_path, 'data', 'standard', 'MNI152_T1_1mm_brain_mask_dil.nii.gz')
    mni_mask_dilation = 0;
    if os.path.exists (mni_mask_path):
      mni_mask_dilation = 4;
    else:
      mni_mask_path = os.path.join(fsl_path, 'data', 'standard', 'MNI152_T1_2mm_brain_mask_dil.nii.gz')
      if os.path.exists (mni_mask_path):
        mni_mask_dilation = 2;

    # standard_space_roi can sometimes crash; if this happens, try to allow the script to continue
    if mni_mask_dilation:
      runCommand('maskfilter ' + mni_mask_path + ' dilate mni_mask.nii -npass ' + str(mni_mask_dilation))
      if lib.app.args.nocrop:
        ssroi_roi_option = ' -roiNONE'
      else:
        ssroi_roi_option = ' -roiFOV'
      runCommand(ssroi_cmd + ' T1.nii T1_preBET' + fsl_suffix + ' -maskMASK mni_mask.nii' + ssroi_roi_option, False)
    else:
      runCommand(ssroi_cmd + ' T1.nii T1_preBET' + fsl_suffix + ' -b', False)

    if not os.path.isfile('T1_preBET' + fsl_suffix):
      warnMessage('FSL command ' + ssroi_cmd + ' appears to have failed; passing T1 directly to BET')
      runCommand('mrconvert input.mif T1_preBET' + fsl_suffix)

    # BET
    runCommand(bet_cmd + ' T1_preBET' + fsl_suffix + ' T1_masked' + fsl_suffix + ' -f 0.15 -R')

  # Finish branching based on brain masking  

  # FAST
  runCommand(fast_cmd + ' T1_masked' + fsl_suffix)

  # FIRST
  first_input_is_brain_extracted = ''
  if lib.app.args.premasked:
    first_input_is_brain_extracted = ' -b'
  runCommand(first_cmd + ' -s ' + ','.join(sgm_structures) + ' -i T1.nii -o first' + first_input_is_brain_extracted)

  # Convert FIRST meshes to partial volume images
  pve_image_list = [ ]
  for struct in sgm_structures:
    pve_image_path = 'mesh2pve_' + struct + '.nii'
    vtk_in_path = 'first-' + struct + '_first.vtk'
    vtk_temp_path = struct + '.vtk'
    if not os.path.exists(vtk_in_path):
      errorMessage('Missing .vtk file for structure ' + struct + '; run_first_all must have failed')
    runCommand('meshconvert ' + vtk_in_path + ' ' + vtk_temp_path + ' -transform_first2real input.mif')
    runCommand('mesh2pve ' + vtk_temp_path + ' T1_preBET' + fsl_suffix + ' ' + pve_image_path)
    pve_image_list.append(pve_image_path)
  pve_cat = ' '.join(pve_image_list)
  runCommand('mrmath ' + pve_cat + ' sum - | mrcalc - 1.0 -min all_sgms.nii')

  # Looks like FAST in 5.0 ignores FSLOUTPUTTYPE when writing the PVE images
  # Will have to wait and see whether this changes, and update the script accordingly
  if fast_cmd == 'fast':
    fast_suffix = fsl_suffix
  else:
    fast_suffix = '.nii.gz'

  # Combine the tissue images into the 5TT format within the script itself
  # Step 1: Run LCC on the WM image
  runCommand('mrthreshold T1_masked_pve_2' + fast_suffix + ' - -abs 0.001 | maskfilter - connect wm_mask.mif -largest')
  # Step 2: Generate the images in the same fashion as the 5ttgen command
  runCommand('mrconvert T1_masked_pve_0' + fast_suffix + ' csf.mif')
  runCommand('mrcalc 1 csf.mif -sub all_sgms.nii -min sgm.mif')
  runCommand('mrcalc 1.0 csf.mif sgm.mif -add -sub T1_masked_pve_1' + fast_suffix + ' T1_masked_pve_2' + fast_suffix + ' -add -div multiplier.mif')
  runCommand('mrcalc multiplier.mif -finite multiplier.mif 0.0 -if multiplier_noNAN.mif')
  runCommand('mrcalc T1_masked_pve_1' + fast_suffix + ' multiplier_noNAN.mif -mult cgm.mif')
  runCommand('mrcalc T1_masked_pve_2' + fast_suffix + ' multiplier_noNAN.mif -mult wm_mask.mif -mult wm.mif')
  runCommand('mrcalc 0 wm.mif -min path.mif')
  runCommand('mrcat cgm.mif sgm.mif wm.mif csf.mif path.mif - -axis 3 | mrconvert - combined_precrop.mif -stride +2,+3,+4,+1')

  # Use mrcrop to reduce file size (improves caching of image data during tracking)
  if lib.app.args.nocrop:
    runCommand('mrconvert combined_precrop.mif result.mif')
  else:
    runCommand('mrmath combined_precrop.mif sum - -axis 3 | mrthreshold - - -abs 0.5 | mrcrop combined_precrop.mif result.mif -mask -')
Example #14
0
def execute():
    import math, os, shutil
    import lib.app
    from lib.getHeaderInfo import getHeaderInfo
    from lib.getImageStat import getImageStat
    from lib.getUserPath import getUserPath
    from lib.printMessage import printMessage
    from lib.runCommand import runCommand
    from lib.warnMessage import warnMessage
    from lib.errorMessage import errorMessage

    # Ideally want to use the oversampling-based regridding of the 5TT image from the SIFT model, not mrtransform
    # May need to commit 5ttregrid...

    # Verify input 5tt image
    sizes = [int(x) for x in getHeaderInfo('5tt.mif', 'size').split()]
    datatype = getHeaderInfo('5tt.mif', 'datatype')
    if not len(sizes) == 4 or not sizes[3] == 5 or not datatype.startswith(
            'Float'):
        errorMessage('Imported anatomical image ' +
                     os.path.basename(lib.app.args.in_5tt) +
                     ' is not in the 5TT format')

    # Get shell information
    shells = [
        int(round(float(x)))
        for x in getHeaderInfo('dwi.mif', 'shells').split()
    ]
    if len(shells) < 3:
        warnMessage(
            'Less than three b-value shells; response functions will not be applicable in MSMT-CSD algorithm'
        )

    # Get lmax information (if provided)
    wm_lmax = []
    if lib.app.args.lmax:
        wm_lmax = [int(x.strip()) for x in lib.app.args.lmax.split(',')]
        if not len(wm_lmax) == len(shells):
            errorMessage('Number of manually-defined lmax\'s (' +
                         str(len(wm_lmax)) +
                         ') does not match number of b-value shells (' +
                         str(len(shells)) + ')')
        for l in wm_lmax:
            if l % 2:
                errorMessage('Values for lmax must be even')
            if l < 0:
                errorMessage('Values for lmax must be non-negative')

    runCommand(
        'dwi2tensor dwi.mif - -mask mask.mif | tensor2metric - -fa fa.mif -vector vector.mif'
    )
    if not os.path.exists('dirs.mif'):
        shutil.copy('vector.mif', 'dirs.mif')
    runCommand(
        'mrtransform 5tt.mif 5tt_regrid.mif -template fa.mif -interp linear')

    # Basic tissue masks
    runCommand(
        'mrconvert 5tt_regrid.mif - -coord 3 2 -axes 0,1,2 | mrcalc - ' +
        str(lib.app.args.pvf) + ' -gt mask.mif -mult wm_mask.mif')
    runCommand(
        'mrconvert 5tt_regrid.mif - -coord 3 0 -axes 0,1,2 | mrcalc - ' +
        str(lib.app.args.pvf) + ' -gt fa.mif ' + str(lib.app.args.fa) +
        ' -lt -mult mask.mif -mult gm_mask.mif')
    runCommand(
        'mrconvert 5tt_regrid.mif - -coord 3 3 -axes 0,1,2 | mrcalc - ' +
        str(lib.app.args.pvf) + ' -gt fa.mif ' + str(lib.app.args.fa) +
        ' -lt -mult mask.mif -mult csf_mask.mif')

    # Revise WM mask to only include single-fibre voxels
    printMessage(
        'Calling dwi2response recursively to select WM single-fibre voxels using \''
        + lib.app.args.wm_algo + '\' algorithm')
    recursive_cleanup_option = ''
    if not lib.app.cleanup:
        recursive_cleanup_option = ' -nocleanup'
    runCommand(
        'dwi2response -quiet -tempdir ' + lib.app.tempDir +
        recursive_cleanup_option + ' ' + lib.app.args.wm_algo +
        ' dwi.mif wm_ss_response.txt -mask wm_mask.mif -voxels wm_sf_mask.mif')

    # Check for empty masks
    wm_voxels = int(getImageStat('wm_sf_mask.mif', 'count', 'wm_sf_mask.mif'))
    gm_voxels = int(getImageStat('gm_mask.mif', 'count', 'gm_mask.mif'))
    csf_voxels = int(getImageStat('csf_mask.mif', 'count', 'csf_mask.mif'))
    empty_masks = []
    if not wm_voxels:
        empty_masks.append('WM')
    if not gm_voxels:
        empty_masks.append('GM')
    if not csf_voxels:
        empty_masks.append('CSF')
    if empty_masks:
        message = ','.join(empty_masks)
        message += ' tissue mask'
        if len(empty_masks) > 1:
            message += 's'
        message += ' empty; cannot estimate response function'
        if len(empty_masks) > 1:
            message += 's'
        errorMessage(message)

    # For each of the three tissues, generate a multi-shell response
    # Since here we're guaranteeing that GM and CSF will be isotropic in all shells, let's use mrstats rather than sh2response (seems a bit weird passing a directions file to sh2response with lmax=0...)

    wm_responses = []
    gm_responses = []
    csf_responses = []
    max_length = 0

    for index, b in enumerate(shells):
        dwi_path = 'dwi_b' + str(b) + '.mif'
        # dwiextract will yield a 4D image, even if there's only a single volume in a shell
        runCommand('dwiextract dwi.mif -shell ' + str(b) + ' ' + dwi_path)
        this_b_lmax_option = ''
        if wm_lmax:
            this_b_lmax_option = ' -lmax ' + str(wm_lmax[index])
        runCommand('amp2sh ' + dwi_path +
                   ' - | sh2response - wm_sf_mask.mif dirs.mif wm_response_b' +
                   str(b) + '.txt' + this_b_lmax_option)
        wm_response = open('wm_response_b' + str(b) + '.txt',
                           'r').read().split()
        wm_responses.append(wm_response)
        max_length = max(max_length, len(wm_response))
        mean_dwi_path = 'dwi_b' + str(b) + '_mean.mif'
        runCommand('mrmath ' + dwi_path + ' mean ' + mean_dwi_path +
                   ' -axis 3')
        gm_mean = float(getImageStat(mean_dwi_path, 'mean', 'gm_mask.mif'))
        csf_mean = float(getImageStat(mean_dwi_path, 'mean', 'csf_mask.mif'))
        gm_responses.append(str(gm_mean * math.sqrt(4.0 * math.pi)))
        csf_responses.append(str(csf_mean * math.sqrt(4.0 * math.pi)))

    with open('wm.txt', 'w') as f:
        for line in wm_responses:
            line += ['0'] * (max_length - len(line))
            f.write(' '.join(line) + '\n')
    with open('gm.txt', 'w') as f:
        for line in gm_responses:
            f.write(line + '\n')
    with open('csf.txt', 'w') as f:
        for line in csf_responses:
            f.write(line + '\n')

    shutil.copyfile('wm.txt', getUserPath(lib.app.args.out_wm, False))
    shutil.copyfile('gm.txt', getUserPath(lib.app.args.out_gm, False))
    shutil.copyfile('csf.txt', getUserPath(lib.app.args.out_csf, False))

    # Generate output 4D binary image with voxel selections; RGB as in MSMT-CSD paper
    runCommand(
        'mrcat csf_mask.mif gm_mask.mif wm_sf_mask.mif voxels.mif -axis 3')
Example #15
0
def runCommand(cmd, exitOnError=True):

    import lib.app, os, subprocess, sys
    from lib.errorMessage import errorMessage
    from lib.isWindows import isWindows
    from lib.printMessage import printMessage
    from lib.warnMessage import warnMessage
    import distutils
    from distutils.spawn import find_executable
    global mrtrix_bin_list
    global mrtrix_bin_path

    if not mrtrix_bin_list:
        mrtrix_bin_path = os.path.abspath(
            os.path.join(
                os.path.abspath(os.path.dirname(os.path.realpath(__file__))),
                os.pardir, os.pardir, 'release', 'bin'))
        # On Windows, strip the .exe's
        mrtrix_bin_list = [
            os.path.splitext(name)[0] for name in os.listdir(mrtrix_bin_path)
        ]

    if lib.app.lastFile:
        # Check to see if the last file produced is produced by this command;
        #   if it is, this will be the last called command that gets skipped
        if lib.app.lastFile in cmd:
            lib.app.lastFile = ''
        if lib.app.verbosity:
            sys.stdout.write('Skipping command: ' + cmd + '\n')
            sys.stdout.flush()
        return

    # Vectorise the command string, preserving anything encased within quotation marks
    # This will eventually allow the use of subprocess rather than os.system()
    # TODO Use shlex.split()?
    quotation_split = cmd.split('\"')
    if not len(quotation_split) % 2:
        errorMessage('Malformed command \"' + cmd +
                     '\": odd number of quotation marks')
    cmdsplit = []
    if len(quotation_split) == 1:
        cmdsplit = cmd.split()
    else:
        for index, item in enumerate(quotation_split):
            if index % 2:
                cmdsplit.append(item)
            else:
                cmdsplit.extend(item.split())

    # For any MRtrix commands, need to insert the nthreads and quiet calls
    new_cmdsplit = []
    is_mrtrix_binary = False
    next_is_binary = True
    for item in cmdsplit:
        if next_is_binary:
            is_mrtrix_binary = item in mrtrix_bin_list
            # Make sure we're not accidentally running an MRtrix command from a different installation to the script
            if is_mrtrix_binary:
                binary_sys = find_executable(item)
                binary_manual = os.path.join(mrtrix_bin_path, item)
                if (isWindows()):
                    binary_manual = binary_manual + '.exe'
                use_manual_binary_path = not binary_sys
                if not use_manual_binary_path:
                    # os.path.samefile() not supported on all platforms / Python versions
                    if hasattr(os.path, 'samefile'):
                        use_manual_binary_path = not os.path.samefile(
                            binary_sys, binary_manual)
                    else:
                        # Hack equivalent of samefile(); not perfect, but should be adequate for use here
                        use_manual_binary_path = not os.path.normcase(
                            os.path.normpath(binary_sys)) == os.path.normcase(
                                os.path.normpath(binary_manual))
                if use_manual_binary_path:
                    item = binary_manual
            next_is_binary = False
        if item == '|':
            if is_mrtrix_binary:
                if lib.app.mrtrixNThreads:
                    new_cmdsplit.extend(lib.app.mrtrixNThreads.strip().split())
                if lib.app.mrtrixQuiet:
                    new_cmdsplit.append(lib.app.mrtrixQuiet.strip())
            next_is_binary = True
        new_cmdsplit.append(item)
    if is_mrtrix_binary:
        if lib.app.mrtrixNThreads:
            new_cmdsplit.extend(lib.app.mrtrixNThreads.strip().split())
        if lib.app.mrtrixQuiet:
            new_cmdsplit.append(lib.app.mrtrixQuiet.strip())
    cmdsplit = new_cmdsplit

    # If the piping symbol appears anywhere, we need to split this into multiple commands and execute them separately
    # If no piping symbols, the entire command should just appear as a single row in cmdstack
    cmdstack = []
    prev = 0
    for index, item in enumerate(cmdsplit):
        if item == '|':
            cmdstack.append(cmdsplit[prev:index])
            prev = index + 1
    cmdstack.append(cmdsplit[prev:])

    if lib.app.verbosity:
        sys.stdout.write(lib.app.colourConsole + 'Command:' +
                         lib.app.colourClear + ' ' + cmd + '\n')
        sys.stdout.flush()

    error = False
    error_text = ''
    # TODO If script is running in verbose mode, ideally want to duplicate stderr output in the terminal
    if len(cmdstack) == 1:
        process = subprocess.Popen(cmdstack[0],
                                   stdin=None,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
        (stdoutdata, stderrdata) = process.communicate()
        if process.returncode:
            error = True
            error_text = stdoutdata.decode('utf-8') + stderrdata.decode(
                'utf-8')
    else:
        processes = []
        for index, command in enumerate(cmdstack):
            if index > 0:
                proc_in = processes[index - 1].stdout
            else:
                proc_in = None
            process = subprocess.Popen(command,
                                       stdin=proc_in,
                                       stdout=subprocess.PIPE,
                                       stderr=subprocess.PIPE)
            processes.append(process)

        # Wait for all commands to complete
        for index, process in enumerate(processes):
            if index < len(cmdstack) - 1:
                # Only capture the output if the command failed; otherwise, let it pipe to the next command
                process.wait()
                if process.returncode:
                    error = True
                    (stdoutdata, stderrdata) = process.communicate()
                    error_text = error_text + stdoutdata.decode(
                        'utf-8') + stderrdata.decode('utf-8')
            else:
                (stdoutdata, stderrdata) = process.communicate()
                if process.returncode:
                    error = True
                    error_text = error_text + stdoutdata.decode(
                        'utf-8') + stderrdata.decode('utf-8')

    if (error):
        lib.app.cleanup = False
        if exitOnError:
            printMessage('')
            sys.stderr.write(
                os.path.basename(sys.argv[0]) + ': ' + lib.app.colourError +
                '[ERROR] Command failed: ' + cmd + lib.app.colourClear + '\n')
            sys.stderr.write(
                os.path.basename(sys.argv[0]) + ': ' + lib.app.colourPrint +
                'Output of failed command:' + lib.app.colourClear + '\n')
            sys.stderr.write(error_text)
            if lib.app.tempDir:
                with open(os.path.join(lib.app.tempDir, 'error.txt'),
                          'w') as outfile:
                    outfile.write(cmd + '\n\n' + error_text + '\n')
            lib.app.complete()
            exit(1)
        else:
            warnMessage('Command failed: ' + cmd)

    # Only now do we append to the script log, since the command has completed successfully
    # Note: Writing the command as it was formed as the input to runCommand():
    #   other flags may potentially change if this file is eventually used to resume the script
    if lib.app.tempDir:
        with open(os.path.join(lib.app.tempDir, 'log.txt'), 'a') as outfile:
            outfile.write(cmd + '\n')
Example #16
0
def runCommand(cmd, exitOnError=True):

  import lib.app, os, subprocess, sys
  from lib.errorMessage import errorMessage
  from lib.isWindows    import isWindows
  from lib.warnMessage  import warnMessage
  import distutils
  from distutils.spawn import find_executable
  global mrtrix_bin_list
  global mrtrix_bin_path

  if not mrtrix_bin_list:
    mrtrix_bin_path = os.path.abspath(os.path.join(os.path.abspath(os.path.dirname(os.path.realpath(__file__))), os.pardir, os.pardir, 'release', 'bin'));
    # On Windows, strip the .exe's
    mrtrix_bin_list = [ os.path.splitext(name)[0] for name in os.listdir(mrtrix_bin_path) ]

  if lib.app.lastFile:
    # Check to see if the last file produced is produced by this command;
    #   if it is, this will be the last called command that gets skipped
    if lib.app.lastFile in cmd:
      lib.app.lastFile = ''
    if lib.app.verbosity:
      sys.stdout.write('Skipping command: ' + cmd + '\n')
      sys.stdout.flush()
    return

  # Vectorise the command string, preserving anything encased within quotation marks
  # This will eventually allow the use of subprocess rather than os.system()
  quotation_split = cmd.split('\"')
  if not len(quotation_split)%2:
    errorMessage('Malformed command \"' + cmd + '\": odd number of quotation marks')
  cmdsplit = [ ]
  if len(quotation_split) == 1:
    cmdsplit = cmd.split()
  else:
    for index, item in enumerate(quotation_split):
      if index%2:
        cmdsplit.append(item)
      else:
        cmdsplit.extend(item.split())

  # For any MRtrix commands, need to insert the nthreads and quiet calls
  new_cmdsplit = [ ]
  is_mrtrix_binary = False
  next_is_binary = True
  for item in cmdsplit:
    if next_is_binary:
      is_mrtrix_binary = item in mrtrix_bin_list
      # Make sure we're not accidentally running an MRtrix command from a different installation to the script
      if is_mrtrix_binary:
        binary_sys = find_executable(item)
        binary_manual = os.path.join(mrtrix_bin_path, item)
        if (isWindows()):
          binary_manual = binary_manual + '.exe'
        use_manual_binary_path = not binary_sys
        if not use_manual_binary_path:
          # os.path.samefile() not supported on all platforms / Python versions
          if hasattr(os.path, 'samefile'):
            use_manual_binary_path = not os.path.samefile(binary_sys, binary_manual)
          else:
            # Hack equivalent of samefile(); not perfect, but should be adequate for use here
            use_manual_binary_path = not os.path.normcase(os.path.normpath(binary_sys)) == os.path.normcase(os.path.normpath(binary_manual))
        if use_manual_binary_path:
          item = binary_manual
      next_is_binary = False
    if item == '|':
      if is_mrtrix_binary:
        if lib.app.mrtrixNThreads:
          new_cmdsplit.extend(lib.app.mrtrixNThreads.strip().split())
        if lib.app.mrtrixQuiet:
          new_cmdsplit.append(lib.app.mrtrixQuiet.strip())
      next_is_binary = True
    new_cmdsplit.append(item)
  if is_mrtrix_binary:
    if lib.app.mrtrixNThreads:
      new_cmdsplit.extend(lib.app.mrtrixNThreads.strip().split())
    if lib.app.mrtrixQuiet:
      new_cmdsplit.append(lib.app.mrtrixQuiet.strip())
  cmdsplit = new_cmdsplit

  # If the piping symbol appears anywhere, we need to split this into multiple commands and execute them separately
  # If no piping symbols, the entire command should just appear as a single row in cmdstack
  cmdstack = [ ]
  prev = 0
  for index, item in enumerate(cmdsplit):
    if item == '|':
      cmdstack.append(cmdsplit[prev:index])
      prev = index + 1
  cmdstack.append(cmdsplit[prev:])

  if lib.app.verbosity:
    sys.stdout.write(lib.app.colourConsole + 'Command:' + lib.app.colourClear + ' ' + cmd + '\n')
    sys.stdout.flush()

  error = False
  if len(cmdstack) == 1:
    error = subprocess.call (cmdstack[0], stdin=None, stdout=None, stderr=None)
  else:
    processes = [ ]
    for index, command in enumerate(cmdstack):
      if index > 0:
        proc_in = processes[index-1].stdout
      else:
        proc_in = None
      if index < len(cmdstack)-1:
        proc_out = subprocess.PIPE
      else:
        proc_out = None
      process = subprocess.Popen (command, stdin=proc_in, stdout=proc_out, stderr=None)
      processes.append(process)

    # Wait for all commands to complete
    for process in processes:
      process.wait()
      if process.returncode:
        error = True

  if (error):
    if exitOnError:
      errorMessage('Command failed: ' + cmd)
    else:
      warnMessage('Command failed: ' + cmd)