def execute(): #pylint: disable=unused-variable import math, os, shutil from mrtrix3 import app, image, matrix, MRtrixError, path, run lmax_option = '' if app.ARGS.lmax: lmax_option = ' -lmax ' + app.ARGS.lmax convergence_change = 0.01 * app.ARGS.convergence progress = app.ProgressBar('Optimising') iteration = 0 while iteration < 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' # Grab the mean and standard deviation across all volumes in a single mrstats call # Also scale them to reflect the fact that we're moving to the SH basis mean = image.statistic('dwi.mif', 'mean', '-mask mask.mif -allvolumes') * math.sqrt( 4.0 * math.pi) std = image.statistic('dwi.mif', 'std', '-mask mask.mif -allvolumes') * 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 init_rf_file: init_rf_file.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 run.command('dwi2fod csd dwi.mif ' + rf_in_path + ' ' + prefix + 'FOD.mif -mask ' + mask_in_path) # Get amplitudes of two largest peaks, and directions of largest run.command('fod2fixel ' + prefix + 'FOD.mif ' + prefix + 'fixel -peak peaks.mif -mask ' + mask_in_path + ' -fmls_no_thresholds') app.cleanup(prefix + 'FOD.mif') run.command('fixel2voxel ' + prefix + 'fixel/peaks.mif split_data ' + prefix + 'amps.mif') run.command('mrconvert ' + prefix + 'amps.mif ' + prefix + 'first_peaks.mif -coord 3 0 -axes 0,1,2') run.command('mrconvert ' + prefix + 'amps.mif ' + prefix + 'second_peaks.mif -coord 3 1 -axes 0,1,2') app.cleanup(prefix + 'amps.mif') run.command('fixel2voxel ' + prefix + 'fixel/directions.mif split_dir ' + prefix + 'all_dirs.mif') app.cleanup(prefix + 'fixel') run.command('mrconvert ' + prefix + 'all_dirs.mif ' + prefix + 'first_dir.mif -coord 3 0:2') app.cleanup(prefix + 'all_dirs.mif') # Revise single-fibre voxel selection based on ratio of tallest to second-tallest peak run.command('mrcalc ' + prefix + 'second_peaks.mif ' + prefix + 'first_peaks.mif -div ' + prefix + 'peak_ratio.mif') app.cleanup(prefix + 'first_peaks.mif') app.cleanup(prefix + 'second_peaks.mif') run.command('mrcalc ' + prefix + 'peak_ratio.mif ' + str(app.ARGS.peak_ratio) + ' -lt ' + mask_in_path + ' -mult ' + prefix + 'SF.mif -datatype bit') app.cleanup(prefix + 'peak_ratio.mif') # Make sure image isn't empty sf_voxel_count = image.statistic(prefix + 'SF.mif', 'count', '-mask ' + prefix + 'SF.mif') if not sf_voxel_count: raise MRtrixError( 'Aborting: All voxels have been excluded from single-fibre selection' ) # Generate a new response function run.command('amp2response dwi.mif ' + prefix + 'SF.mif ' + prefix + 'first_dir.mif ' + prefix + 'RF.txt' + lmax_option) app.cleanup(prefix + 'first_dir.mif') new_rf = matrix.load_vector(prefix + 'RF.txt') progress.increment('Optimising (' + str(iteration + 1) + ' iterations, ' + str(sf_voxel_count) + ' voxels, RF: [ ' + ', '.join('{:.3f}'.format(n) for n in new_rf) + '] )') # Detect convergence # Look for a change > some percentage - don't bother looking at the masks if iteration > 0: old_rf = matrix.load_vector(rf_in_path) reiterate = False for old_value, new_value in zip(old_rf, new_rf): mean = 0.5 * (old_value + new_value) diff = math.fabs(0.5 * (old_value - new_value)) ratio = diff / mean if ratio > convergence_change: reiterate = True if not reiterate: run.function(shutil.copyfile, prefix + 'RF.txt', 'response.txt') run.function(shutil.copyfile, prefix + 'SF.mif', 'voxels.mif') break app.cleanup(rf_in_path) app.cleanup(mask_in_path) iteration += 1 progress.done() # If we've terminated due to hitting the iteration limiter, we still need to copy the output file(s) to the correct location if os.path.exists('response.txt'): app.console('Exited at iteration ' + str(iteration + 1) + ' with ' + str(sf_voxel_count) + ' SF voxels due to unchanged RF coefficients') else: app.console('Exited after maximum ' + str(app.ARGS.max_iters) + ' iterations with ' + str(sf_voxel_count) + ' SF voxels') run.function(shutil.copyfile, 'iter' + str(app.ARGS.max_iters - 1) + '_RF.txt', 'response.txt') run.function(shutil.copyfile, 'iter' + str(app.ARGS.max_iters - 1) + '_SF.mif', 'voxels.mif') run.function(shutil.copyfile, 'response.txt', path.from_user(app.ARGS.output, False)) if app.ARGS.voxels: run.command('mrconvert voxels.mif ' + path.from_user(app.ARGS.voxels), mrconvert_keyval=path.from_user(app.ARGS.input), force=app.FORCE_OVERWRITE)
def execute(): #pylint: disable=unused-variable lmax_option = '' if app.ARGS.lmax: lmax_option = ' -lmax ' + app.ARGS.lmax if app.ARGS.max_iters < 2: raise MRtrixError('Number of iterations must be at least 2') progress = app.ProgressBar('Optimising') iter_voxels = app.ARGS.iter_voxels if iter_voxels == 0: iter_voxels = 10 * app.ARGS.number elif iter_voxels < app.ARGS.number: raise MRtrixError( 'Number of selected voxels (-iter_voxels) must be greater than number of voxels desired (-number)' ) iteration = 0 while iteration < 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 init_rf_file: init_rf_file.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 run.command('dwi2fod csd dwi.mif ' + rf_in_path + ' ' + prefix + 'FOD.mif -mask ' + mask_in_path) # Get amplitudes of two largest peaks, and direction of largest run.command('fod2fixel ' + prefix + 'FOD.mif ' + prefix + 'fixel -peak peaks.mif -mask ' + mask_in_path + ' -fmls_no_thresholds') app.cleanup(prefix + 'FOD.mif') if iteration: app.cleanup(mask_in_path) run.command('fixel2voxel ' + prefix + 'fixel/peaks.mif none ' + prefix + 'amps.mif -number 2') run.command('mrconvert ' + prefix + 'amps.mif ' + prefix + 'first_peaks.mif -coord 3 0 -axes 0,1,2') run.command('mrconvert ' + prefix + 'amps.mif ' + prefix + 'second_peaks.mif -coord 3 1 -axes 0,1,2') app.cleanup(prefix + 'amps.mif') run.command('fixel2peaks ' + prefix + 'fixel/directions.mif ' + prefix + 'first_dir.mif -number 1') app.cleanup(prefix + 'fixel') # Calculate the 'cost function' Donald derived for selecting single-fibre voxels # https://github.com/MRtrix3/mrtrix3/pull/426 # sqrt(|peak1|) * (1 - |peak2| / |peak1|)^2 run.command('mrcalc ' + prefix + 'first_peaks.mif -sqrt 1 ' + prefix + 'second_peaks.mif ' + prefix + 'first_peaks.mif -div -sub 2 -pow -mult ' + prefix + 'CF.mif') app.cleanup(prefix + 'first_peaks.mif') app.cleanup(prefix + 'second_peaks.mif') voxel_count = image.statistics(prefix + 'CF.mif').count # Select the top-ranked voxels run.command('mrthreshold ' + prefix + 'CF.mif -top ' + str(min([app.ARGS.number, voxel_count])) + ' ' + prefix + 'SF.mif') # Generate a new response function based on this selection run.command('amp2response dwi.mif ' + prefix + 'SF.mif ' + prefix + 'first_dir.mif ' + prefix + 'RF.txt' + iter_lmax_option) app.cleanup(prefix + 'first_dir.mif') new_rf = matrix.load_vector(prefix + 'RF.txt') progress.increment('Optimising (' + str(iteration + 1) + ' iterations, RF: [ ' + ', '.join('{:.3f}'.format(n) for n in new_rf) + '] )') # Should we terminate? if iteration > 0: run.command('mrcalc ' + prefix + 'SF.mif iter' + str(iteration - 1) + '_SF.mif -sub ' + prefix + 'SF_diff.mif') app.cleanup('iter' + str(iteration - 1) + '_SF.mif') max_diff = image.statistics(prefix + 'SF_diff.mif').max app.cleanup(prefix + 'SF_diff.mif') if not max_diff: app.cleanup(prefix + 'CF.mif') run.function(shutil.copyfile, prefix + 'RF.txt', 'response.txt') run.function(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 run.command('mrthreshold ' + prefix + 'CF.mif -top ' + str(min([iter_voxels, voxel_count])) + ' - | maskfilter - dilate - -npass ' + str(app.ARGS.dilate) + ' | mrcalc mask.mif - -mult ' + prefix + 'SF_dilated.mif') app.cleanup(prefix + 'CF.mif') iteration += 1 progress.done() # If terminating due to running out of iterations, still need to put the results in the appropriate location if os.path.exists('response.txt'): app.console( 'Convergence of SF voxel selection detected at iteration ' + str(iteration + 1)) else: app.console('Exiting after maximum ' + str(app.ARGS.max_iters) + ' iterations') run.function(shutil.copyfile, 'iter' + str(app.ARGS.max_iters - 1) + '_RF.txt', 'response.txt') run.function(shutil.move, 'iter' + str(app.ARGS.max_iters - 1) + '_SF.mif', 'voxels.mif') run.function(shutil.copyfile, 'response.txt', path.from_user(app.ARGS.output, False)) if app.ARGS.voxels: run.command('mrconvert voxels.mif ' + path.from_user(app.ARGS.voxels), mrconvert_keyval=path.from_user(app.ARGS.input, False), force=app.FORCE_OVERWRITE)