def getInputFiles(): import os import lib.app from lib.getUserPath import getUserPath from lib.runCommand import runCommand runCommand('mrconvert ' + getUserPath(lib.app.args.in_5tt, True) + ' ' + os.path.join(lib.app.tempDir, '5tt.mif')) if lib.app.args.dirs: runCommand('mrconvert ' + getUserPath(lib.app.args.dirs, True) + ' ' + os.path.join(lib.app.tempDir, 'dirs.mif') + ' -stride 0,0,0,1')
def getInputFiles(): import os import lib.app from lib.getUserPath import getUserPath 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 ' + getUserPath(lib.app.args.in_voxels, True) + ' ' + os.path.join(lib.app.tempDir, 'in_voxels.mif')) if lib.app.args.dirs: runCommand('mrconvert ' + getUserPath(lib.app.args.dirs, True) + ' ' + os.path.join(lib.app.tempDir, 'dirs.mif') + ' -stride 0,0,0,1')
def getInputFiles(): import os import lib.app from lib.imagesMatch import imagesMatch from lib.errorMessage import errorMessage from lib.getHeaderInfo import getHeaderInfo from lib.getUserPath import getUserPath from lib.runCommand import runCommand if hasattr(lib.app.args, 'mask') and lib.app.args.mask is not None: runCommand('mrconvert ' + getUserPath(lib.app.args.mask, True) + ' ' + os.path.join(lib.app.tempDir, 'mask.mif') + ' -datatype bit -stride -1,+2,+3') if hasattr(lib.app.args, 't2') and lib.app.args.t2 is not None: if not imagesMatch(lib.app.args.input, lib.app.args.t2): errorMessage('Provided T2 image does not match input T1 image') runCommand('mrconvert ' + getUserPath(lib.app.args.t2, True) + ' ' + os.path.join(lib.app.tempDir, 'T2.nii') + ' -stride -1,+2,+3')
def execute(): import os, shutil import lib.app from lib.getUserPath import getUserPath from lib.runCommand import runCommand lmax_option = '' if lib.app.args.lmax: lmax_option = ' -lmax ' + lib.app.args.lmax if not lib.app.args.mask: runCommand('maskfilter mask.mif erode mask_eroded.mif -npass ' + str(lib.app.args.erode)) mask_path = 'mask_eroded.mif' else: mask_path = 'mask.mif' runCommand('dwi2tensor dwi.mif -mask ' + mask_path + ' tensor.mif') runCommand( 'tensor2metric tensor.mif -fa fa.mif -vector vector.mif -mask ' + mask_path) if lib.app.args.threshold: runCommand('mrthreshold fa.mif voxels.mif -abs ' + str(lib.app.args.threshold)) else: runCommand('mrthreshold fa.mif voxels.mif -top ' + str(lib.app.args.number)) runCommand('amp2sh dwi.mif dwiSH.mif' + lmax_option) runCommand('sh2response dwiSH.mif voxels.mif vector.mif response.txt' + lmax_option) shutil.copyfile('response.txt', getUserPath(lib.app.args.output, False))
def getInputFiles(): import os, shutil import lib.app from lib.getUserPath import getUserPath if hasattr(lib.app.args, 'lut') and lib.app.args.lut: shutil.copyfile(getUserPath(lib.app.args.lut, False), os.path.join(lib.app.tempDir, 'LUT.txt'))
def execute(): import os, shutil import lib.app from lib.errorMessage import errorMessage from lib.getHeaderInfo import getHeaderInfo from lib.getUserPath import getUserPath from lib.runCommand import runCommand shells = [ int(round(float(x))) for x in getHeaderInfo('dwi.mif', 'shells').split() ] # Get lmax information (if provided) lmax = [] if lib.app.args.lmax: lmax = [int(x.strip()) for x in lib.app.args.lmax.split(',')] if not len(lmax) == len(shells): errorMessage('Number of manually-defined lmax\'s (' + str(len(lmax)) + ') does not match number of b-value shells (' + str(len(shells)) + ')') for l in lmax: if l % 2: errorMessage('Values for lmax must be even') if l < 0: errorMessage('Values for lmax must be non-negative') # Do we have directions, or do we need to calculate them? if not os.path.exists('dirs.mif'): runCommand( 'dwi2tensor dwi.mif - -mask in_voxels.mif | tensor2metric - -vector dirs.mif' ) # Loop over shells response = [] max_length = 0 for index, b in enumerate(shells): lmax_option = '' if lmax: lmax_option = ' -lmax ' + str(lmax[index]) runCommand( 'dwiextract dwi.mif - -shell ' + str(b) + ' | amp2sh - - | sh2response - in_voxels.mif dirs.mif response_b' + str(b) + '.txt' + lmax_option) shell_response = open('response_b' + str(b) + '.txt', 'r').read().split() response.append(shell_response) max_length = max(max_length, len(shell_response)) with open('response.txt', 'w') as f: for line in response: line += ['0'] * (max_length - len(line)) f.write(' '.join(line) + '\n') shutil.copyfile('response.txt', getUserPath(lib.app.args.output, False)) shutil.copyfile('in_voxels.mif', 'voxels.mif')
def execute(): import os, shutil import lib.app from lib.errorMessage import errorMessage from lib.getHeaderInfo import getHeaderInfo from lib.getUserPath import getUserPath from lib.runCommand import runCommand shells = [ int(round(float(x))) for x in getHeaderInfo('dwi.mif', 'shells').split() ] # Get lmax information (if provided) lmax = [ ] if lib.app.args.lmax: lmax = [ int(x.strip()) for x in lib.app.args.lmax.split(',') ] if not len(lmax) == len(shells): errorMessage('Number of manually-defined lmax\'s (' + str(len(lmax)) + ') does not match number of b-value shells (' + str(len(shells)) + ')') for l in lmax: if l%2: errorMessage('Values for lmax must be even') if l<0: errorMessage('Values for lmax must be non-negative') # Do we have directions, or do we need to calculate them? if not os.path.exists('dirs.mif'): runCommand('dwi2tensor dwi.mif - -mask in_voxels.mif | tensor2metric - -vector dirs.mif') # Loop over shells response = [ ] max_length = 0 for index, b in enumerate(shells): lmax_option = '' if lmax: lmax_option = ' -lmax ' + str(lmax[index]) runCommand('dwiextract dwi.mif - -shell ' + str(b) + ' | amp2sh - - | sh2response - in_voxels.mif dirs.mif response_b' + str(b) + '.txt' + lmax_option) shell_response = open('response_b' + str(b) + '.txt', 'r').read().split() response.append(shell_response) max_length = max(max_length, len(shell_response)) with open('response.txt', 'w') as f: for line in response: line += ['0'] * (max_length - len(line)) f.write(' '.join(line) + '\n') shutil.copyfile('response.txt', getUserPath(lib.app.args.output, False)) shutil.copyfile('in_voxels.mif', 'voxels.mif')
def execute(): import os, shutil import lib.app from lib.getUserPath import getUserPath from lib.runCommand import runCommand lmax_option = '' if lib.app.args.lmax: lmax_option = ' -lmax ' + lib.app.args.lmax if not lib.app.args.mask: runCommand('maskfilter mask.mif erode mask_eroded.mif -npass ' + str(lib.app.args.erode)) mask_path = 'mask_eroded.mif' else: mask_path = 'mask.mif' runCommand('dwi2tensor dwi.mif -mask ' + mask_path + ' tensor.mif') runCommand('tensor2metric tensor.mif -fa fa.mif -vector vector.mif -mask ' + mask_path) if lib.app.args.threshold: runCommand('mrthreshold fa.mif voxels.mif -abs ' + str(lib.app.args.threshold)) else: runCommand('mrthreshold fa.mif voxels.mif -top ' + str(lib.app.args.number)) runCommand('amp2sh dwi.mif dwiSH.mif' + lmax_option) runCommand('sh2response dwiSH.mif voxels.mif vector.mif response.txt' + lmax_option) shutil.copyfile('response.txt', getUserPath(lib.app.args.output, False))
def execute(): import math, os, shutil import lib.app from lib.getImageStat import getImageStat from lib.getUserPath import getUserPath from lib.printMessage import printMessage from lib.runCommand import runCommand lmax_option = '' if lib.app.args.lmax: lmax_option = ' -lmax ' + lib.app.args.lmax runCommand('amp2sh dwi.mif dwiSH.mif' + lmax_option) convergence_change = 0.01 * lib.app.args.convergence for iteration in range(0, lib.app.args.max_iters): prefix = 'iter' + str(iteration) + '_' # How to initialise response function? # old dwi2response command used mean & standard deviation of DWI data; however # this may force the output FODs to lmax=2 at the first iteration # Chantal used a tensor with low FA, but it'd be preferable to get the scaling right # Other option is to do as before, but get the ratio between l=0 and l=2, and # generate l=4,6,... using that amplitude ratio if iteration == 0: RF_in_path = 'init_RF.txt' mask_in_path = 'mask.mif' runCommand('dwiextract dwi.mif shell.mif') # TODO This can be changed once #71 is implemented (mrstats statistics across volumes) volume_means = [float(x) for x in getImageStat('shell.mif', 'mean', 'mask.mif').split()] mean = sum(volume_means) / float(len(volume_means)) volume_stds = [float(x) for x in getImageStat('shell.mif', 'std', 'mask.mif').split()] std = sum(volume_stds) / float(len(volume_stds)) # Scale these to reflect the fact that we're moving to the SH basis mean *= math.sqrt(4.0 * math.pi) std *= math.sqrt(4.0 * math.pi) # Now produce the initial response function # Let's only do it to lmax 4 init_RF = [ str(mean), str(-0.5*std), str(0.25*std*std/mean) ] with open('init_RF.txt', 'w') as f: f.write(' '.join(init_RF)) else: RF_in_path = 'iter' + str(iteration-1) + '_RF.txt' mask_in_path = 'iter' + str(iteration-1) + '_SF.mif' # Run CSD runCommand('dwi2fod csd dwi.mif ' + RF_in_path + ' ' + prefix + 'FOD.mif -mask ' + mask_in_path) # Get amplitudes of two largest peaks, and directions of largest runCommand('fod2fixel ' + prefix + 'FOD.mif -peak ' + prefix + 'peaks.msf -mask ' + mask_in_path + ' -fmls_no_thresholds') runCommand('fixel2voxel ' + prefix + 'peaks.msf split_value ' + prefix + 'amps.mif') runCommand('mrconvert ' + prefix + 'amps.mif ' + prefix + 'first_peaks.mif -coord 3 0 -axes 0,1,2') runCommand('mrconvert ' + prefix + 'amps.mif ' + prefix + 'second_peaks.mif -coord 3 1 -axes 0,1,2') runCommand('fixel2voxel ' + prefix + 'peaks.msf split_dir ' + prefix + 'all_dirs.mif') runCommand('mrconvert ' + prefix + 'all_dirs.mif ' + prefix + 'first_dir.mif -coord 3 0:2') # Revise single-fibre voxel selection based on ratio of tallest to second-tallest peak runCommand('mrcalc ' + prefix + 'second_peaks.mif ' + prefix + 'first_peaks.mif -div ' + prefix + 'peak_ratio.mif') runCommand('mrcalc ' + prefix + 'peak_ratio.mif ' + str(lib.app.args.peak_ratio) + ' -lt ' + mask_in_path + ' -mult ' + prefix + 'SF.mif') # Make sure image isn't empty SF_voxel_count = int(getImageStat(prefix + 'SF.mif', 'count', prefix + 'SF.mif')) if not SF_voxel_count: errorMessage('Aborting: All voxels have been excluded from single-fibre selection') # Generate a new response function runCommand('sh2response dwiSH.mif ' + prefix + 'SF.mif ' + prefix + 'first_dir.mif ' + prefix + 'RF.txt' + lmax_option) # Detect convergence # Look for a change > some percentage - don't bother looking at the masks if iteration > 0: old_RF_file = open(RF_in_path, 'r') old_RF = [ float(x) for x in old_RF_file.read().split() ] new_RF_file = open(prefix + 'RF.txt', 'r') new_RF = [ float(x) for x in new_RF_file.read().split() ] reiterate = False for index in range(0, len(old_RF)): mean = 0.5 * (old_RF[index] + new_RF[index]) diff = math.fabs(0.5 * (old_RF[index] - new_RF[index])) ratio = diff / mean if ratio > convergence_change: reiterate = True if not reiterate: printMessage('Exiting at iteration ' + str(iteration) + ' with ' + str(SF_voxel_count) + ' SF voxels due to unchanged response function coefficients') shutil.copyfile(prefix + 'RF.txt', 'response.txt') shutil.copyfile(prefix + 'SF.mif', 'voxels.mif') break # Go to the next iteration # If we've terminated due to hitting the iteration limiter, we still need to copy the output file(s) to the correct location if not os.path.exists('response.txt'): printMessage('Exiting after maximum ' + str(lib.app.args.max_iters-1) + ' iterations with ' + str(SF_voxel_count) + ' SF voxels') shutil.copyfile('iter' + str(lib.app.args.max_iters-1) + '_RF.txt', 'response.txt') shutil.copyfile('iter' + str(lib.app.args.max_iters-1) + '_SF.mif', 'voxels.mif') shutil.copyfile('response.txt', getUserPath(lib.app.args.output, False))
def execute(): import os, shutil import lib.app from lib.delFile import delFile from lib.getImageStat import getImageStat from lib.getUserPath import getUserPath from lib.printMessage import printMessage from lib.runCommand import runCommand lmax_option = '' if lib.app.args.lmax: lmax_option = ' -lmax ' + lib.app.args.lmax if lib.app.args.max_iters < 2: errorMessage('Number of iterations must be at least 2') runCommand('amp2sh dwi.mif dwiSH.mif' + lmax_option) for iteration in range(0, lib.app.args.max_iters): prefix = 'iter' + str(iteration) + '_' if iteration == 0: RF_in_path = 'init_RF.txt' mask_in_path = 'mask.mif' init_RF = '1 -1 1' with open(RF_in_path, 'w') as f: f.write(init_RF); iter_lmax_option = ' -lmax 4' else: RF_in_path = 'iter' + str(iteration-1) + '_RF.txt' mask_in_path = 'iter' + str(iteration-1) + '_SF_dilated.mif' iter_lmax_option = lmax_option # Run CSD runCommand('dwi2fod csd dwi.mif ' + RF_in_path + ' ' + prefix + 'FOD.mif -mask ' + mask_in_path + iter_lmax_option) # Get amplitudes of two largest peaks, and direction of largest # TODO Speed-test fod2fixel against sh2peaks # TODO Add maximum number of fixels per voxel option to fod2fixel? runCommand('fod2fixel ' + prefix + 'FOD.mif -peak ' + prefix + 'peaks.msf -mask ' + mask_in_path + ' -fmls_no_thresholds') delFile(prefix + 'FOD.mif') if iteration: delFile(mask_in_path) runCommand('fixel2voxel ' + prefix + 'peaks.msf split_value ' + prefix + 'amps.mif') runCommand('mrconvert ' + prefix + 'amps.mif ' + prefix + 'first_peaks.mif -coord 3 0 -axes 0,1,2') runCommand('mrconvert ' + prefix + 'amps.mif ' + prefix + 'second_peaks.mif -coord 3 1 -axes 0,1,2') delFile(prefix + 'amps.mif') runCommand('fixel2voxel ' + prefix + 'peaks.msf split_dir ' + prefix + 'all_dirs.mif') delFile(prefix + 'peaks.msf') runCommand('mrconvert ' + prefix + 'all_dirs.mif ' + prefix + 'first_dir.mif -coord 3 0:2') delFile(prefix + 'all_dirs.mif') # Calculate the 'cost function' Donald derived for selecting single-fibre voxels # https://github.com/MRtrix3/mrtrix3/pull/426 # sqrt(|peak1|) * (1 - |peak2| / |peak1|)^2 runCommand('mrcalc ' + prefix + 'first_peaks.mif -sqrt 1 ' + prefix + 'second_peaks.mif ' + prefix + 'first_peaks.mif -div -sub 2 -pow -mult '+ prefix + 'CF.mif') delFile(prefix + 'first_peaks.mif') delFile(prefix + 'second_peaks.mif') # Select the top-ranked voxels runCommand('mrthreshold ' + prefix + 'CF.mif -top ' + str(lib.app.args.sf_voxels) + ' ' + prefix + 'SF.mif') # Generate a new response function based on this selection runCommand('sh2response dwiSH.mif ' + prefix + 'SF.mif ' + prefix + 'first_dir.mif ' + prefix + 'RF.txt' + iter_lmax_option) delFile(prefix + 'first_dir.mif') # Should we terminate? if iteration > 0: runCommand('mrcalc ' + prefix + 'SF.mif iter' + str(iteration-1) + '_SF.mif -sub ' + prefix + 'SF_diff.mif') delFile('iter' + str(iteration-1) + '_SF.mif') max_diff = getImageStat(prefix + 'SF_diff.mif', 'max') delFile(prefix + 'SF_diff.mif') if int(max_diff) == 0: printMessage('Convergence of SF voxel selection detected at iteration ' + str(iteration)) delFile(prefix + 'CF.mif') shutil.copyfile(prefix + 'RF.txt', 'response.txt') shutil.move(prefix + 'SF.mif', 'voxels.mif') break # Select a greater number of top single-fibre voxels, and dilate (within bounds of initial mask); # these are the voxels that will be re-tested in the next iteration runCommand('mrthreshold ' + prefix + 'CF.mif -top ' + str(lib.app.args.iter_voxels) + ' - | maskfilter - dilate - -npass ' + str(lib.app.args.dilate) + ' | mrcalc mask.mif - -mult ' + prefix + 'SF_dilated.mif') delFile(prefix + 'CF.mif') # Commence the next iteration # If terminating due to running out of iterations, still need to put the results in the appropriate location if not os.path.exists('response.txt'): printMessage('Exiting after maximum ' + str(lib.app.args.max_iters) + ' iterations') shutil.copyfile('iter' + str(lib.app.args.max_iters-1) + '_RF.txt', 'response.txt') shutil.move('iter' + str(lib.app.args.max_iters-1) + '_SF.mif', 'voxels.mif') shutil.copyfile('response.txt', getUserPath(lib.app.args.output, False))
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')
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 # Get b-values and number of volumes per b-value. bvalues = [ int(round(float(x))) for x in getHeaderInfo('dwi.mif', 'shells').split() ] bvolumes = [ int(x) for x in getHeaderInfo('dwi.mif', 'shellcounts').split() ] printMessage( str(len(bvalues)) + ' unique b-value(s) detected: ' + ','.join(map(str, bvalues)) + ' with ' + ','.join(map(str, bvolumes)) + ' volumes.') if len(bvalues) < 2: errorMessage('Need at least 2 unique b-values (including b=0).') # Get lmax information (if provided). sfwm_lmax = [] if lib.app.args.lmax: sfwm_lmax = [int(x.strip()) for x in lib.app.args.lmax.split(',')] if not len(sfwm_lmax) == len(bvalues): errorMessage('Number of lmax\'s (' + str(len(sfwm_lmax)) + ', as supplied to the -lmax option: ' + ','.join(map(str, sfwm_lmax)) + ') does not match number of unique b-values.') for l in sfwm_lmax: if l % 2: errorMessage( 'Values supplied to the -lmax option must be even.') if l < 0: errorMessage( 'Values supplied to the -lmax option must be non-negative.' ) # Erode (brain) mask. if lib.app.args.erode > 0: runCommand('maskfilter mask.mif erode eroded_mask.mif -npass ' + str(lib.app.args.erode)) else: runCommand('mrconvert mask.mif eroded_mask.mif -datatype bit') # Get volumes, compute mean signal and SDM per b-value; compute overall SDM; get rid of erroneous values. totvolumes = 0 fullsdmcmd = 'mrcalc' errcmd = 'mrcalc' zeropath = 'mean_b' + str(bvalues[0]) + '.mif' for i, b in enumerate(bvalues): dwipath = 'dwi_b' + str(b) + '.mif' runCommand('dwiextract dwi.mif -shell ' + str(b) + ' ' + dwipath) meanpath = 'mean_b' + str(b) + '.mif' runCommand('mrmath ' + dwipath + ' mean ' + meanpath + ' -axis 3') errpath = 'err_b' + str(b) + '.mif' runCommand('mrcalc ' + meanpath + ' -finite ' + meanpath + ' 0 -if 0 -le ' + errpath + ' -datatype bit') errcmd += ' ' + errpath if i > 0: errcmd += ' -add' sdmpath = 'sdm_b' + str(b) + '.mif' runCommand('mrcalc ' + zeropath + ' ' + meanpath + ' -divide -log ' + sdmpath) totvolumes += bvolumes[i] fullsdmcmd += ' ' + sdmpath + ' ' + str(bvolumes[i]) + ' -mult' if i > 1: fullsdmcmd += ' -add' fullsdmcmd += ' ' + str(totvolumes) + ' -divide full_sdm.mif' runCommand(fullsdmcmd) runCommand( 'mrcalc full_sdm.mif -finite full_sdm.mif 0 -if 0 -le err_sdm.mif -datatype bit' ) errcmd += ' err_sdm.mif -add 0 eroded_mask.mif -if safe_mask.mif -datatype bit' runCommand(errcmd) runCommand('mrcalc safe_mask.mif full_sdm.mif 0 -if 10 -min safe_sdm.mif') # Compute FA and principal eigenvectors; crude WM versus GM-CSF separation based on FA. runCommand( 'dwi2tensor dwi.mif - -mask safe_mask.mif | tensor2metric - -fa safe_fa.mif -vector safe_vecs.mif -modulate none -mask safe_mask.mif' ) runCommand('mrcalc safe_mask.mif safe_fa.mif 0 -if ' + str(lib.app.args.fa) + ' -gt crude_wm.mif -datatype bit') runCommand( 'mrcalc crude_wm.mif 0 safe_mask.mif -if _crudenonwm.mif -datatype bit' ) # Crude GM versus CSF separation based on SDM. crudenonwmmedian = getImageStat('safe_sdm.mif', 'median', '_crudenonwm.mif') runCommand( 'mrcalc _crudenonwm.mif safe_sdm.mif ' + str(crudenonwmmedian) + ' -subtract 0 -if - | mrthreshold - - -mask _crudenonwm.mif | mrcalc _crudenonwm.mif - 0 -if crude_csf.mif -datatype bit' ) runCommand( 'mrcalc crude_csf.mif 0 _crudenonwm.mif -if crude_gm.mif -datatype bit' ) # Refine WM: remove high SDM outliers. crudewmmedian = getImageStat('safe_sdm.mif', 'median', 'crude_wm.mif') runCommand('mrcalc crude_wm.mif safe_sdm.mif 0 -if ' + str(crudewmmedian) + ' -gt _crudewmhigh.mif -datatype bit') runCommand( 'mrcalc _crudewmhigh.mif 0 crude_wm.mif -if _crudewmlow.mif -datatype bit' ) crudewmQ1 = float(getImageStat('safe_sdm.mif', 'median', '_crudewmlow.mif')) crudewmQ3 = float( getImageStat('safe_sdm.mif', 'median', '_crudewmhigh.mif')) crudewmoutlthresh = crudewmQ3 + (crudewmQ3 - crudewmQ1) runCommand('mrcalc crude_wm.mif safe_sdm.mif 0 -if ' + str(crudewmoutlthresh) + ' -gt _crudewmoutliers.mif -datatype bit') runCommand( 'mrcalc _crudewmoutliers.mif 0 crude_wm.mif -if refined_wm.mif -datatype bit' ) # Refine GM: separate safer GM from partial volumed voxels. crudegmmedian = getImageStat('safe_sdm.mif', 'median', 'crude_gm.mif') runCommand('mrcalc crude_gm.mif safe_sdm.mif 0 -if ' + str(crudegmmedian) + ' -gt _crudegmhigh.mif -datatype bit') runCommand( 'mrcalc _crudegmhigh.mif 0 crude_gm.mif -if _crudegmlow.mif -datatype bit' ) runCommand( 'mrcalc _crudegmhigh.mif safe_sdm.mif ' + str(crudegmmedian) + ' -subtract 0 -if - | mrthreshold - - -mask _crudegmhigh.mif -invert | mrcalc _crudegmhigh.mif - 0 -if _crudegmhighselect.mif -datatype bit' ) runCommand( 'mrcalc _crudegmlow.mif safe_sdm.mif ' + str(crudegmmedian) + ' -subtract -neg 0 -if - | mrthreshold - - -mask _crudegmlow.mif -invert | mrcalc _crudegmlow.mif - 0 -if _crudegmlowselect.mif -datatype bit' ) runCommand( 'mrcalc _crudegmhighselect.mif 1 _crudegmlowselect.mif -if refined_gm.mif -datatype bit' ) # Refine CSF: recover lost CSF from crude WM SDM outliers, separate safer CSF from partial volumed voxels. crudecsfmin = getImageStat('safe_sdm.mif', 'min', 'crude_csf.mif') runCommand('mrcalc _crudewmoutliers.mif safe_sdm.mif 0 -if ' + str(crudecsfmin) + ' -gt 1 crude_csf.mif -if _crudecsfextra.mif -datatype bit') runCommand( 'mrcalc _crudecsfextra.mif safe_sdm.mif ' + str(crudecsfmin) + ' -subtract 0 -if - | mrthreshold - - -mask _crudecsfextra.mif | mrcalc _crudecsfextra.mif - 0 -if refined_csf.mif -datatype bit' ) # Get final voxels for single-fibre WM response function estimation from WM using 'tournier' algorithm. refwmcount = float( getImageStat('refined_wm.mif', 'count', 'refined_wm.mif')) voxsfwmcount = int(round(refwmcount * lib.app.args.sfwm / 100.0)) printMessage('Running \'tournier\' algorithm to select ' + str(voxsfwmcount) + ' single-fibre WM voxels.') cleanopt = '' if not lib.app.cleanup: cleanopt = ' -nocleanup' runCommand( 'dwi2response tournier dwi.mif _respsfwmss.txt -sf_voxels ' + str(voxsfwmcount) + ' -iter_voxels ' + str(voxsfwmcount * 10) + ' -mask refined_wm.mif -voxels voxels_sfwm.mif -quiet -tempdir ' + lib.app.tempDir + cleanopt) # Get final voxels for GM response function estimation from GM. refgmmedian = getImageStat('safe_sdm.mif', 'median', 'refined_gm.mif') runCommand('mrcalc refined_gm.mif safe_sdm.mif 0 -if ' + str(refgmmedian) + ' -gt _refinedgmhigh.mif -datatype bit') runCommand( 'mrcalc _refinedgmhigh.mif 0 refined_gm.mif -if _refinedgmlow.mif -datatype bit' ) refgmhighcount = float( getImageStat('_refinedgmhigh.mif', 'count', '_refinedgmhigh.mif')) refgmlowcount = float( getImageStat('_refinedgmlow.mif', 'count', '_refinedgmlow.mif')) voxgmhighcount = int(round(refgmhighcount * lib.app.args.gm / 100.0)) voxgmlowcount = int(round(refgmlowcount * lib.app.args.gm / 100.0)) runCommand( 'mrcalc _refinedgmhigh.mif safe_sdm.mif 0 -if - | mrthreshold - - -bottom ' + str(voxgmhighcount) + ' -ignorezero | mrcalc _refinedgmhigh.mif - 0 -if _refinedgmhighselect.mif -datatype bit' ) runCommand( 'mrcalc _refinedgmlow.mif safe_sdm.mif 0 -if - | mrthreshold - - -top ' + str(voxgmlowcount) + ' -ignorezero | mrcalc _refinedgmlow.mif - 0 -if _refinedgmlowselect.mif -datatype bit' ) runCommand( 'mrcalc _refinedgmhighselect.mif 1 _refinedgmlowselect.mif -if voxels_gm.mif -datatype bit' ) # Get final voxels for CSF response function estimation from CSF. refcsfcount = float( getImageStat('refined_csf.mif', 'count', 'refined_csf.mif')) voxcsfcount = int(round(refcsfcount * lib.app.args.csf / 100.0)) runCommand( 'mrcalc refined_csf.mif safe_sdm.mif 0 -if - | mrthreshold - - -top ' + str(voxcsfcount) + ' -ignorezero | mrcalc refined_csf.mif - 0 -if voxels_csf.mif -datatype bit' ) # Show summary of voxels counts. textarrow = ' --> ' printMessage('Summary of voxel counts:') printMessage( 'Mask: ' + str(int(getImageStat('mask.mif', 'count', 'mask.mif'))) + textarrow + str(int(getImageStat('eroded_mask.mif', 'count', 'eroded_mask.mif'))) + textarrow + str(int(getImageStat('safe_mask.mif', 'count', 'safe_mask.mif')))) printMessage( 'WM: ' + str(int(getImageStat('crude_wm.mif', 'count', 'crude_wm.mif'))) + textarrow + str(int(getImageStat('refined_wm.mif', 'count', 'refined_wm.mif'))) + textarrow + str(int(getImageStat('voxels_sfwm.mif', 'count', 'voxels_sfwm.mif'))) + ' (SF)') printMessage( 'GM: ' + str(int(getImageStat('crude_gm.mif', 'count', 'crude_gm.mif'))) + textarrow + str(int(getImageStat('refined_gm.mif', 'count', 'refined_gm.mif'))) + textarrow + str(int(getImageStat('voxels_gm.mif', 'count', 'voxels_gm.mif')))) printMessage( 'CSF: ' + str(int(getImageStat('crude_csf.mif', 'count', 'crude_csf.mif'))) + textarrow + str(int(getImageStat('refined_csf.mif', 'count', 'refined_csf.mif'))) + textarrow + str(int(getImageStat('voxels_csf.mif', 'count', 'voxels_csf.mif')))) # Generate single-fibre WM, GM and CSF responses sfwm_responses = [] gm_responses = [] csf_responses = [] max_length = 0 for index, b in enumerate(bvalues): dwipath = 'dwi_b' + str(b) + '.mif' this_b_lmax_option = '' if sfwm_lmax: this_b_lmax_option = ' -lmax ' + str(sfwm_lmax[index]) runCommand( 'amp2sh ' + dwipath + ' - | sh2response - voxels_sfwm.mif safe_vecs.mif _respsfwmb' + str(b) + '.txt' + this_b_lmax_option) sfwm_response = open('_respsfwmb' + str(b) + '.txt', 'r').read().split() sfwm_responses.append(sfwm_response) max_length = max(max_length, len(sfwm_response)) meanpath = 'mean_b' + str(b) + '.mif' gm_mean = float(getImageStat(meanpath, 'mean', 'voxels_gm.mif')) csf_mean = float(getImageStat(meanpath, 'mean', 'voxels_csf.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('response_sfwm.txt', 'w') as f: for line in sfwm_responses: line += ['0'] * (max_length - len(line)) f.write(' '.join(line) + '\n') with open('response_gm.txt', 'w') as f: for line in gm_responses: f.write(line + '\n') with open('response_csf.txt', 'w') as f: for line in csf_responses: f.write(line + '\n') shutil.copyfile('response_sfwm.txt', getUserPath(lib.app.args.out_sfwm, False)) shutil.copyfile('response_gm.txt', getUserPath(lib.app.args.out_gm, False)) shutil.copyfile('response_csf.txt', getUserPath(lib.app.args.out_csf, False)) # Generate 4D binary images with voxel selection at major stages in algorithm (RGB as in MSMT-CSD paper). runCommand( 'mrcat crude_csf.mif crude_gm.mif crude_wm.mif crude.mif -axis 3') runCommand( 'mrcat refined_csf.mif refined_gm.mif refined_wm.mif refined.mif -axis 3' ) runCommand( 'mrcat voxels_csf.mif voxels_gm.mif voxels_sfwm.mif voxels.mif -axis 3' )
def execute(): import math, os, shutil import lib.app from lib.getImageStat import getImageStat from lib.getUserPath import getUserPath from lib.printMessage import printMessage from lib.runCommand import runCommand lmax_option = '' if lib.app.args.lmax: lmax_option = ' -lmax ' + lib.app.args.lmax runCommand('amp2sh dwi.mif dwiSH.mif' + lmax_option) convergence_change = 0.01 * lib.app.args.convergence for iteration in range(0, lib.app.args.max_iters): prefix = 'iter' + str(iteration) + '_' # How to initialise response function? # old dwi2response command used mean & standard deviation of DWI data; however # this may force the output FODs to lmax=2 at the first iteration # Chantal used a tensor with low FA, but it'd be preferable to get the scaling right # Other option is to do as before, but get the ratio between l=0 and l=2, and # generate l=4,6,... using that amplitude ratio if iteration == 0: RF_in_path = 'init_RF.txt' mask_in_path = 'mask.mif' runCommand('dwiextract dwi.mif shell.mif') # TODO This can be changed once #71 is implemented (mrstats statistics across volumes) volume_means = [ float(x) for x in getImageStat('shell.mif', 'mean', 'mask.mif').split() ] mean = sum(volume_means) / float(len(volume_means)) volume_stds = [ float(x) for x in getImageStat('shell.mif', 'std', 'mask.mif').split() ] std = sum(volume_stds) / float(len(volume_stds)) # Scale these to reflect the fact that we're moving to the SH basis mean *= math.sqrt(4.0 * math.pi) std *= math.sqrt(4.0 * math.pi) # Now produce the initial response function # Let's only do it to lmax 4 init_RF = [ str(mean), str(-0.5 * std), str(0.25 * std * std / mean) ] with open('init_RF.txt', 'w') as f: f.write(' '.join(init_RF)) else: RF_in_path = 'iter' + str(iteration - 1) + '_RF.txt' mask_in_path = 'iter' + str(iteration - 1) + '_SF.mif' # Run CSD runCommand('dwi2fod csd dwi.mif ' + RF_in_path + ' ' + prefix + 'FOD.mif -mask ' + mask_in_path) # Get amplitudes of two largest peaks, and directions of largest runCommand('fod2fixel ' + prefix + 'FOD.mif -peak ' + prefix + 'peaks.msf -mask ' + mask_in_path + ' -fmls_no_thresholds') runCommand('fixel2voxel ' + prefix + 'peaks.msf split_value ' + prefix + 'amps.mif') runCommand('mrconvert ' + prefix + 'amps.mif ' + prefix + 'first_peaks.mif -coord 3 0 -axes 0,1,2') runCommand('mrconvert ' + prefix + 'amps.mif ' + prefix + 'second_peaks.mif -coord 3 1 -axes 0,1,2') runCommand('fixel2voxel ' + prefix + 'peaks.msf split_dir ' + prefix + 'all_dirs.mif') runCommand('mrconvert ' + prefix + 'all_dirs.mif ' + prefix + 'first_dir.mif -coord 3 0:2') # Revise single-fibre voxel selection based on ratio of tallest to second-tallest peak runCommand('mrcalc ' + prefix + 'second_peaks.mif ' + prefix + 'first_peaks.mif -div ' + prefix + 'peak_ratio.mif') runCommand('mrcalc ' + prefix + 'peak_ratio.mif ' + str(lib.app.args.peak_ratio) + ' -lt ' + mask_in_path + ' -mult ' + prefix + 'SF.mif') # Make sure image isn't empty SF_voxel_count = int( getImageStat(prefix + 'SF.mif', 'count', prefix + 'SF.mif')) if not SF_voxel_count: errorMessage( 'Aborting: All voxels have been excluded from single-fibre selection' ) # Generate a new response function runCommand('sh2response dwiSH.mif ' + prefix + 'SF.mif ' + prefix + 'first_dir.mif ' + prefix + 'RF.txt' + lmax_option) # Detect convergence # Look for a change > some percentage - don't bother looking at the masks if iteration > 0: old_RF_file = open(RF_in_path, 'r') old_RF = [float(x) for x in old_RF_file.read().split()] new_RF_file = open(prefix + 'RF.txt', 'r') new_RF = [float(x) for x in new_RF_file.read().split()] reiterate = False for index in range(0, len(old_RF)): mean = 0.5 * (old_RF[index] + new_RF[index]) diff = math.fabs(0.5 * (old_RF[index] - new_RF[index])) ratio = diff / mean if ratio > convergence_change: reiterate = True if not reiterate: printMessage( 'Exiting at iteration ' + str(iteration) + ' with ' + str(SF_voxel_count) + ' SF voxels due to unchanged response function coefficients' ) shutil.copyfile(prefix + 'RF.txt', 'response.txt') shutil.copyfile(prefix + 'SF.mif', 'voxels.mif') break # Go to the next iteration # If we've terminated due to hitting the iteration limiter, we still need to copy the output file(s) to the correct location if not os.path.exists('response.txt'): printMessage('Exiting after maximum ' + str(lib.app.args.max_iters - 1) + ' iterations with ' + str(SF_voxel_count) + ' SF voxels') shutil.copyfile('iter' + str(lib.app.args.max_iters - 1) + '_RF.txt', 'response.txt') shutil.copyfile('iter' + str(lib.app.args.max_iters - 1) + '_SF.mif', 'voxels.mif') shutil.copyfile('response.txt', getUserPath(lib.app.args.output, False))
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')