Beispiel #1
0
def execute():  #pylint: disable=unused-variable
    import os, shutil
    from mrtrix3 import app, file, image, path, run  #pylint: disable=redefined-builtin

    lmax_option = ''
    if app.args.lmax:
        lmax_option = ' -lmax ' + app.args.lmax

    if app.args.max_iters < 2:
        app.error('Number of iterations must be at least 2')

    for iteration in range(0, 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
        run.command('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
        run.command('fod2fixel ' + prefix + 'FOD.mif ' + prefix +
                    'fixel -peak peaks.mif -mask ' + mask_in_path +
                    ' -fmls_no_thresholds')
        file.delTemporary(prefix + 'FOD.mif')
        if iteration:
            file.delTemporary(mask_in_path)
        run.command('fixel2voxel ' + prefix + 'fixel/peaks.mif split_data ' +
                    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')
        file.delTemporary(prefix + 'amps.mif')
        run.command('fixel2voxel ' + prefix +
                    'fixel/directions.mif split_dir ' + prefix +
                    'all_dirs.mif -number 1')
        file.delTemporary(prefix + 'fixel')
        run.command('mrconvert ' + prefix + 'all_dirs.mif ' + prefix +
                    'first_dir.mif -coord 3 0:2')
        file.delTemporary(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
        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')
        file.delTemporary(prefix + 'first_peaks.mif')
        file.delTemporary(prefix + 'second_peaks.mif')
        # Select the top-ranked voxels
        run.command('mrthreshold ' + prefix + 'CF.mif -top ' +
                    str(app.args.sf_voxels) + ' ' + 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)
        file.delTemporary(prefix + 'first_dir.mif')
        # Should we terminate?
        if iteration > 0:
            run.command('mrcalc ' + prefix + 'SF.mif iter' +
                        str(iteration - 1) + '_SF.mif -sub ' + prefix +
                        'SF_diff.mif')
            file.delTemporary('iter' + str(iteration - 1) + '_SF.mif')
            max_diff = image.statistic(prefix + 'SF_diff.mif', 'max')
            file.delTemporary(prefix + 'SF_diff.mif')
            if int(max_diff) == 0:
                app.console(
                    'Convergence of SF voxel selection detected at iteration '
                    + str(iteration))
                file.delTemporary(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(app.args.iter_voxels) +
                    ' - | maskfilter - dilate - -npass ' +
                    str(app.args.dilate) + ' | mrcalc mask.mif - -mult ' +
                    prefix + 'SF_dilated.mif')
        file.delTemporary(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'):
        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.fromUser(app.args.output, False))
Beispiel #2
0
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)
Beispiel #3
0
def runSubject(bids_dir, label, output_prefix):

    output_dir = os.path.join(output_prefix, label)
    if os.path.exists(output_dir):
        shutil.rmtree(output_dir)
    os.makedirs(output_dir)
    os.makedirs(os.path.join(output_dir, 'connectome'))
    os.makedirs(os.path.join(output_dir, 'dwi'))

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

    flirt_cmd = fsl.exeName('flirt')
    fslanat_cmd = fsl.exeName('fsl_anat')
    fsl_suffix = fsl.suffix()

    unring_cmd = 'unring.a64'
    if not find_executable(unring_cmd):
        app.console('Command \'' + unring_cmd +
                    '\' not found; cannot perform Gibbs ringing removal')
        unring_cmd = ''

    dwibiascorrect_algo = '-ants'
    if not find_executable('N4BiasFieldCorrection'):
        # Can't use findFSLBinary() here, since we want to proceed even if it's not found
        if find_executable('fast') or find_executable('fsl5.0-fast'):
            dwibiascorrect_algo = '-fsl'
            app.console('Could not find ANTs program N4BiasFieldCorrection; '
                        'using FSL FAST for bias field correction')
        else:
            dwibiascorrect_algo = ''
            app.warn(
                'Could not find ANTs program \'N4BiasFieldCorrection\' or FSL program \'fast\'; '
                'will proceed without performing DWI bias field correction')

    if not app.args.parcellation:
        app.error(
            'For participant-level analysis, desired parcellation must be provided using the -parcellation option'
        )

    parc_image_path = ''
    parc_lut_file = ''
    mrtrix_lut_file = os.path.join(
        os.path.dirname(os.path.abspath(app.__file__)), os.pardir, os.pardir,
        'share', 'mrtrix3', 'labelconvert')

    if app.args.parcellation == 'fs_2005' or app.args.parcellation == 'fs_2009':
        if not 'FREESURFER_HOME' in os.environ:
            app.error(
                'Environment variable FREESURFER_HOME not set; please verify FreeSurfer installation'
            )
        if not find_executable('recon-all'):
            app.error(
                'Could not find FreeSurfer script recon-all; please verify FreeSurfer installation'
            )
        parc_lut_file = os.path.join(os.environ['FREESURFER_HOME'],
                                     'FreeSurferColorLUT.txt')
        if app.args.parcellation == 'fs_2005':
            mrtrix_lut_file = os.path.join(mrtrix_lut_file, 'fs_default.txt')
        else:
            mrtrix_lut_file = os.path.join(mrtrix_lut_file, 'fs_a2009s.txt')

    if app.args.parcellation == 'aal' or app.args.parcellation == 'aal2':
        mni152_path = os.path.join(fsl_path, 'data', 'standard',
                                   'MNI152_T1_1mm.nii.gz')
        if not os.path.isfile(mni152_path):
            app.error(
                'Could not find MNI152 template image within FSL installation (expected location: '
                + mni152_path + ')')
        if app.args.parcellation == 'aal':
            parc_image_path = os.path.abspath(
                os.path.join(os.sep, 'opt', 'aal', 'ROI_MNI_V4.nii'))
            parc_lut_file = os.path.abspath(
                os.path.join(os.sep, 'opt', 'aal', 'ROI_MNI_V4.txt'))
            mrtrix_lut_file = os.path.join(mrtrix_lut_file, 'aal.txt')
        else:
            parc_image_path = os.path.abspath(
                os.path.join(os.sep, 'opt', 'aal', 'ROI_MNI_V5.nii'))
            parc_lut_file = os.path.abspath(
                os.path.join(os.sep, 'opt', 'aal', 'ROI_MNI_V5.txt'))
            mrtrix_lut_file = os.path.join(mrtrix_lut_file, 'aal2.txt')

    if parc_image_path and not os.path.isfile(parc_image_path):
        if app.args.atlas_path:
            parc_image_path = [
                parc_image_path,
                os.path.join(os.path.dirname(app.args.atlas_path),
                             os.path.basename(parc_image_path))
            ]
            if os.path.isfile(parc_image_path[1]):
                parc_image_path = parc_image_path[1]
            else:
                app.error(
                    'Could not find parcellation image (tested locations: ' +
                    str(parc_image_path) + ')')
        else:
            app.error(
                'Could not find parcellation image (expected location: ' +
                parc_image_path + ')')
    if not os.path.isfile(parc_lut_file):
        if app.args.atlas_path:
            parc_lut_file = [
                parc_lut_file,
                os.path.join(os.path.dirname(app.args.atlas_path),
                             os.path.basename(parc_lut_file))
            ]
            if os.path.isfile(parc_lut_file[1]):
                parc_lut_file = parc_lut_file[1]
            else:
                app.error(
                    'Could not find parcellation lookup table file (tested locations: '
                    + str(parc_lut_file) + ')')
        else:
            app.error(
                'Could not find parcellation lookup table file (expected location: '
                + parc_lut_file + ')')
    if not os.path.exists(mrtrix_lut_file):
        app.error(
            'Could not find MRtrix3 connectome lookup table file (expected location: '
            + mrtrix_lut_file + ')')

    app.makeTempDir()

    # Need to perform an initial import of JSON data using mrconvert; so let's grab the diffusion gradient table as well
    # If no bvec/bval present, need to go down the directory listing
    # Only try to import JSON file if it's actually present
    #   direction in the acquisition they'll need to be split across multiple files
    # May need to concatenate more than one input DWI, since if there's more than one phase-encode direction
    #   in the acquired DWIs (i.e. not just those used for estimating the inhomogeneity field), they will
    #   need to be stored as separate NIfTI files in the 'dwi/' directory.
    dwi_image_list = glob.glob(
        os.path.join(bids_dir, label, 'dwi', label) + '*_dwi.nii*')
    dwi_index = 1
    for entry in dwi_image_list:
        # os.path.split() falls over with .nii.gz extensions; only removes the .gz
        prefix = entry.split(os.extsep)[0]
        if os.path.isfile(prefix + '.bval') and os.path.isfile(prefix +
                                                               '.bvec'):
            prefix = prefix + '.'
        else:
            prefix = os.path.join(bids_dir, 'dwi')
            if not (os.path.isfile(prefix + 'bval')
                    and os.path.isfile(prefix + 'bvec')):
                app.error(
                    'Unable to locate valid diffusion gradient table for image \''
                    + entry + '\'')
        grad_import_option = ' -fslgrad ' + prefix + 'bvec ' + prefix + 'bval'
        json_path = prefix + 'json'
        if os.path.isfile(json_path):
            json_import_option = ' -json_import ' + json_path
        else:
            json_import_option = ''
        run.command('mrconvert ' + entry + grad_import_option +
                    json_import_option + ' ' +
                    path.toTemp('dwi' + str(dwi_index) + '.mif', True))
        dwi_index += 1

    # Go hunting for reversed phase-encode data dedicated to field map estimation
    fmap_image_list = []
    fmap_dir = os.path.join(bids_dir, label, 'fmap')
    fmap_index = 1
    if os.path.isdir(fmap_dir):
        if app.args.preprocessed:
            app.error('fmap/ directory detected for subject \'' + label +
                      '\' despite use of ' + option_prefix +
                      'preprocessed option')
        fmap_image_list = glob.glob(
            os.path.join(fmap_dir, label) + '_dir-*_epi.nii*')
        for entry in fmap_image_list:
            prefix = entry.split(os.extsep)[0]
            json_path = prefix + '.json'
            with open(json_path, 'r') as f:
                json_elements = json.load(f)
            if 'IntendedFor' in json_elements and not any(
                    i.endswith(json_elements['IntendedFor'])
                    for i in dwi_image_list):
                app.console('Image \'' + entry +
                            '\' is not intended for use with DWIs; skipping')
                continue
            if os.path.isfile(json_path):
                json_import_option = ' -json_import ' + json_path
                # fmap files will not come with any gradient encoding in the JSON;
                #   therefore we need to add it manually ourselves so that mrcat / mrconvert can
                #   appropriately handle the table once these images are concatenated with the DWIs
                fmap_image_size = image.Header(entry).size()
                fmap_image_num_volumes = 1 if len(
                    fmap_image_size) == 3 else fmap_image_size[3]
                run.command('mrconvert ' + entry + json_import_option +
                            ' -set_property dw_scheme \"' +
                            '\\n'.join(['0,0,1,0'] * fmap_image_num_volumes) +
                            '\" ' +
                            path.toTemp('fmap' + str(fmap_index) +
                                        '.mif', True))
                fmap_index += 1
            else:
                app.warn('No corresponding .json file found for image \'' +
                         entry + '\'; skipping')

        fmap_image_list = [
            'fmap' + str(index) + '.mif' for index in range(1, fmap_index)
        ]
    # If there's no data in fmap/ directory, need to check to see if there's any phase-encoding
    #   contrast within the input DWI(s)
    elif len(dwi_image_list) < 2 and not app.args.preprocessed:
        app.error(
            'Inadequate data for pre-processing of subject \'' + label +
            '\': No phase-encoding contrast in input DWIs or fmap/ directory')

    dwi_image_list = [
        'dwi' + str(index) + '.mif' for index in range(1, dwi_index)
    ]

    # Import anatomical image
    run.command('mrconvert ' +
                os.path.join(bids_dir, label, 'anat', label + '_T1w.nii.gz') +
                ' ' + path.toTemp('T1.mif', True))

    cwd = os.getcwd()
    app.gotoTempDir()

    dwipreproc_se_epi = ''
    dwipreproc_se_epi_option = ''

    # For automated testing, down-sampled images are used. However, this invalidates the requirements of
    #   both MP-PCA denoising and Gibbs ringing removal. In addition, eddy can still take a long time
    #   despite the down-sampling. Therefore, provide images that have been pre-processed to the stage
    #   where it is still only DWI, JSON & bvecs/bvals that need to be provided.
    if app.args.preprocessed:

        if len(dwi_image_list) > 1:
            app.error(
                'If DWIs have been pre-processed, then only a single DWI file should need to be provided'
            )
        app.console(
            'Skipping MP-PCA denoising, ' +
            ('Gibbs ringing removal, ' if unring_cmd else '') +
            'distortion correction and bias field correction due to use of ' +
            option_prefix + 'preprocessed option')
        run.function(os.rename, dwi_image_list[0], 'dwi.mif')

    else:  # Do initial image pre-processing (denoising, Gibbs ringing removal if available, distortion correction & bias field correction) as normal

        # Concatenate any SE EPI images with the DWIs before denoising (& unringing), then
        #   separate them again after the fact
        dwidenoise_input = 'dwidenoise_input.mif'
        fmap_num_volumes = 0
        if fmap_image_list:
            run.command('mrcat ' + ' '.join(fmap_image_list) +
                        ' fmap_cat.mif -axis 3')
            for i in fmap_image_list:
                file.delTemporary(i)
            fmap_num_volumes = image.Header('fmap_cat.mif').size()[3]
            dwidenoise_input = 'all_cat.mif'
            run.command('mrcat fmap_cat.mif ' + ' '.join(dwi_image_list) +
                        ' ' + dwidenoise_input + ' -axis 3')
            file.delTemporary('fmap_cat.mif')
        else:
            # Even if no explicit fmap images, may still need to concatenate multiple DWI inputs
            if len(dwi_image_list) > 1:
                run.command('mrcat ' + ' '.join(dwi_image_list) + ' ' +
                            dwidenoise_input + ' -axis 3')
            else:
                run.function(shutil.move, dwi_image_list[0], dwidenoise_input)

        for i in dwi_image_list:
            file.delTemporary(i)

        # Step 1: Denoise
        run.command('dwidenoise ' + dwidenoise_input + ' dwi_denoised.' +
                    ('nii' if unring_cmd else 'mif'))
        if unring_cmd:
            run.command('mrinfo ' + dwidenoise_input +
                        ' -json_keyval input.json')
        file.delTemporary(dwidenoise_input)

        # Step 2: Gibbs ringing removal (if available)
        if unring_cmd:
            run.command(unring_cmd + ' dwi_denoised.nii dwi_unring' +
                        fsl_suffix + ' -n 100')
            file.delTemporary('dwi_denoised.nii')
            unring_output_path = fsl.findImage('dwi_unring')
            run.command('mrconvert ' + unring_output_path +
                        ' dwi_unring.mif -json_import input.json')
            file.delTemporary(unring_output_path)
            file.delTemporary('input.json')

        # If fmap images and DWIs have been concatenated, now is the time to split them back apart
        dwipreproc_input = 'dwi_unring.mif' if unring_cmd else 'dwi_denoised.mif'

        if fmap_num_volumes:
            cat_input = 'dwi_unring.mif' if unring_cmd else 'dwi_denoised.mif'
            dwipreproc_se_epi = 'se_epi.mif'
            run.command('mrconvert ' + cat_input + ' ' + dwipreproc_se_epi +
                        ' -coord 3 0:' + str(fmap_num_volumes - 1))
            cat_num_volumes = image.Header(cat_input).size()[3]
            run.command('mrconvert ' + cat_input +
                        ' dwipreproc_in.mif -coord 3 ' +
                        str(fmap_num_volumes) + ':' + str(cat_num_volumes - 1))
            file.delTemporary(dwipreproc_input)
            dwipreproc_input = 'dwipreproc_in.mif'
            dwipreproc_se_epi_option = ' -se_epi ' + dwipreproc_se_epi

        # Step 3: Distortion correction
        run.command('dwipreproc ' + dwipreproc_input +
                    ' dwi_preprocessed.mif -rpe_header' +
                    dwipreproc_se_epi_option)
        file.delTemporary(dwipreproc_input)
        if dwipreproc_se_epi:
            file.delTemporary(dwipreproc_se_epi)

        # Step 4: Bias field correction
        if dwibiascorrect_algo:
            run.command('dwibiascorrect dwi_preprocessed.mif dwi.mif ' +
                        dwibiascorrect_algo)
            file.delTemporary('dwi_preprocessed.mif')
        else:
            run.function(shutil.move, 'dwi_preprocessed.mif', 'dwi.mif')

    # No longer branching based on whether or not -preprocessed was specified

    # Step 5: Generate a brain mask for DWI
    run.command('dwi2mask dwi.mif dwi_mask.mif')

    # Step 6: Perform brain extraction on the T1 image in its original space
    #         (this is necessary for histogram matching prior to registration)
    #         Use fsl_anat script
    run.command('mrconvert T1.mif T1.nii -stride -1,+2,+3')
    run.command(fslanat_cmd + ' -i T1.nii --noseg --nosubcortseg')
    run.command('mrconvert ' +
                fsl.findImage('T1.anat' + os.sep + 'T1_biascorr_brain_mask') +
                ' T1_mask.mif -datatype bit')
    run.command('mrconvert ' +
                fsl.findImage('T1.anat' + os.sep + 'T1_biascorr_brain') +
                ' T1_biascorr_brain.mif')
    file.delTemporary('T1.anat')

    # Step 7: Generate target images for T1->DWI registration
    run.command('dwiextract dwi.mif -bzero - | '
                'mrcalc - 0.0 -max - | '
                'mrmath - mean -axis 3 dwi_meanbzero.mif')
    run.command(
        'mrcalc 1 dwi_meanbzero.mif -div dwi_mask.mif -mult - | '
        'mrhistmatch - T1_biascorr_brain.mif dwi_pseudoT1.mif -mask_input dwi_mask.mif -mask_target T1_mask.mif'
    )
    run.command(
        'mrcalc 1 T1_biascorr_brain.mif -div T1_mask.mif -mult - | '
        'mrhistmatch - dwi_meanbzero.mif T1_pseudobzero.mif -mask_input T1_mask.mif -mask_target dwi_mask.mif'
    )

    # Step 8: Perform T1->DWI registration
    #         Note that two registrations are performed: Even though we have a symmetric registration,
    #         generation of the two histogram-matched images means that you will get slightly different
    #         answers depending on which synthesized image & original image you use.
    run.command(
        'mrregister T1_biascorr_brain.mif dwi_pseudoT1.mif -type rigid -mask1 T1_mask.mif -mask2 dwi_mask.mif -rigid rigid_T1_to_pseudoT1.txt'
    )
    file.delTemporary('T1_biascorr_brain.mif')
    run.command(
        'mrregister T1_pseudobzero.mif dwi_meanbzero.mif -type rigid -mask1 T1_mask.mif -mask2 dwi_mask.mif -rigid rigid_pseudobzero_to_bzero.txt'
    )
    file.delTemporary('dwi_meanbzero.mif')
    run.command(
        'transformcalc rigid_T1_to_pseudoT1.txt rigid_pseudobzero_to_bzero.txt average rigid_T1_to_dwi.txt'
    )
    file.delTemporary('rigid_T1_to_pseudoT1.txt')
    file.delTemporary('rigid_pseudobzero_to_bzero.txt')
    run.command(
        'mrtransform T1.mif T1_registered.mif -linear rigid_T1_to_dwi.txt')
    file.delTemporary('T1.mif')
    # Note: Since we're using a mask from fsl_anat (which crops the FoV), but using it as input to 5ttge fsl
    #   (which is receiving the raw T1), we need to resample in order to have the same dimensions between these two
    run.command(
        'mrtransform T1_mask.mif T1_mask_registered.mif -linear rigid_T1_to_dwi.txt -template T1_registered.mif -interp nearest'
    )
    file.delTemporary('T1_mask.mif')

    # Step 9: Generate 5TT image for ACT
    run.command(
        '5ttgen fsl T1_registered.mif 5TT.mif -mask T1_mask_registered.mif')
    file.delTemporary('T1_mask_registered.mif')

    # Step 10: Estimate response functions for spherical deconvolution
    run.command(
        'dwi2response dhollander dwi.mif response_wm.txt response_gm.txt response_csf.txt -mask dwi_mask.mif'
    )

    # Step 11: Determine whether we are working with single-shell or multi-shell data
    shells = [
        int(round(float(value)))
        for value in image.mrinfo('dwi.mif', 'shellvalues').strip().split()
    ]
    multishell = (len(shells) > 2)

    # Step 12: Perform spherical deconvolution
    #          Use a dilated mask for spherical deconvolution as a 'safety margin' -
    #          ACT should be responsible for stopping streamlines before they reach the edge of the DWI mask
    run.command('maskfilter dwi_mask.mif dilate dwi_mask_dilated.mif -npass 3')
    if multishell:
        run.command(
            'dwi2fod msmt_csd dwi.mif response_wm.txt FOD_WM.mif response_gm.txt FOD_GM.mif response_csf.txt FOD_CSF.mif '
            '-mask dwi_mask_dilated.mif -lmax 10,0,0')
        file.delTemporary('FOD_GM.mif')
        file.delTemporary('FOD_CSF.mif')
    else:
        # Still use the msmt_csd algorithm with single-shell data: Use hard non-negativity constraint
        # Also incorporate the CSF response to provide some fluid attenuation
        run.command(
            'dwi2fod msmt_csd dwi.mif response_wm.txt FOD_WM.mif response_csf.txt FOD_CSF.mif '
            '-mask dwi_mask_dilated.mif -lmax 10,0')
        file.delTemporary('FOD_CSF.mif')

    # Step 13: Generate the grey matter parcellation
    #          The necessary steps here will vary significantly depending on the parcellation scheme selected
    run.command(
        'mrconvert T1_registered.mif T1_registered.nii -stride +1,+2,+3')
    if app.args.parcellation == 'fs_2005' or app.args.parcellation == 'fs_2009':

        # Run FreeSurfer pipeline on this subject's T1 image
        run.command('recon-all -sd ' + app.tempDir +
                    ' -subjid freesurfer -i T1_registered.nii')
        run.command('recon-all -sd ' + app.tempDir +
                    ' -subjid freesurfer -all')

        # Grab the relevant parcellation image and target lookup table for conversion
        parc_image_path = os.path.join('freesurfer', 'mri')
        if app.args.parcellation == 'fs_2005':
            parc_image_path = os.path.join(parc_image_path, 'aparc+aseg.mgz')
        else:
            parc_image_path = os.path.join(parc_image_path,
                                           'aparc.a2009s+aseg.mgz')

        # Perform the index conversion
        run.command('labelconvert ' + parc_image_path + ' ' + parc_lut_file +
                    ' ' + mrtrix_lut_file + ' parc_init.mif')
        if app.cleanup:
            run.function(shutil.rmtree, 'freesurfer')

        # Fix the sub-cortical grey matter parcellations using FSL FIRST
        run.command('labelsgmfix parc_init.mif T1_registered.mif ' +
                    mrtrix_lut_file + ' parc.mif')
        file.delTemporary('parc_init.mif')

    elif app.args.parcellation == 'aal' or app.args.parcellation == 'aal2':

        # Can use MNI152 image provided with FSL for registration
        run.command(flirt_cmd + ' -ref ' + mni152_path +
                    ' -in T1_registered.nii -omat T1_to_MNI_FLIRT.mat -dof 12')
        run.command('transformconvert T1_to_MNI_FLIRT.mat T1_registered.nii ' +
                    mni152_path + ' flirt_import T1_to_MNI_MRtrix.mat')
        file.delTemporary('T1_to_MNI_FLIRT.mat')
        run.command(
            'transformcalc T1_to_MNI_MRtrix.mat invert MNI_to_T1_MRtrix.mat')
        file.delTemporary('T1_to_MNI_MRtrix.mat')
        run.command('mrtransform ' + parc_image_path +
                    ' AAL.mif -linear MNI_to_T1_MRtrix.mat '
                    '-template T1_registered.mif -interp nearest')
        file.delTemporary('MNI_to_T1_MRtrix.mat')
        run.command('labelconvert AAL.mif ' + parc_lut_file + ' ' +
                    mrtrix_lut_file + ' parc.mif')
        file.delTemporary('AAL.mif')

    else:
        app.error('Unknown parcellation scheme requested: ' +
                  app.args.parcellation)
    file.delTemporary('T1_registered.nii')

    # Step 14: Generate the tractogram
    # If not manually specified, determine the appropriate number of streamlines based on the number of nodes in the parcellation:
    #   mean edge weight of 1,000 streamlines
    # A smaller FOD amplitude threshold of 0.06 (default 0.1) is used for tracking due to the use of the msmt_csd
    #   algorithm, which imposes a hard rather than soft non-negativity constraint
    num_nodes = int(image.statistic('parc.mif', 'max'))
    num_streamlines = 1000 * num_nodes * num_nodes
    if app.args.streamlines:
        num_streamlines = app.args.streamlines
    run.command(
        'tckgen FOD_WM.mif tractogram.tck -act 5TT.mif -backtrack -crop_at_gmwmi -cutoff 0.06 -maxlength 250 -power 0.33 '
        '-select ' + str(num_streamlines) + ' -seed_dynamic FOD_WM.mif')

    # Step 15: Use SIFT2 to determine streamline weights
    fd_scale_gm_option = ''
    if not multishell:
        fd_scale_gm_option = ' -fd_scale_gm'
    run.command(
        'tcksift2 tractogram.tck FOD_WM.mif weights.csv -act 5TT.mif -out_mu mu.txt'
        + fd_scale_gm_option)

    # Step 16: Generate a TDI (to verify that SIFT2 has worked correctly)
    with open('mu.txt', 'r') as f:
        mu = float(f.read())
    run.command(
        'tckmap tractogram.tck -tck_weights_in weights.csv -template FOD_WM.mif -precise - | '
        'mrcalc - ' + str(mu) + ' -mult tdi.mif')

    # Step 17: Generate the connectome
    #          Only provide the standard density-weighted connectome for now
    run.command(
        'tck2connectome tractogram.tck parc.mif connectome.csv -tck_weights_in weights.csv'
    )
    file.delTemporary('weights.csv')

    # Move necessary files to output directory
    run.function(
        shutil.copy, 'connectome.csv',
        os.path.join(output_dir, 'connectome', label + '_connectome.csv'))
    run.command('mrconvert dwi.mif ' +
                os.path.join(output_dir, 'dwi', label + '_dwi.nii.gz') +
                ' -export_grad_fsl ' +
                os.path.join(output_dir, 'dwi', label + '_dwi.bvec') + ' ' +
                os.path.join(output_dir, 'dwi', label + '_dwi.bval') +
                ' -json_export ' +
                os.path.join(output_dir, 'dwi', label + '_dwi.json'))
    run.command('mrconvert tdi.mif ' +
                os.path.join(output_dir, 'dwi', label + '_tdi.nii.gz'))
    run.function(shutil.copy, 'mu.txt',
                 os.path.join(output_dir, 'connectome', label + '_mu.txt'))
    run.function(shutil.copy, 'response_wm.txt',
                 os.path.join(output_dir, 'dwi', label + '_response.txt'))

    # Manually wipe and zero the temp directory (since we might be processing more than one subject)
    os.chdir(cwd)
    if app.cleanup:
        app.console('Deleting temporary directory ' + app.tempDir)
        # Can't use run.function() here; it'll try to write to the log file that resides in the temp directory just deleted
        shutil.rmtree(app.tempDir)
    else:
        app.console('Contents of temporary directory kept, location: ' +
                    app.tempDir)
    app.tempDir = ''
Beispiel #4
0
def runGroup(output_dir):

    # Check presence of all required input files before proceeding
    # Pre-calculate paths of all files since many will be used in more than one location
    class subjectPaths(object):
        def __init__(self, label):
            self.in_dwi = os.path.join(output_dir, label, 'dwi',
                                       label + '_dwi.nii.gz')
            self.in_bvec = os.path.join(output_dir, label, 'dwi',
                                        label + '_dwi.bvec')
            self.in_bval = os.path.join(output_dir, label, 'dwi',
                                        label + '_dwi.bval')
            self.in_json = os.path.join(output_dir, label, 'dwi',
                                        label + '_dwi.json')
            self.in_rf = os.path.join(output_dir, label, 'dwi',
                                      label + '_response.txt')
            self.in_connectome = os.path.join(output_dir, label, 'connectome',
                                              label + '_connectome.csv')
            self.in_mu = os.path.join(output_dir, label, 'connectome',
                                      label + '_mu.txt')

            for entry in vars(self).values():
                if not os.path.exists(entry):
                    app.error(
                        'Unable to find critical subject data (expected location: '
                        + entry + ')')

            with open(self.in_mu, 'r') as f:
                self.mu = float(f.read())

            self.RF = []
            with open(self.in_rf, 'r') as f:
                for line in f:
                    self.RF.append([float(v) for v in line.split()])

            self.temp_mask = os.path.join('masks', label + '.mif')
            self.temp_fa = os.path.join('images', label + '.mif')
            self.temp_bzero = os.path.join('bzeros', label + '.mif')
            self.temp_warp = os.path.join('warps', label + '.mif')
            self.temp_voxels = os.path.join('voxels', label + '.mif')
            self.median_bzero = 0.0
            self.dwiintensitynorm_factor = 1.0
            self.RF_multiplier = 1.0
            self.global_multiplier = 1.0
            self.temp_connectome = os.path.join('connectomes', label + '.csv')
            self.out_scale_bzero = os.path.join(
                output_dir, label, 'connectome',
                label + '_scalefactor_bzero.csv')
            self.out_scale_RF = os.path.join(
                output_dir, label, 'connectome',
                label + '_scalefactor_response.csv')
            self.out_connectome = os.path.join(
                output_dir, label, 'connectome',
                label + '_connectome_scaled.csv')

            self.label = label

    subject_list = [
        'sub-' + sub_dir.split("-")[-1]
        for sub_dir in glob.glob(os.path.join(output_dir, 'sub-*'))
    ]
    if not subject_list:
        app.error(
            'No processed subject data found in output directory for group analysis'
        )
    subjects = []
    for label in subject_list:
        subjects.append(subjectPaths(label))

    app.makeTempDir()
    app.gotoTempDir()

    # First pass through subject data in group analysis:
    #   - Grab DWI data (written back from single-subject analysis back into BIDS format)
    #   - Generate mask and FA images to be used in populate template generation
    #   - Generate mean b=0 image for each subject for later use
    progress = app.progressBar('Importing and preparing subject data',
                               len(subjects))
    run.function(os.makedirs, 'bzeros')
    run.function(os.makedirs, 'images')
    run.function(os.makedirs, 'masks')
    for s in subjects:
        grad_import_option = ' -fslgrad ' + s.in_bvec + ' ' + s.in_bval
        run.command('dwi2mask ' + s.in_dwi + ' ' + s.temp_mask +
                    grad_import_option)
        run.command('dwi2tensor ' + s.in_dwi + ' - -mask ' + s.temp_mask +
                    grad_import_option + ' | tensor2metric - -fa ' + s.temp_fa)
        run.command('dwiextract ' + s.in_dwi + grad_import_option +
                    ' - -bzero | mrmath - mean ' + s.temp_bzero + ' -axis 3')
        progress.increment()
    progress.done()

    # First group-level calculation: Generate the population FA template
    app.console(
        'Generating population template for inter-subject intensity normalisation WM mask derivation'
    )
    run.command(
        'population_template images -mask_dir masks -warp_dir warps template.mif '
        '-type rigid_affine_nonlinear -rigid_scale 0.25,0.5,0.8,1.0 -affine_scale 0.7,0.8,1.0,1.0 '
        '-nl_scale 0.5,0.75,1.0,1.0,1.0 -nl_niter 5,5,5,5,5 -linear_no_pause')
    file.delTemporary('images')
    file.delTemporary('masks')

    # Second pass through subject data in group analysis:
    #   - Warp template FA image back to subject space & threshold to define a WM mask in subject space
    #   - Calculate the median subject b=0 value within this mask
    #   - Store this in a file, and contribute to calculation of the mean of these values across subjects
    #   - Contribute to the group average response function
    progress = app.progressBar(
        'Generating group-average response function and intensity normalisation factors',
        len(subjects) + 1)
    run.function(os.makedirs, 'voxels')
    sum_median_bzero = 0.0
    sum_RF = []
    for s in subjects:
        run.command('mrtransform template.mif -warp_full ' + s.temp_warp +
                    ' - -from 2 -template ' + s.temp_bzero + ' | '
                    'mrthreshold - ' + s.temp_voxels + ' -abs 0.4')
        s.median_bzero = float(
            image.statistic(s.temp_bzero, 'median', '-mask ' + s.temp_voxels))
        file.delTemporary(s.temp_bzero)
        file.delTemporary(s.temp_voxels)
        file.delTemporary(s.temp_warp)
        sum_median_bzero += s.median_bzero
        if sum_RF:
            sum_RF = [[a + b for a, b in zip(one, two)]
                      for one, two in zip(sum_RF, s.RF)]
        else:
            sum_RF = s.RF
        progress.increment()
    file.delTemporary('bzeros')
    file.delTemporary('voxels')
    file.delTemporary('warps')
    progress.done()

    # Second group-level calculation:
    #   - Calculate the mean of median b=0 values
    #   - Calculate the mean response function, and extract the l=0 values from it
    mean_median_bzero = sum_median_bzero / len(subjects)
    mean_RF = [[v / len(subjects) for v in line] for line in sum_RF]
    mean_RF_lzero = [line[0] for line in mean_RF]

    # Third pass through subject data in group analysis:
    #   - Scale the connectome strengths:
    #     - Multiply by SIFT proportionality coefficient mu
    #     - Multiply by (mean median b=0) / (subject median b=0)
    #     - Multiply by (subject RF size) / (mean RF size)
    #         (needs to account for multi-shell data)
    #   - Write the result to file
    progress = app.progressBar(
        'Applying normalisation scaling to subject connectomes', len(subjects))
    run.function(os.makedirs, 'connectomes')
    for s in subjects:
        RF_lzero = [line[0] for line in s.RF]
        s.RF_multiplier = 1.0
        for (mean, subj) in zip(mean_RF_lzero, RF_lzero):
            s.RF_multiplier = s.RF_multiplier * subj / mean
        # Don't want to be scaling connectome independently for differences in RF l=0 terms across all shells;
        #   use the geometric mean of the per-shell scale factors
        s.RF_multiplier = math.pow(s.RF_multiplier, 1.0 / len(mean_RF_lzero))

        s.bzero_multiplier = mean_median_bzero / s.median_bzero

        s.global_multiplier = s.mu * s.bzero_multiplier * s.RF_multiplier

        connectome = []
        with open(s.in_connectome, 'r') as f:
            for line in f:
                connectome.append([float(v) for v in line.split()])
        with open(s.temp_connectome, 'w') as f:
            for line in connectome:
                f.write(' '.join([str(v * s.global_multiplier)
                                  for v in line]) + '\n')
        progress.increment()
    progress.done()

    # Third group-level calculation: Generate the group mean connectome
    # For any higher-level analysis (e.g. NBSE, computing connectome global measures, etc.),
    #   trying to incorporate such analysis into this particular pipeline script is likely to
    #   overly complicate the interface, and not actually provide much in terms of
    #   convenience / reproducibility guarantees. The primary functionality of this group-level
    #   analysis is therefore to achieve inter-subject connection density normalisation; users
    #   then have the flexibility to subsequently analyse the data however they choose (ideally
    #   based on subject classification data provided with the BIDS-compliant dataset).
    progress = app.progressBar('Calculating group mean connectome',
                               len(subjects) + 1)
    mean_connectome = []
    for s in subjects:
        connectome = []
        with open(s.temp_connectome, 'r') as f:
            for line in f:
                connectome.append([float(v) for v in line.split()])
        if mean_connectome:
            mean_connectome = [[c1 + c2 for c1, c2 in zip(r1, r2)]
                               for r1, r2 in zip(mean_connectome, connectome)]
        else:
            mean_connectome = connectome
        progress.increment()

    mean_connectome = [[v / len(subjects) for v in row]
                       for row in mean_connectome]
    progress.done()

    # Write results of interest back to the output directory;
    #   both per-subject and group information
    progress = app.progressBar('Writing results to output directory',
                               len(subjects) + 2)
    for s in subjects:
        run.function(shutil.copyfile, s.temp_connectome, s.out_connectome)
        with open(s.out_scale_bzero, 'w') as f:
            f.write(str(s.bzero_multiplier))
        with open(s.out_scale_RF, 'w') as f:
            f.write(str(s.RF_multiplier))
        progress.increment()

    with open(os.path.join(output_dir, 'mean_response.txt'), 'w') as f:
        for row in mean_RF:
            f.write(' '.join([str(v) for v in row]) + '\n')
    progress.increment()
    with open(os.path.join(output_dir, 'mean_connectome.csv'), 'w') as f:
        for row in mean_connectome:
            f.write(' '.join([str(v) for v in row]) + '\n')
    progress.done()
Beispiel #5
0
def execute():
  import math, os, shutil
  from mrtrix3 import app, image, path, run



  # Get b-values and number of volumes per b-value.
  bvalues = [ int(round(float(x))) for x in image.headerField('dwi.mif', 'shells').split() ]
  bvolumes = [ int(x) for x in image.headerField('dwi.mif', 'shellcounts').split() ]
  app.console(str(len(bvalues)) + ' unique b-value(s) detected: ' + ','.join(map(str,bvalues)) + ' with ' + ','.join(map(str,bvolumes)) + ' volumes.')
  if len(bvalues) < 2:
    app.error('Need at least 2 unique b-values (including b=0).')


  # Get lmax information (if provided).
  sfwm_lmax = [ ]
  if app.args.lmax:
    sfwm_lmax = [ int(x.strip()) for x in app.args.lmax.split(',') ]
    if not len(sfwm_lmax) == len(bvalues):
      app.error('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:
        app.error('Values supplied to the -lmax option must be even.')
      if l<0:
        app.error('Values supplied to the -lmax option must be non-negative.')


  # Erode (brain) mask.
  if app.args.erode > 0:
    run.command('maskfilter mask.mif erode eroded_mask.mif -npass ' + str(app.args.erode))
  else:
    run.command('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):
    meanpath = 'mean_b' + str(b) + '.mif'
    run.command('dwiextract dwi.mif -shell ' + str(b) + ' - | mrmath - mean ' + meanpath + ' -axis 3')
    errpath = 'err_b' + str(b) + '.mif'
    run.command('mrcalc ' + meanpath + ' -finite ' + meanpath + ' 0 -if 0 -le ' + errpath + ' -datatype bit')
    errcmd += ' ' + errpath
    if i>0:
      errcmd += ' -add'
      sdmpath = 'sdm_b' + str(b) + '.mif'
      run.command('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'
  run.command(fullsdmcmd)
  run.command('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'
  run.command(errcmd)
  run.command('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.
  run.command('dwi2tensor dwi.mif - -mask safe_mask.mif | tensor2metric - -fa safe_fa.mif -vector safe_vecs.mif -modulate none -mask safe_mask.mif')
  run.command('mrcalc safe_mask.mif safe_fa.mif 0 -if ' + str(app.args.fa) + ' -gt crude_wm.mif -datatype bit')
  run.command('mrcalc crude_wm.mif 0 safe_mask.mif -if _crudenonwm.mif -datatype bit')

  # Crude GM versus CSF separation based on SDM.
  crudenonwmmedian = image.statistic('safe_sdm.mif', 'median', '_crudenonwm.mif')
  run.command('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')
  run.command('mrcalc crude_csf.mif 0 _crudenonwm.mif -if crude_gm.mif -datatype bit')


  # Refine WM: remove high SDM outliers.
  crudewmmedian = image.statistic('safe_sdm.mif', 'median', 'crude_wm.mif')
  run.command('mrcalc crude_wm.mif safe_sdm.mif 0 -if ' + str(crudewmmedian) + ' -gt _crudewmhigh.mif -datatype bit')
  run.command('mrcalc _crudewmhigh.mif 0 crude_wm.mif -if _crudewmlow.mif -datatype bit')
  crudewmQ1 = float(image.statistic('safe_sdm.mif', 'median', '_crudewmlow.mif'))
  crudewmQ3 = float(image.statistic('safe_sdm.mif', 'median', '_crudewmhigh.mif'))
  crudewmoutlthresh = crudewmQ3 + (crudewmQ3 - crudewmQ1)
  run.command('mrcalc crude_wm.mif safe_sdm.mif 0 -if ' + str(crudewmoutlthresh) + ' -gt _crudewmoutliers.mif -datatype bit')
  run.command('mrcalc _crudewmoutliers.mif 0 crude_wm.mif -if refined_wm.mif -datatype bit')

  # Refine GM: separate safer GM from partial volumed voxels.
  crudegmmedian = image.statistic('safe_sdm.mif', 'median', 'crude_gm.mif')
  run.command('mrcalc crude_gm.mif safe_sdm.mif 0 -if ' + str(crudegmmedian) + ' -gt _crudegmhigh.mif -datatype bit')
  run.command('mrcalc _crudegmhigh.mif 0 crude_gm.mif -if _crudegmlow.mif -datatype bit')
  run.command('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')
  run.command('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')
  run.command('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 = image.statistic('safe_sdm.mif', 'min', 'crude_csf.mif')
  run.command('mrcalc _crudewmoutliers.mif safe_sdm.mif 0 -if ' + str(crudecsfmin) + ' -gt 1 crude_csf.mif -if _crudecsfextra.mif -datatype bit')
  run.command('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(image.statistic('refined_wm.mif', 'count', 'refined_wm.mif'))
  voxsfwmcount = int(round(refwmcount * app.args.sfwm / 100.0))
  app.console('Running \'tournier\' algorithm to select ' + str(voxsfwmcount) + ' single-fibre WM voxels.')
  cleanopt = ''
  if not app._cleanup:
    cleanopt = ' -nocleanup'
  run.command('dwi2response tournier dwi.mif _respsfwmss.txt -sf_voxels ' + str(voxsfwmcount) + ' -iter_voxels ' + str(voxsfwmcount * 10) + ' -mask refined_wm.mif -voxels voxels_sfwm.mif -tempdir ' + app._tempDir + cleanopt)

  # Get final voxels for GM response function estimation from GM.
  refgmmedian = image.statistic('safe_sdm.mif', 'median', 'refined_gm.mif')
  run.command('mrcalc refined_gm.mif safe_sdm.mif 0 -if ' + str(refgmmedian) + ' -gt _refinedgmhigh.mif -datatype bit')
  run.command('mrcalc _refinedgmhigh.mif 0 refined_gm.mif -if _refinedgmlow.mif -datatype bit')
  refgmhighcount = float(image.statistic('_refinedgmhigh.mif', 'count', '_refinedgmhigh.mif'))
  refgmlowcount = float(image.statistic('_refinedgmlow.mif', 'count', '_refinedgmlow.mif'))
  voxgmhighcount = int(round(refgmhighcount * app.args.gm / 100.0))
  voxgmlowcount = int(round(refgmlowcount * app.args.gm / 100.0))
  run.command('mrcalc _refinedgmhigh.mif safe_sdm.mif 0 -if - | mrthreshold - - -bottom ' + str(voxgmhighcount) + ' -ignorezero | mrcalc _refinedgmhigh.mif - 0 -if _refinedgmhighselect.mif -datatype bit')
  run.command('mrcalc _refinedgmlow.mif safe_sdm.mif 0 -if - | mrthreshold - - -top ' + str(voxgmlowcount) + ' -ignorezero | mrcalc _refinedgmlow.mif - 0 -if _refinedgmlowselect.mif -datatype bit')
  run.command('mrcalc _refinedgmhighselect.mif 1 _refinedgmlowselect.mif -if voxels_gm.mif -datatype bit')

  # Get final voxels for CSF response function estimation from CSF.
  refcsfcount = float(image.statistic('refined_csf.mif', 'count', 'refined_csf.mif'))
  voxcsfcount = int(round(refcsfcount * app.args.csf / 100.0))
  run.command('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 = ' --> '
  app.console('Summary of voxel counts:')
  app.console('Mask: ' + str(int(image.statistic('mask.mif', 'count', 'mask.mif'))) + textarrow + str(int(image.statistic('eroded_mask.mif', 'count', 'eroded_mask.mif'))) + textarrow + str(int(image.statistic('safe_mask.mif', 'count', 'safe_mask.mif'))))
  app.console('WM: ' + str(int(image.statistic('crude_wm.mif', 'count', 'crude_wm.mif'))) + textarrow + str(int(image.statistic('refined_wm.mif', 'count', 'refined_wm.mif'))) + textarrow + str(int(image.statistic('voxels_sfwm.mif', 'count', 'voxels_sfwm.mif'))) + ' (SF)')
  app.console('GM: ' + str(int(image.statistic('crude_gm.mif', 'count', 'crude_gm.mif'))) + textarrow + str(int(image.statistic('refined_gm.mif', 'count', 'refined_gm.mif'))) + textarrow + str(int(image.statistic('voxels_gm.mif', 'count', 'voxels_gm.mif'))))
  app.console('CSF: ' + str(int(image.statistic('crude_csf.mif', 'count', 'crude_csf.mif'))) + textarrow + str(int(image.statistic('refined_csf.mif', 'count', 'refined_csf.mif'))) + textarrow + str(int(image.statistic('voxels_csf.mif', 'count', 'voxels_csf.mif'))))


  # Generate single-fibre WM, GM and CSF responses
  bvalues_option = ' -shell ' + ','.join(map(str,bvalues))
  sfwm_lmax_option = ''
  if sfwm_lmax:
    sfwm_lmax_option = ' -lmax ' + ','.join(map(str,sfwm_lmax))
  run.command('amp2response dwi.mif voxels_sfwm.mif safe_vecs.mif response_sfwm.txt' + bvalues_option + sfwm_lmax_option)
  run.command('amp2response dwi.mif voxels_gm.mif safe_vecs.mif response_gm.txt' + bvalues_option + ' -isotropic')
  run.command('amp2response dwi.mif voxels_csf.mif safe_vecs.mif response_csf.txt' + bvalues_option + ' -isotropic')
  run.function(shutil.copyfile, 'response_sfwm.txt', path.fromUser(app.args.out_sfwm, False))
  run.function(shutil.copyfile, 'response_gm.txt', path.fromUser(app.args.out_gm, False))
  run.function(shutil.copyfile, 'response_csf.txt', path.fromUser(app.args.out_csf, False))


  # Generate 4D binary images with voxel selections at major stages in algorithm (RGB as in MSMT-CSD paper).
  run.command('mrcat crude_csf.mif crude_gm.mif crude_wm.mif crude.mif -axis 3')
  run.command('mrcat refined_csf.mif refined_gm.mif refined_wm.mif refined.mif -axis 3')
  run.command('mrcat voxels_csf.mif voxels_gm.mif voxels_sfwm.mif voxels.mif -axis 3')
Beispiel #6
0
def execute():
  import math, os, shutil
  from mrtrix3 import app, file, image, path, run

  lmax_option = ''
  if app.args.lmax:
    lmax_option = ' -lmax ' + app.args.lmax

  convergence_change = 0.01 * app.args.convergence

  for iteration in range(0, 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'
      # TODO This can be changed once #71 is implemented (mrstats statistics across volumes)
      volume_means = [float(x) for x in image.statistic('dwi.mif', 'mean', 'mask.mif').split()]
      mean = sum(volume_means) / float(len(volume_means))
      volume_stds = [float(x) for x in image.statistic('dwi.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
    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')
    file.delTempFile(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')
    file.delTempFile(prefix + 'amps.mif')
    run.command('fixel2voxel ' + prefix + 'fixel/directions.mif split_dir ' + prefix + 'all_dirs.mif')
    file.delTempFolder(prefix + 'fixel')
    run.command('mrconvert ' + prefix + 'all_dirs.mif ' + prefix + 'first_dir.mif -coord 3 0:2')
    file.delTempFile(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')
    file.delTempFile(prefix + 'first_peaks.mif')
    file.delTempFile(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')
    file.delTempFile(prefix + 'peak_ratio.mif')
    # Make sure image isn't empty
    SF_voxel_count = int(image.statistic(prefix + 'SF.mif', 'count', prefix + 'SF.mif'))
    if not SF_voxel_count:
      app.error('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)
    file.delTempFile(prefix + 'first_dir.mif')

    # Detect convergence
    # Look for a change > some percentage - don't bother looking at the masks
    if iteration > 0:
      with open(RF_in_path, 'r') as old_RF_file:
        old_RF = [ float(x) for x in old_RF_file.read().split() ]
      with open(prefix + 'RF.txt', 'r') as new_RF_file:
        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:
        app.console('Exiting at iteration ' + str(iteration) + ' with ' + str(SF_voxel_count) + ' SF voxels due to unchanged response function coefficients')
        run.function(shutil.copyfile, prefix + 'RF.txt', 'response.txt')
        run.function(shutil.copyfile, prefix + 'SF.mif', 'voxels.mif')
        break

    file.delTempFile(RF_in_path)
    file.delTempFile(mask_in_path)
  # 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'):
    app.console('Exiting after maximum ' + str(app.args.max_iters-1) + ' 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.fromUser(app.args.output, False))
Beispiel #7
0
def execute():
    import math, os, shutil
    from mrtrix3 import app, image, path, run

    # Get b-values and number of volumes per b-value.
    bvalues = [
        int(round(float(x)))
        for x in image.headerField('dwi.mif', 'shells').split()
    ]
    bvolumes = [
        int(x) for x in image.headerField('dwi.mif', 'shellcounts').split()
    ]
    app.console(
        str(len(bvalues)) + ' unique b-value(s) detected: ' +
        ','.join(map(str, bvalues)) + ' with ' + ','.join(map(str, bvolumes)) +
        ' volumes.')
    if len(bvalues) < 2:
        app.error('Need at least 2 unique b-values (including b=0).')

    # Get lmax information (if provided).
    sfwm_lmax = []
    if app.args.lmax:
        sfwm_lmax = [int(x.strip()) for x in app.args.lmax.split(',')]
        if not len(sfwm_lmax) == len(bvalues):
            app.error('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:
                app.error('Values supplied to the -lmax option must be even.')
            if l < 0:
                app.error(
                    'Values supplied to the -lmax option must be non-negative.'
                )

    # Erode (brain) mask.
    if app.args.erode > 0:
        run.command('maskfilter mask.mif erode eroded_mask.mif -npass ' +
                    str(app.args.erode))
    else:
        run.command('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):
        meanpath = 'mean_b' + str(b) + '.mif'
        run.command('dwiextract dwi.mif -shell ' + str(b) +
                    ' - | mrmath - mean ' + meanpath + ' -axis 3')
        errpath = 'err_b' + str(b) + '.mif'
        run.command('mrcalc ' + meanpath + ' -finite ' + meanpath +
                    ' 0 -if 0 -le ' + errpath + ' -datatype bit')
        errcmd += ' ' + errpath
        if i > 0:
            errcmd += ' -add'
            sdmpath = 'sdm_b' + str(b) + '.mif'
            run.command('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'
    run.command(fullsdmcmd)
    run.command(
        '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'
    run.command(errcmd)
    run.command('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.
    run.command(
        'dwi2tensor dwi.mif - -mask safe_mask.mif | tensor2metric - -fa safe_fa.mif -vector safe_vecs.mif -modulate none -mask safe_mask.mif'
    )
    run.command('mrcalc safe_mask.mif safe_fa.mif 0 -if ' + str(app.args.fa) +
                ' -gt crude_wm.mif -datatype bit')
    run.command(
        'mrcalc crude_wm.mif 0 safe_mask.mif -if _crudenonwm.mif -datatype bit'
    )

    # Crude GM versus CSF separation based on SDM.
    crudenonwmmedian = image.statistic('safe_sdm.mif', 'median',
                                       '_crudenonwm.mif')
    run.command(
        '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'
    )
    run.command(
        'mrcalc crude_csf.mif 0 _crudenonwm.mif -if crude_gm.mif -datatype bit'
    )

    # Refine WM: remove high SDM outliers.
    crudewmmedian = image.statistic('safe_sdm.mif', 'median', 'crude_wm.mif')
    run.command('mrcalc crude_wm.mif safe_sdm.mif 0 -if ' +
                str(crudewmmedian) + ' -gt _crudewmhigh.mif -datatype bit')
    run.command(
        'mrcalc _crudewmhigh.mif 0 crude_wm.mif -if _crudewmlow.mif -datatype bit'
    )
    crudewmQ1 = float(
        image.statistic('safe_sdm.mif', 'median', '_crudewmlow.mif'))
    crudewmQ3 = float(
        image.statistic('safe_sdm.mif', 'median', '_crudewmhigh.mif'))
    crudewmoutlthresh = crudewmQ3 + (crudewmQ3 - crudewmQ1)
    run.command('mrcalc crude_wm.mif safe_sdm.mif 0 -if ' +
                str(crudewmoutlthresh) +
                ' -gt _crudewmoutliers.mif -datatype bit')
    run.command(
        'mrcalc _crudewmoutliers.mif 0 crude_wm.mif -if refined_wm.mif -datatype bit'
    )

    # Refine GM: separate safer GM from partial volumed voxels.
    crudegmmedian = image.statistic('safe_sdm.mif', 'median', 'crude_gm.mif')
    run.command('mrcalc crude_gm.mif safe_sdm.mif 0 -if ' +
                str(crudegmmedian) + ' -gt _crudegmhigh.mif -datatype bit')
    run.command(
        'mrcalc _crudegmhigh.mif 0 crude_gm.mif -if _crudegmlow.mif -datatype bit'
    )
    run.command(
        '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'
    )
    run.command(
        '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'
    )
    run.command(
        '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 = image.statistic('safe_sdm.mif', 'min', 'crude_csf.mif')
    run.command('mrcalc _crudewmoutliers.mif safe_sdm.mif 0 -if ' +
                str(crudecsfmin) +
                ' -gt 1 crude_csf.mif -if _crudecsfextra.mif -datatype bit')
    run.command(
        '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(
        image.statistic('refined_wm.mif', 'count', 'refined_wm.mif'))
    voxsfwmcount = int(round(refwmcount * app.args.sfwm / 100.0))
    app.console('Running \'tournier\' algorithm to select ' +
                str(voxsfwmcount) + ' single-fibre WM voxels.')
    cleanopt = ''
    if not app._cleanup:
        cleanopt = ' -nocleanup'
    run.command('dwi2response tournier dwi.mif _respsfwmss.txt -sf_voxels ' +
                str(voxsfwmcount) + ' -iter_voxels ' + str(voxsfwmcount * 10) +
                ' -mask refined_wm.mif -voxels voxels_sfwm.mif -tempdir ' +
                app._tempDir + cleanopt)

    # Get final voxels for GM response function estimation from GM.
    refgmmedian = image.statistic('safe_sdm.mif', 'median', 'refined_gm.mif')
    run.command('mrcalc refined_gm.mif safe_sdm.mif 0 -if ' +
                str(refgmmedian) + ' -gt _refinedgmhigh.mif -datatype bit')
    run.command(
        'mrcalc _refinedgmhigh.mif 0 refined_gm.mif -if _refinedgmlow.mif -datatype bit'
    )
    refgmhighcount = float(
        image.statistic('_refinedgmhigh.mif', 'count', '_refinedgmhigh.mif'))
    refgmlowcount = float(
        image.statistic('_refinedgmlow.mif', 'count', '_refinedgmlow.mif'))
    voxgmhighcount = int(round(refgmhighcount * app.args.gm / 100.0))
    voxgmlowcount = int(round(refgmlowcount * app.args.gm / 100.0))
    run.command(
        'mrcalc _refinedgmhigh.mif safe_sdm.mif 0 -if - | mrthreshold - - -bottom '
        + str(voxgmhighcount) +
        ' -ignorezero | mrcalc _refinedgmhigh.mif - 0 -if _refinedgmhighselect.mif -datatype bit'
    )
    run.command(
        'mrcalc _refinedgmlow.mif safe_sdm.mif 0 -if - | mrthreshold - - -top '
        + str(voxgmlowcount) +
        ' -ignorezero | mrcalc _refinedgmlow.mif - 0 -if _refinedgmlowselect.mif -datatype bit'
    )
    run.command(
        'mrcalc _refinedgmhighselect.mif 1 _refinedgmlowselect.mif -if voxels_gm.mif -datatype bit'
    )

    # Get final voxels for CSF response function estimation from CSF.
    refcsfcount = float(
        image.statistic('refined_csf.mif', 'count', 'refined_csf.mif'))
    voxcsfcount = int(round(refcsfcount * app.args.csf / 100.0))
    run.command(
        '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 = ' --> '
    app.console('Summary of voxel counts:')
    app.console(
        'Mask: ' + str(int(image.statistic('mask.mif', 'count', 'mask.mif'))) +
        textarrow +
        str(int(image.statistic('eroded_mask.mif', 'count',
                                'eroded_mask.mif'))) + textarrow +
        str(int(image.statistic('safe_mask.mif', 'count', 'safe_mask.mif'))))
    app.console(
        'WM: ' +
        str(int(image.statistic('crude_wm.mif', 'count', 'crude_wm.mif'))) +
        textarrow +
        str(int(image.statistic('refined_wm.mif', 'count',
                                'refined_wm.mif'))) + textarrow +
        str(int(image.statistic('voxels_sfwm.mif', 'count',
                                'voxels_sfwm.mif'))) + ' (SF)')
    app.console(
        'GM: ' +
        str(int(image.statistic('crude_gm.mif', 'count', 'crude_gm.mif'))) +
        textarrow +
        str(int(image.statistic('refined_gm.mif', 'count',
                                'refined_gm.mif'))) + textarrow +
        str(int(image.statistic('voxels_gm.mif', 'count', 'voxels_gm.mif'))))
    app.console(
        'CSF: ' +
        str(int(image.statistic('crude_csf.mif', 'count', 'crude_csf.mif'))) +
        textarrow +
        str(int(image.statistic('refined_csf.mif', 'count',
                                'refined_csf.mif'))) + textarrow +
        str(int(image.statistic('voxels_csf.mif', 'count', 'voxels_csf.mif'))))

    # Generate single-fibre WM, GM and CSF responses
    bvalues_option = ' -shell ' + ','.join(map(str, bvalues))
    sfwm_lmax_option = ''
    if sfwm_lmax:
        sfwm_lmax_option = ' -lmax ' + ','.join(map(str, sfwm_lmax))
    run.command(
        'amp2response dwi.mif voxels_sfwm.mif safe_vecs.mif response_sfwm.txt'
        + bvalues_option + sfwm_lmax_option)
    run.command(
        'amp2response dwi.mif voxels_gm.mif safe_vecs.mif response_gm.txt' +
        bvalues_option + ' -isotropic')
    run.command(
        'amp2response dwi.mif voxels_csf.mif safe_vecs.mif response_csf.txt' +
        bvalues_option + ' -isotropic')
    run.function(shutil.copyfile, 'response_sfwm.txt',
                 path.fromUser(app.args.out_sfwm, False))
    run.function(shutil.copyfile, 'response_gm.txt',
                 path.fromUser(app.args.out_gm, False))
    run.function(shutil.copyfile, 'response_csf.txt',
                 path.fromUser(app.args.out_csf, False))

    # Generate 4D binary images with voxel selections at major stages in algorithm (RGB as in MSMT-CSD paper).
    run.command(
        'mrcat crude_csf.mif crude_gm.mif crude_wm.mif crude.mif -axis 3')
    run.command(
        'mrcat refined_csf.mif refined_gm.mif refined_wm.mif refined.mif -axis 3'
    )
    run.command(
        'mrcat voxels_csf.mif voxels_gm.mif voxels_sfwm.mif voxels.mif -axis 3'
    )
Beispiel #8
0
def execute():  #pylint: disable=unused-variable
    import math
    from mrtrix3 import CONFIG, app, image, matrix, MRtrixError, path, run

    bzero_threshold = float(
        CONFIG['BZeroThreshold']) if 'BZeroThreshold' in CONFIG else 10.0

    # CHECK INPUTS AND OPTIONS
    app.console('-------')

    # Get b-values and number of volumes per b-value.
    bvalues = [
        int(round(float(x)))
        for x in image.mrinfo('dwi.mif', 'shell_bvalues').split()
    ]
    bvolumes = [int(x) for x in image.mrinfo('dwi.mif', 'shell_sizes').split()]
    app.console(
        str(len(bvalues)) + ' unique b-value(s) detected: ' +
        ','.join(map(str, bvalues)) + ' with ' + ','.join(map(str, bvolumes)) +
        ' volumes')
    if len(bvalues) < 2:
        raise MRtrixError('Need at least 2 unique b-values (including b=0).')
    bvalues_option = ' -shells ' + ','.join(map(str, bvalues))

    # Get lmax information (if provided).
    sfwm_lmax = []
    if app.ARGS.lmax:
        sfwm_lmax = [int(x.strip()) for x in app.ARGS.lmax.split(',')]
        if not len(sfwm_lmax) == len(bvalues):
            raise MRtrixError('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 sfl in sfwm_lmax:
            if sfl % 2:
                raise MRtrixError(
                    'Values supplied to the -lmax option must be even.')
            if sfl < 0:
                raise MRtrixError(
                    'Values supplied to the -lmax option must be non-negative.'
                )
    sfwm_lmax_option = ''
    if sfwm_lmax:
        sfwm_lmax_option = ' -lmax ' + ','.join(map(str, sfwm_lmax))

    # PREPARATION
    app.console('-------')
    app.console('Preparation:')

    # Erode (brain) mask.
    if app.ARGS.erode > 0:
        app.console('* Eroding brain mask by ' + str(app.ARGS.erode) +
                    ' pass(es)...')
        run.command('maskfilter mask.mif erode eroded_mask.mif -npass ' +
                    str(app.ARGS.erode),
                    show=False)
    else:
        app.console('Not eroding brain mask.')
        run.command('mrconvert mask.mif eroded_mask.mif -datatype bit',
                    show=False)
    statmaskcount = image.statistic('mask.mif', 'count', '-mask mask.mif')
    statemaskcount = image.statistic('eroded_mask.mif', 'count',
                                     '-mask eroded_mask.mif')
    app.console('  [ mask: ' + str(statmaskcount) + ' -> ' +
                str(statemaskcount) + ' ]')

    # Get volumes, compute mean signal and SDM per b-value; compute overall SDM; get rid of erroneous values.
    app.console('* Computing signal decay metric (SDM):')
    totvolumes = 0
    fullsdmcmd = 'mrcalc'
    errcmd = 'mrcalc'
    zeropath = 'mean_b' + str(bvalues[0]) + '.mif'
    for ibv, bval in enumerate(bvalues):
        app.console(' * b=' + str(bval) + '...')
        meanpath = 'mean_b' + str(bval) + '.mif'
        run.command('dwiextract dwi.mif -shells ' + str(bval) +
                    ' - | mrmath - mean ' + meanpath + ' -axis 3',
                    show=False)
        errpath = 'err_b' + str(bval) + '.mif'
        run.command('mrcalc ' + meanpath + ' -finite ' + meanpath +
                    ' 0 -if 0 -le ' + errpath + ' -datatype bit',
                    show=False)
        errcmd += ' ' + errpath
        if ibv > 0:
            errcmd += ' -add'
            sdmpath = 'sdm_b' + str(bval) + '.mif'
            run.command('mrcalc ' + zeropath + ' ' + meanpath +
                        ' -divide -log ' + sdmpath,
                        show=False)
            totvolumes += bvolumes[ibv]
            fullsdmcmd += ' ' + sdmpath + ' ' + str(bvolumes[ibv]) + ' -mult'
            if ibv > 1:
                fullsdmcmd += ' -add'
    fullsdmcmd += ' ' + str(totvolumes) + ' -divide full_sdm.mif'
    run.command(fullsdmcmd, show=False)
    app.console('* Removing erroneous voxels from mask and correcting SDM...')
    run.command(
        'mrcalc full_sdm.mif -finite full_sdm.mif 0 -if 0 -le err_sdm.mif -datatype bit',
        show=False)
    errcmd += ' err_sdm.mif -add 0 eroded_mask.mif -if safe_mask.mif -datatype bit'
    run.command(errcmd, show=False)
    run.command('mrcalc safe_mask.mif full_sdm.mif 0 -if 10 -min safe_sdm.mif',
                show=False)
    statsmaskcount = image.statistic('safe_mask.mif', 'count',
                                     '-mask safe_mask.mif')
    app.console('  [ mask: ' + str(statemaskcount) + ' -> ' +
                str(statsmaskcount) + ' ]')

    # CRUDE SEGMENTATION
    app.console('-------')
    app.console('Crude segmentation:')

    # Compute FA and principal eigenvectors; crude WM versus GM-CSF separation based on FA.
    app.console('* Crude WM versus GM-CSF separation (at FA=' +
                str(app.ARGS.fa) + ')...')
    run.command(
        'dwi2tensor dwi.mif - -mask safe_mask.mif | tensor2metric - -fa safe_fa.mif -vector safe_vecs.mif -modulate none -mask safe_mask.mif',
        show=False)
    run.command('mrcalc safe_mask.mif safe_fa.mif 0 -if ' + str(app.ARGS.fa) +
                ' -gt crude_wm.mif -datatype bit',
                show=False)
    run.command(
        'mrcalc crude_wm.mif 0 safe_mask.mif -if _crudenonwm.mif -datatype bit',
        show=False)
    statcrudewmcount = image.statistic('crude_wm.mif', 'count',
                                       '-mask crude_wm.mif')
    statcrudenonwmcount = image.statistic('_crudenonwm.mif', 'count',
                                          '-mask _crudenonwm.mif')
    app.console('  [ ' + str(statsmaskcount) + ' -> ' + str(statcrudewmcount) +
                ' (WM) & ' + str(statcrudenonwmcount) + ' (GM-CSF) ]')

    # Crude GM versus CSF separation based on SDM.
    app.console('* Crude GM versus CSF separation...')
    crudenonwmmedian = image.statistic('safe_sdm.mif', 'median',
                                       '-mask _crudenonwm.mif')
    run.command(
        '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',
        show=False)
    run.command(
        'mrcalc crude_csf.mif 0 _crudenonwm.mif -if crude_gm.mif -datatype bit',
        show=False)
    statcrudegmcount = image.statistic('crude_gm.mif', 'count',
                                       '-mask crude_gm.mif')
    statcrudecsfcount = image.statistic('crude_csf.mif', 'count',
                                        '-mask crude_csf.mif')
    app.console('  [ ' + str(statcrudenonwmcount) + ' -> ' +
                str(statcrudegmcount) + ' (GM) & ' + str(statcrudecsfcount) +
                ' (CSF) ]')

    # REFINED SEGMENTATION
    app.console('-------')
    app.console('Refined segmentation:')

    # Refine WM: remove high SDM outliers.
    app.console('* Refining WM...')
    crudewmmedian = image.statistic('safe_sdm.mif', 'median',
                                    '-mask crude_wm.mif')
    run.command('mrcalc crude_wm.mif safe_sdm.mif ' + str(crudewmmedian) +
                ' -subtract -abs 0 -if _crudewm_sdmad.mif',
                show=False)
    crudewmmad = image.statistic('_crudewm_sdmad.mif', 'median',
                                 '-mask crude_wm.mif')
    crudewmoutlthresh = crudewmmedian + (1.4826 * crudewmmad * 2.0)
    run.command('mrcalc crude_wm.mif safe_sdm.mif 0 -if ' +
                str(crudewmoutlthresh) +
                ' -gt _crudewmoutliers.mif -datatype bit',
                show=False)
    run.command(
        'mrcalc _crudewmoutliers.mif 0 crude_wm.mif -if refined_wm.mif -datatype bit',
        show=False)
    statrefwmcount = image.statistic('refined_wm.mif', 'count',
                                     '-mask refined_wm.mif')
    app.console('  [ WM: ' + str(statcrudewmcount) + ' -> ' +
                str(statrefwmcount) + ' ]')

    # Refine GM: separate safer GM from partial volumed voxels.
    app.console('* Refining GM...')
    crudegmmedian = image.statistic('safe_sdm.mif', 'median',
                                    '-mask crude_gm.mif')
    run.command('mrcalc crude_gm.mif safe_sdm.mif 0 -if ' +
                str(crudegmmedian) + ' -gt _crudegmhigh.mif -datatype bit',
                show=False)
    run.command(
        'mrcalc _crudegmhigh.mif 0 crude_gm.mif -if _crudegmlow.mif -datatype bit',
        show=False)
    run.command(
        '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',
        show=False)
    run.command(
        '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',
        show=False)
    run.command(
        'mrcalc _crudegmhighselect.mif 1 _crudegmlowselect.mif -if refined_gm.mif -datatype bit',
        show=False)
    statrefgmcount = image.statistic('refined_gm.mif', 'count',
                                     '-mask refined_gm.mif')
    app.console('  [ GM: ' + str(statcrudegmcount) + ' -> ' +
                str(statrefgmcount) + ' ]')

    # Refine CSF: recover lost CSF from crude WM SDM outliers, separate safer CSF from partial volumed voxels.
    app.console('* Refining CSF...')
    crudecsfmin = image.statistic('safe_sdm.mif', 'min', '-mask crude_csf.mif')
    run.command('mrcalc _crudewmoutliers.mif safe_sdm.mif 0 -if ' +
                str(crudecsfmin) +
                ' -gt 1 crude_csf.mif -if _crudecsfextra.mif -datatype bit',
                show=False)
    run.command(
        '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',
        show=False)
    statrefcsfcount = image.statistic('refined_csf.mif', 'count',
                                      '-mask refined_csf.mif')
    app.console('  [ CSF: ' + str(statcrudecsfcount) + ' -> ' +
                str(statrefcsfcount) + ' ]')

    # FINAL VOXEL SELECTION AND RESPONSE FUNCTION ESTIMATION
    app.console('-------')
    app.console('Final voxel selection and response function estimation:')

    # Get final voxels for CSF response function estimation from refined CSF.
    app.console('* CSF:')
    app.console(' * Selecting final voxels (' + str(app.ARGS.csf) +
                '% of refined CSF)...')
    voxcsfcount = int(round(statrefcsfcount * app.ARGS.csf / 100.0))
    run.command(
        'mrcalc refined_csf.mif safe_sdm.mif 0 -if - | mrthreshold - - -top ' +
        str(voxcsfcount) +
        ' -ignorezero | mrcalc refined_csf.mif - 0 -if - -datatype bit | mrconvert - voxels_csf.mif -axes 0,1,2',
        show=False)
    statvoxcsfcount = image.statistic('voxels_csf.mif', 'count',
                                      '-mask voxels_csf.mif')
    app.console('   [ CSF: ' + str(statrefcsfcount) + ' -> ' +
                str(statvoxcsfcount) + ' ]')
    # Estimate CSF response function
    app.console(' * Estimating response function...')
    run.command(
        'amp2response dwi.mif voxels_csf.mif safe_vecs.mif response_csf.txt' +
        bvalues_option + ' -isotropic',
        show=False)

    # Get final voxels for GM response function estimation from refined GM.
    app.console('* GM:')
    app.console(' * Selecting final voxels (' + str(app.ARGS.gm) +
                '% of refined GM)...')
    voxgmcount = int(round(statrefgmcount * app.ARGS.gm / 100.0))
    refgmmedian = image.statistic('safe_sdm.mif', 'median',
                                  '-mask refined_gm.mif')
    run.command(
        'mrcalc refined_gm.mif safe_sdm.mif ' + str(refgmmedian) +
        ' -subtract -abs 1 -add 0 -if - | mrthreshold - - -bottom ' +
        str(voxgmcount) +
        ' -ignorezero | mrcalc refined_gm.mif - 0 -if - -datatype bit | mrconvert - voxels_gm.mif -axes 0,1,2',
        show=False)
    statvoxgmcount = image.statistic('voxels_gm.mif', 'count',
                                     '-mask voxels_gm.mif')
    app.console('   [ GM: ' + str(statrefgmcount) + ' -> ' +
                str(statvoxgmcount) + ' ]')
    # Estimate GM response function
    app.console(' * Estimating response function...')
    run.command(
        'amp2response dwi.mif voxels_gm.mif safe_vecs.mif response_gm.txt' +
        bvalues_option + ' -isotropic',
        show=False)

    # Get final voxels for single-fibre WM response function estimation from refined WM.
    app.console('* single-fibre WM:')
    app.console(' * Selecting final voxels (' + str(app.ARGS.sfwm) +
                '% of refined WM)...')
    voxsfwmcount = int(round(statrefwmcount * app.ARGS.sfwm / 100.0))
    run.command('mrmath dwi.mif mean mean_sig.mif -axis 3', show=False)
    refwmcoef = image.statistic('mean_sig.mif', 'median',
                                '-mask refined_wm.mif') * math.sqrt(
                                    4.0 * math.pi)
    if sfwm_lmax:
        isiso = [lm == 0 for lm in sfwm_lmax]
    else:
        isiso = [bv < bzero_threshold for bv in bvalues]
    with open('ewmrf.txt', 'w') as ewr:
        for iis in isiso:
            if iis:
                ewr.write("%s 0 0 0\n" % refwmcoef)
            else:
                ewr.write("%s -%s %s -%s\n" %
                          (refwmcoef, refwmcoef, refwmcoef, refwmcoef))
    run.command(
        'dwi2fod msmt_csd dwi.mif ewmrf.txt abs_ewm2.mif response_csf.txt abs_csf2.mif -mask refined_wm.mif -lmax 2,0'
        + bvalues_option,
        show=False)
    run.command(
        'mrconvert abs_ewm2.mif - -coord 3 0 | mrcalc - abs_csf2.mif -add abs_sum2.mif',
        show=False)
    run.command(
        'sh2peaks abs_ewm2.mif - -num 1 -mask refined_wm.mif | peaks2amp - - | mrcalc - abs_sum2.mif -divide - | mrconvert - metric_sfwm2.mif -coord 3 0 -axes 0,1,2',
        show=False)
    run.command(
        'mrcalc refined_wm.mif metric_sfwm2.mif 0 -if - | mrthreshold - - -top '
        + str(voxsfwmcount * 2) +
        ' -ignorezero | mrcalc refined_wm.mif - 0 -if - -datatype bit | mrconvert - refined_sfwm.mif -axes 0,1,2',
        show=False)
    run.command(
        'dwi2fod msmt_csd dwi.mif ewmrf.txt abs_ewm6.mif response_csf.txt abs_csf6.mif -mask refined_sfwm.mif -lmax 6,0'
        + bvalues_option,
        show=False)
    run.command(
        'mrconvert abs_ewm6.mif - -coord 3 0 | mrcalc - abs_csf6.mif -add abs_sum6.mif',
        show=False)
    run.command(
        'sh2peaks abs_ewm6.mif abs_peak_ewm6.mif -num 1 -mask refined_sfwm.mif',
        show=False)
    run.command(
        'peaks2amp abs_peak_ewm6.mif - | mrcalc - abs_sum6.mif -divide - | mrconvert - metric_sfwm6.mif -coord 3 0 -axes 0,1,2',
        show=False)
    run.command(
        'mrcalc refined_sfwm.mif metric_sfwm6.mif 0 -if - | mrthreshold - - -top '
        + str(voxsfwmcount) +
        ' -ignorezero | mrcalc refined_sfwm.mif - 0 -if - -datatype bit | mrconvert - voxels_sfwm.mif -axes 0,1,2',
        show=False)
    statvoxsfwmcount = image.statistic('voxels_sfwm.mif', 'count',
                                       '-mask voxels_sfwm.mif')
    app.console('   [ WM: ' + str(statrefwmcount) + ' -> ' +
                str(statvoxsfwmcount) + ' (single-fibre) ]')
    # Estimate SF WM response function
    app.console(' * Estimating response function...')
    run.command(
        'amp2response dwi.mif voxels_sfwm.mif abs_peak_ewm6.mif response_sfwm.txt'
        + bvalues_option + sfwm_lmax_option,
        show=False)

    # OUTPUT AND SUMMARY
    app.console('-------')
    app.console('Generating outputs...')

    # Generate 4D binary images with voxel selections at major stages in algorithm (RGB: WM=blue, GM=green, CSF=red).
    run.command(
        'mrcat crude_csf.mif crude_gm.mif crude_wm.mif check_crude.mif -axis 3',
        show=False)
    run.command(
        'mrcat refined_csf.mif refined_gm.mif refined_wm.mif check_refined.mif -axis 3',
        show=False)
    run.command(
        'mrcat voxels_csf.mif voxels_gm.mif voxels_sfwm.mif check_voxels.mif -axis 3',
        show=False)

    # Save results to output files
    bvalhdr = {'b-values': ','.join(map(str, bvalues))}
    matrix.save_matrix(path.from_user(app.ARGS.out_sfwm, False),
                       matrix.load_matrix('response_sfwm.txt'),
                       header=bvalhdr,
                       fmt='%.15g',
                       footer={})
    matrix.save_matrix(path.from_user(app.ARGS.out_gm, False),
                       matrix.load_matrix('response_gm.txt'),
                       header=bvalhdr,
                       fmt='%.15g',
                       footer={})
    matrix.save_matrix(path.from_user(app.ARGS.out_csf, False),
                       matrix.load_matrix('response_csf.txt'),
                       header=bvalhdr,
                       fmt='%.15g',
                       footer={})
    if app.ARGS.voxels:
        run.command('mrconvert check_voxels.mif ' +
                    path.from_user(app.ARGS.voxels),
                    mrconvert_keyval=path.from_user(app.ARGS.input),
                    force=app.FORCE_OVERWRITE,
                    show=False)
    app.console('-------')
Beispiel #9
0
def execute():  #pylint: disable=unused-variable
    import os, shutil
    from mrtrix3 import app, image, MRtrixError, path, run

    # 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
    stderr_5ttcheck = run.command('5ttcheck 5tt.mif').stderr
    if '[WARNING]' in stderr_5ttcheck:
        app.warn(
            'Command 5ttcheck indicates minor problems with provided input 5TT image \''
            + app.ARGS.in_5tt + '\':')
        for line in stderr_5ttcheck.splitlines():
            app.warn(line)
        app.warn(
            'These may or may not interfere with the dwi2response msmt_5tt script'
        )

    # Get shell information
    shells = [
        int(round(float(x)))
        for x in image.mrinfo('dwi.mif', 'shell_bvalues').split()
    ]
    if len(shells) < 3:
        app.warn(
            'Less than three b-values; response functions will not be applicable in resolving three tissues using MSMT-CSD algorithm'
        )

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

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

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

    # Revise WM mask to only include single-fibre voxels
    recursive_cleanup_option = ''
    if not app.DO_CLEANUP:
        recursive_cleanup_option = ' -nocleanup'
    if not app.ARGS.sfwm_fa_threshold:
        app.console('Selecting WM single-fibre voxels using \'' +
                    app.ARGS.wm_algo + '\' algorithm')
        run.command(
            'dwi2response ' + app.ARGS.wm_algo +
            ' dwi.mif wm_ss_response.txt -mask wm_mask.mif -voxels wm_sf_mask.mif -scratch '
            + path.quote(app.SCRATCH_DIR) + recursive_cleanup_option)
    else:
        app.console(
            'Selecting WM single-fibre voxels using \'fa\' algorithm with a hard FA threshold of '
            + str(app.ARGS.sfwm_fa_threshold))
        run.command(
            'dwi2response fa dwi.mif wm_ss_response.txt -mask wm_mask.mif -threshold '
            + str(app.ARGS.sfwm_fa_threshold) +
            ' -voxels wm_sf_mask.mif -scratch ' + path.quote(app.SCRATCH_DIR) +
            recursive_cleanup_option)

    # Check for empty masks
    wm_voxels = image.statistic('wm_sf_mask.mif', 'count',
                                '-mask wm_sf_mask.mif')
    gm_voxels = image.statistic('gm_mask.mif', 'count', '-mask gm_mask.mif')
    csf_voxels = image.statistic('csf_mask.mif', 'count', '-mask 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'
        raise MRtrixError(message)

    # For each of the three tissues, generate a multi-shell response
    bvalues_option = ' -shells ' + ','.join(map(str, shells))
    sfwm_lmax_option = ''
    if wm_lmax:
        sfwm_lmax_option = ' -lmax ' + ','.join(map(str, wm_lmax))
    run.command('amp2response dwi.mif wm_sf_mask.mif dirs.mif wm.txt' +
                bvalues_option + sfwm_lmax_option)
    run.command('amp2response dwi.mif gm_mask.mif dirs.mif gm.txt' +
                bvalues_option + ' -isotropic')
    run.command('amp2response dwi.mif csf_mask.mif dirs.mif csf.txt' +
                bvalues_option + ' -isotropic')
    run.function(shutil.copyfile, 'wm.txt',
                 path.from_user(app.ARGS.out_wm, False))
    run.function(shutil.copyfile, 'gm.txt',
                 path.from_user(app.ARGS.out_gm, False))
    run.function(shutil.copyfile, 'csf.txt',
                 path.from_user(app.ARGS.out_csf, False))

    # Generate output 4D binary image with voxel selections; RGB as in MSMT-CSD paper
    run.command(
        'mrcat csf_mask.mif gm_mask.mif wm_sf_mask.mif voxels.mif -axis 3')
    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)
Beispiel #10
0
def execute():
    import math, os, shutil
    from mrtrix3 import app, image, path, run

    # 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
    run.command('5ttcheck 5tt.mif', False)

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

    # Get lmax information (if provided)
    wm_lmax = []
    if app.args.lmax:
        wm_lmax = [int(x.strip()) for x in app.args.lmax.split(',')]
        if not len(wm_lmax) == len(shells):
            app.error('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:
                app.error('Values for lmax must be even')
            if l < 0:
                app.error('Values for lmax must be non-negative')

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

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

    # Revise WM mask to only include single-fibre voxels
    app.console(
        'Calling dwi2response recursively to select WM single-fibre voxels using \''
        + app.args.wm_algo + '\' algorithm')
    recursive_cleanup_option = ''
    if not app._cleanup:
        recursive_cleanup_option = ' -nocleanup'
    run.command(
        'dwi2response ' + app.args.wm_algo +
        ' dwi.mif wm_ss_response.txt -mask wm_mask.mif -voxels wm_sf_mask.mif -tempdir '
        + app._tempDir + recursive_cleanup_option)

    # Check for empty masks
    wm_voxels = int(
        image.statistic('wm_sf_mask.mif', 'count', 'wm_sf_mask.mif'))
    gm_voxels = int(image.statistic('gm_mask.mif', 'count', 'gm_mask.mif'))
    csf_voxels = int(image.statistic('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'
        app.error(message)

    # For each of the three tissues, generate a multi-shell response
    bvalues_option = ' -shell ' + ','.join(map(str, shells))
    sfwm_lmax_option = ''
    if wm_lmax:
        sfwm_lmax_option = ' -lmax ' + ','.join(map(str, wm_lmax))
    run.command('amp2response dwi.mif wm_sf_mask.mif dirs.mif wm.txt' +
                bvalues_option + sfwm_lmax_option)
    run.command('amp2response dwi.mif gm_mask.mif dirs.mif gm.txt' +
                bvalues_option + ' -isotropic')
    run.command('amp2response dwi.mif csf_mask.mif dirs.mif csf.txt' +
                bvalues_option + ' -isotropic')
    run.function(shutil.copyfile, 'wm.txt',
                 path.fromUser(app.args.out_wm, False))
    run.function(shutil.copyfile, 'gm.txt',
                 path.fromUser(app.args.out_gm, False))
    run.function(shutil.copyfile, 'csf.txt',
                 path.fromUser(app.args.out_csf, False))

    # Generate output 4D binary image with voxel selections; RGB as in MSMT-CSD paper
    run.command(
        'mrcat csf_mask.mif gm_mask.mif wm_sf_mask.mif voxels.mif -axis 3')
Beispiel #11
0
def execute():  #pylint: disable=unused-variable
    import os, shutil
    from mrtrix3 import app, image, matrix, MRtrixError, path, run

    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')

    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 split_data ' +
                    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('fixel2voxel ' + prefix +
                    'fixel/directions.mif split_dir ' + prefix +
                    'all_dirs.mif -number 1')
        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')
        # 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 = int(image.statistic(prefix + 'CF.mif', 'count'))
        # Select the top-ranked voxels
        run.command('mrthreshold ' + prefix + 'CF.mif -top ' +
                    str(min([app.ARGS.sf_voxels, 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.statistic(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([app.ARGS.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),
                    force=app.FORCE_OVERWRITE)
Beispiel #12
0
def execute(): #pylint: disable=unused-variable
  import os, shutil
  from mrtrix3 import app, file, image, path, run #pylint: disable=redefined-builtin

  lmax_option = ''
  if app.args.lmax:
    lmax_option = ' -lmax ' + app.args.lmax

  if app.args.max_iters < 2:
    app.error('Number of iterations must be at least 2')

  for iteration in range(0, 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
    run.command('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
    run.command('fod2fixel ' + prefix + 'FOD.mif ' + prefix + 'fixel -peak peaks.mif -mask ' + mask_in_path + ' -fmls_no_thresholds')
    file.delTemporary(prefix + 'FOD.mif')
    if iteration:
      file.delTemporary(mask_in_path)
    run.command('fixel2voxel ' + prefix + 'fixel/peaks.mif split_data ' + 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')
    file.delTemporary(prefix + 'amps.mif')
    run.command('fixel2voxel ' + prefix + 'fixel/directions.mif split_dir ' + prefix + 'all_dirs.mif -number 1')
    file.delTemporary(prefix + 'fixel')
    run.command('mrconvert ' + prefix + 'all_dirs.mif ' + prefix + 'first_dir.mif -coord 3 0:2')
    file.delTemporary(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
    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')
    file.delTemporary(prefix + 'first_peaks.mif')
    file.delTemporary(prefix + 'second_peaks.mif')
    # Select the top-ranked voxels
    run.command('mrthreshold ' + prefix + 'CF.mif -top ' + str(app.args.sf_voxels) + ' ' + 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)
    file.delTemporary(prefix + 'first_dir.mif')
    # Should we terminate?
    if iteration > 0:
      run.command('mrcalc ' + prefix + 'SF.mif iter' + str(iteration-1) + '_SF.mif -sub ' + prefix + 'SF_diff.mif')
      file.delTemporary('iter' + str(iteration-1) + '_SF.mif')
      max_diff = image.statistic(prefix + 'SF_diff.mif', 'max')
      file.delTemporary(prefix + 'SF_diff.mif')
      if int(max_diff) == 0:
        app.console('Convergence of SF voxel selection detected at iteration ' + str(iteration))
        file.delTemporary(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(app.args.iter_voxels) + ' - | maskfilter - dilate - -npass ' + str(app.args.dilate) + ' | mrcalc mask.mif - -mult ' + prefix + 'SF_dilated.mif')
    file.delTemporary(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'):
    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.fromUser(app.args.output, False))
Beispiel #13
0
def execute():
  import math, os, shutil
  from mrtrix3 import app, image, path, run

  # 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
  run.command('5ttcheck 5tt.mif', False)

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

  # Get lmax information (if provided)
  wm_lmax = [ ]
  if app.args.lmax:
    wm_lmax = [ int(x.strip()) for x in app.args.lmax.split(',') ]
    if not len(wm_lmax) == len(shells):
      app.error('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:
        app.error('Values for lmax must be even')
      if l<0:
        app.error('Values for lmax must be non-negative')

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

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

  # Revise WM mask to only include single-fibre voxels
  app.console('Calling dwi2response recursively to select WM single-fibre voxels using \'' + app.args.wm_algo + '\' algorithm')
  recursive_cleanup_option=''
  if not app._cleanup:
    recursive_cleanup_option = ' -nocleanup'
  run.command('dwi2response ' + app.args.wm_algo + ' dwi.mif wm_ss_response.txt -mask wm_mask.mif -voxels wm_sf_mask.mif -tempdir ' + app._tempDir + recursive_cleanup_option)

  # Check for empty masks
  wm_voxels  = int(image.statistic('wm_sf_mask.mif', 'count', 'wm_sf_mask.mif'))
  gm_voxels  = int(image.statistic('gm_mask.mif',    'count', 'gm_mask.mif'))
  csf_voxels = int(image.statistic('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'
    app.error(message)

  # For each of the three tissues, generate a multi-shell response
  bvalues_option = ' -shell ' + ','.join(map(str,shells))
  sfwm_lmax_option = ''
  if wm_lmax:
    sfwm_lmax_option = ' -lmax ' + ','.join(map(str,wm_lmax))
  run.command('amp2response dwi.mif wm_sf_mask.mif dirs.mif wm.txt' + bvalues_option + sfwm_lmax_option)
  run.command('amp2response dwi.mif gm_mask.mif dirs.mif gm.txt' + bvalues_option + ' -isotropic')
  run.command('amp2response dwi.mif csf_mask.mif dirs.mif csf.txt' + bvalues_option + ' -isotropic')
  run.function(shutil.copyfile, 'wm.txt',  path.fromUser(app.args.out_wm,  False))
  run.function(shutil.copyfile, 'gm.txt',  path.fromUser(app.args.out_gm,  False))
  run.function(shutil.copyfile, 'csf.txt', path.fromUser(app.args.out_csf, False))

  # Generate output 4D binary image with voxel selections; RGB as in MSMT-CSD paper
  run.command('mrcat csf_mask.mif gm_mask.mif wm_sf_mask.mif voxels.mif -axis 3')