Пример #1
0
    def test_normal_execution(self, mock_isdir):
        """ Test the normal behaviour of the function.
        """
        # Set the mocked functions returned values
        mock_isdir.side_effect = [True, False]

        # Test execution
        subjects_dir = get_or_check_freesurfer_subjects_dir(
            subjects_dir="/my/path/mock_sdir")
Пример #2
0
def create_bedpostx_organization(subject_id, bedpostx_dir, subjects_dir=None):
    """
    Tracula requires the BedpostX output files to be stored in the FreeSurfer
    folder of the subject: <subjects_dir>/<subject_id>/dmri.bedpostX
    Since we run Bedpostx outside of FreeSurfer/Tracula, this function creates
    the needed file organization using symbolic links.

    Parameters
    ----------
    subject_id: str
        Identifier of subject.
    bedpostx_dir: str
        BedpostX output directory.
    subjects_dir: str, default None
        Path to the FreeSurfer subjects directory. Required if the
        environment variable $SUBJECTS_DIR is not set.

    Returns
    -------
    fs_bedpostx_dir: str
        Path to the FreeSurfer BedpostX directory.
    """
    # FreeSurfer $SUBJECTS_DIR has to be passed or set as an env variable
    subjects_dir = get_or_check_freesurfer_subjects_dir(subjects_dir)

    # Check that subject's dir exists
    subject_dir = os.path.join(subjects_dir, subject_id)
    if not os.path.isdir(subject_dir):
        raise ValueError("Directory does not exist: %s" % subject_dir)

    # Create the FreeSurfer BedpostX directory if not existing
    fs_bedpostx_dir = os.path.join(subject_dir, "dmri.bedpostX")
    if not os.path.isdir(fs_bedpostx_dir):
        os.mkdir(fs_bedpostx_dir)

    # Create symbolic links to all BedpostX files
    for fn in os.listdir(bedpostx_dir):
        source = os.path.join(bedpostx_dir, fn)
        target = os.path.join(fs_bedpostx_dir, fn)
        os.symlink(source, target)

    return fs_bedpostx_dir
Пример #3
0
def create_bedpostx_organization(subject_id, bedpostx_dir, subjects_dir=None):
    """
    Tracula requires the BedpostX output files to be stored in the FreeSurfer
    folder of the subject: <subjects_dir>/<subject_id>/dmri.bedpostX
    Since we run Bedpostx outside of FreeSurfer/Tracula, this function creates
    the needed file organization using symbolic links.

    Parameters
    ----------
    subject_id: str
        Identifier of subject.
    bedpostx_dir: str
        BedpostX output directory.
    subjects_dir: str, default None
        Path to the FreeSurfer subjects directory. Required if the
        environment variable $SUBJECTS_DIR is not set.

    Returns
    -------
    fs_bedpostx_dir: str
        Path to the FreeSurfer BedpostX directory.
    """
    # FreeSurfer $SUBJECTS_DIR has to be passed or set as an env variable
    subjects_dir = get_or_check_freesurfer_subjects_dir(subjects_dir)

    # Check that subject's dir exists
    subject_dir = os.path.join(subjects_dir, subject_id)
    if not os.path.isdir(subject_dir):
        raise ValueError("Directory does not exist: %s" % subject_dir)

    # Create the FreeSurfer BedpostX directory if not existing
    fs_bedpostx_dir = os.path.join(subject_dir, "dmri.bedpostX")
    if not os.path.isdir(fs_bedpostx_dir):
        os.mkdir(fs_bedpostx_dir)

    # Create symbolic links to all BedpostX files
    for fn in os.listdir(bedpostx_dir):
        source = os.path.join(bedpostx_dir, fn)
        target = os.path.join(fs_bedpostx_dir, fn)
        os.symlink(source, target)

    return fs_bedpostx_dir
Пример #4
0
def trac_all(outdir, subjects_dir=None, temp_dir=None,
             fsconfig=DEFAULT_FREESURFER_PATH, fslconfig=DEFAULT_FSL_PATH):
    """ Anisotropy and diffusivity along the trajectory of a pathway.

    Parameters
    ----------
    outdir: str
        Root directory where to create the subject's output directory.
        Created if not existing.
    subjects_dir: str, default None
        Path to the FreeSurfer subjects directory. Required if the
        environment variable $SUBJECTS_DIR is not set.
    temp_dir: str, default None
        Directory to use to store temporary files. By default OS tmp dir.
    fsconfig: str, default <pyfreesurfer.DEFAULT_FREESURFER_PATH>
        Path to the FreeSurfer configuration file.

    Returns
    -------
    statdir: str
        The directory containing the FreeSurfer summary files.
    outlierfile: str
        A file that contains the subjects flagged as outliers.
    """
    # FreeSurfer $SUBJECTS_DIR has to be passed or set as an env variable
    subjects_dir = get_or_check_freesurfer_subjects_dir(subjects_dir)

    # Find all the subjects with a pathway stat file
    statdirs = glob.glob(os.path.join(subjects_dir, "*", "dpath"))
    subjects = set([path.split(os.sep)[-2] for path in statdirs])
    subjects = " ".join(subjects)

    # Create directory for temporary files
    temp_dir = tempfile.mkdtemp(prefix="trac-all_", dir=temp_dir)

    # Create configuration file
    config_str = CONFIG_TEMPLATE.format(subjects_dir=subjects_dir,
                                        subjlist=subjects,
                                        dtroot=subjects_dir)
    path_config = os.path.join(temp_dir, "trac-all.dmrirc")
    with open(path_config, "w") as f:
        f.write(config_str)

    # Run Tracula preparation
    cmd_prep = ["trac-all", "-stat", "-c", path_config]
    FSWrapper(cmd_prep, shfile=fsconfig, subjects_dir=subjects_dir,
              add_fsl_env=True, fsl_sh=fslconfig)()

    # Move results to destination folder
    statdir = os.path.join(subjects_dir, "stats")
    shutil.move(statdir, outdir)

    # Clean tmp dir
    shutil.rmtree(temp_dir)

    # Detect outliers
    statdir = os.path.join(outdir, "stats")
    outlierfile = os.path.join(outdir, "outliers.json")
    logfiles = glob.glob(os.path.join(statdir, "*.log"))
    outliers = set()
    regex = r"^Found outlier path: .*"
    for path in logfiles:
        with open(path, "rt") as open_file:
            for match in re.findall(regex, open_file.read(),
                                    flags=re.MULTILINE):
                outliers.add(match.replace("Found outlier path: ", ""))
    with open(outlierfile, "wt") as open_file:
        json.dump(list(outliers), open_file, indent=4)

    return statdir, outlierfile
Пример #5
0
def mitk_gibbs_tractogram(outdir,
                          subject_id,
                          dwi,
                          bvals,
                          bvecs,
                          nodif_brain=None,
                          nodif_brain_mask=None,
                          subjects_dir=None,
                          sh_order=4,
                          reg_factor=0.006,
                          nb_iterations=5e8,
                          particle_length=0,
                          particle_width=0,
                          particle_weight=0,
                          start_temperature=0.1,
                          end_temperature=0.001,
                          inex_energy_balance=0,
                          min_fiber_length=20,
                          curvature_threshold=45,
                          tempdir=None,
                          fs_sh=DEFAULT_FREESURFER_PATH,
                          fsl_sh=DEFAULT_FSL_PATH):
    """
    Wrapper to the MITK global tractography tool (Gibbs Tracking).

    Parameters
    ----------
    outdir: str
        Directory where to output.
    subject_id: str
        Subject id used with FreeSurfer 'recon-all' command.
    dwi: str
        Path to the diffusion-weighted images (Nifti required).
    bvals: str
        Path to the bvalue list.
    bvecs: str
        Path to the list of diffusion-sensitized directions.
    nodif_brain: str, default None
        Diffusion brain-only Nifti volume with bvalue ~ 0. If not passed, it is
        generated automatically by averaging all the b0 volumes of the DWI.
    nodif_brain_mask: str, default None
        Path to the Nifti brain binary mask in diffusion. If not passed, it is
        created with MRtrix 'dwi2mask'.
    subjects_dir: str or None, default None
        Path to the FreeSurfer subjects directory. Required if the FreeSurfer
        environment variable (i.e. $SUBJECTS_DIR) is not set.
    sh_order: int, default 4
        Qball reconstruction spherical harmonics order.
    reg_factor: float, default
        Qball reconstruction regularization factor..
    nb_iterations: int, default 5E8
        Gibbs tracking number of iterations.
    particle_length: float, default 0
         Gibbs tracking particle length, selected automatically if 0.
    particle_width: float, default 0
        Gibbs tracking particle width, selected automatically if 0.
    particle_weight: float, default 0
        Gibbs tracking particle weight, selected automatically if 0.
    start_temperature: float, default 0.1
        Gibbs tracking start temperature.
    end_temperature: float, default 0.001
        Gibbs tracking end temperature.
    inex_energy_balance: float, default 0
        Gibbs tracking weighting between in/ext energies.
    min_fiber_length: int, default 20
        Minimum fiber length in mm. Fibers that are shorter are discarded.
    curvature_threshold: int, default 45
        Maximum fiber curvature in degrees.
    tempdir: str
        Path to the directory where temporary directories should be written.
        It should be a partition with 5+ GB available.
    fs_sh: str, default NeuroSpin path
        Path to the Bash script setting the FreeSurfer environment
    fsl_sh: str, default NeuroSpin path
        Path to the Bash script setting the FSL environment.

    Returns
    -------
    mitk_tractogram: str
        The computed global tractogram in VTK format.
    """
    # -------------------------------------------------------------------------
    # STEP 0 - Check arguments

    # FreeSurfer subjects_dir
    subjects_dir = get_or_check_freesurfer_subjects_dir(subjects_dir)

    # Check input paths
    paths_to_check = [dwi, bvals, bvecs, nodif_brain_mask, fs_sh, fsl_sh]
    for p in [nodif_brain, nodif_brain_mask]:
        if p is not None:
            paths_to_check.append(p)
    for p in paths_to_check:
        if not os.path.exists(p):
            raise ValueError("File or directory does not exist: %s" % p)

    # Create <outdir> and/or <tempdir> if not existing
    for directory in [outdir, tempdir]:
        if not os.path.isdir(directory):
            os.makedirs(directory)

    # -------------------------------------------------------------------------
    # STEP 1 - Compute DWI to T1 transformation and project the T1
    #          to the diffusion space without resampling.

    # If user has not provided a 'nodif_brain_mask', compute one with
    # MRtrix 'dwi2mask'
    if nodif_brain_mask is None:
        nodif_brain_mask = os.path.join(outdir, "nodif_brain_mask.nii.gz")
        cmd_1a = ["dwi2mask", dwi, nodif_brain_mask, "-fslgrad", bvecs, bvals]
        subprocess.check_call(cmd_1a)

    # If user has not provided a 'nodif_brain', apply 'nodif_brain_mask' to
    # mean b=0 volume
    if nodif_brain is None:
        # Extract b=0 volumes and compute mean b=0 volume
        b0s = os.path.join(outdir, "b0s.nii.gz")
        mean_b0 = os.path.join(outdir, "mean_b0.nii.gz")
        mrtrix_extract_b0s_and_mean_b0(dwi=dwi,
                                       b0s=b0s,
                                       mean_b0=mean_b0,
                                       bvals=bvals,
                                       bvecs=bvecs,
                                       nb_threads=1)
        # Apply nodif_brain_mask to dwi
        nodif_brain = os.path.join(outdir, "nodif_brain.nii.gz")
        cmd_1b = ["mri_mask", mean_b0, nodif_brain_mask, nodif_brain]
        FSWrapper(cmd_1b, shfile=fs_sh)()

    # Register nodif_brain to FreeSurfer T1
    t1_brain_to_dif, dif2anat_dat, _ = freesurfer_bbregister_t1todif(
        outdir=outdir,
        subject_id=subject_id,
        nodif_brain=nodif_brain,
        subjects_dir=subjects_dir,
        fs_sh=fs_sh,
        fsl_sh=fsl_sh)

    # -------------------------------------------------------------------------
    # STEP 2 - Apply brain mask to DWI before Qball reconstruction
    dwi_brain = os.path.join(outdir, "dwi_brain.nii.gz")
    cmd_4 = ["fslmaths", dwi, "-mas", nodif_brain_mask, dwi_brain]
    FSLWrapper(cmd_4, shfile=fsl_sh)()

    # MITK requires the Nifti to have an .fslgz extension and the bvals/bvecs
    # to have the same name with .bvals/.bvecs extension
    dwi_brain_fslgz = os.path.join(outdir, "dwi_brain.fslgz")
    shutil.copyfile(dwi_brain, dwi_brain_fslgz)
    shutil.copyfile(bvals, "%s.bvals" % dwi_brain_fslgz)
    shutil.copyfile(bvecs, "%s.bvecs" % dwi_brain_fslgz)

    # -------------------------------------------------------------------------
    # STEP 3 - Qball reconstruction
    qball_coefs = os.path.join(outdir, "sphericalHarmonics_CSA_Qball.qbi")
    cmd_5 = [
        "MitkQballReconstruction.sh", "-i", dwi_brain_fslgz, "-o", qball_coefs,
        "-sh",
        "%i" % sh_order, "-r",
        "%f" % reg_factor, "-csa", "--mrtrix"
    ]
    # TODO: create MITK wrapper with LD_LIBRARY_PATH and QT_PLUGIN_PATH
    subprocess.check_call(cmd_5)

    # -------------------------------------------------------------------------
    # STEP 4 - Create white matter probability map with FSL Fast

    # Create directory for temporary files
    fast_tempdir = tempfile.mkdtemp(prefix="FSL_fast_", dir=tempdir)
    base_outpath = os.path.join(fast_tempdir, "brain")
    cmd_6 = ["fast", "-o", base_outpath, t1_brain_to_dif]
    FSLWrapper(cmd_6, shfile=fsl_sh)()

    # Save the white matter probability map
    wm_prob_map = os.path.join(outdir, "wm_prob_map.nii.gz")
    shutil.copyfile(base_outpath + "_pve_2.nii.gz", wm_prob_map)

    # Clean temporary directory
    shutil.rmtree(fast_tempdir)

    # -------------------------------------------------------------------------
    # STEP 5 - Gibbs tracking (global tractography)

    # Create XML parameter file
    root = ElementTree.Element("global_tracking_parameter_file")
    root.set("version", "1.0")
    attributes = {
        "iterations": "%i" % nb_iterations,
        "particle_length": "%f" % particle_length,
        "particle_width": "%f" % particle_width,
        "particle_weight": "%f" % particle_weight,
        "temp_start": "%f" % start_temperature,
        "temp_end": "%f" % end_temperature,
        "inexbalance": "%f" % inex_energy_balance,
        "fiber_length": "%i" % min_fiber_length,
        "curvature_threshold": "%i" % curvature_threshold
    }
    ElementTree.SubElement(root, "parameter_set", attrib=attributes)
    tree = ElementTree.ElementTree(element=root)
    path_xml = os.path.join(outdir, "parameters.gtp")
    tree.write(path_xml)

    # Run tractography
    mitk_tractogram = os.path.join(outdir, "fibers.fib")
    cmd_7 = [
        "MitkGibbsTracking.sh", "-i", qball_coefs, "-p", path_xml, "-m",
        wm_prob_map, "-o", mitk_tractogram, "-s", "MRtrix"
    ]
    subprocess.check_call(cmd_7)

    return mitk_tractogram
Пример #6
0
def probtrackx2_connectome_complete(outdir,
                                    subject_id,
                                    lh_surf,
                                    rh_surf,
                                    nodif_brain,
                                    nodif_brain_mask,
                                    bedpostx_dir,
                                    nsamples,
                                    nsteps,
                                    steplength,
                                    subjects_dir=None,
                                    loopcheck=True,
                                    cthr=0.2,
                                    fibthresh=0.01,
                                    distthresh=0.0,
                                    sampvox=0.0,
                                    fs_sh=DEFAULT_FREESURFER_PATH,
                                    fsl_sh=DEFAULT_FSL_PATH):
    """ Compute the connectome of a given tesellation, like the FreeSurfer,
    using ProbTrackx2.

    Requirements:
        - brain masks for the preprocessed DWI: nodif_brain and
          nodif_brain_mask.
        - FreeSurfer: result of recon-all on the T1.
        - FSL Bedpostx: computed for the preprocessed DWI.

    Connectome construction strategy:
        - Pathways are constructed from 'constitutive points' and not from
          endpoints. A pathway is the result of 2 samples propagating in
          opposite directions from a seed point. It is done using the
          --omatrix3 option of Probtrackx2.
        - The seed mask is the mask of WM voxels that are neighbors
          (12-connexity) of nodes.
        - The stop mask is the inverse of white matter, i.e. a sample stops
          propagating as soon as it leaves the white matter.

    Note:

    --randfib refers to initialization of streamlines only (i.e. the very first
    step) and only affects voxels with more than one fiber reconstructed:
    randfib==0, only sample from the strongest fiber
    randfib==1, randomly sample from all fibers regardless of strength that are
    above --fibthresh
    randfib==2, sample fibers stronger than --fibthresh in proportion to their
    strength (in my opinion, this is the best choice)
    randfib==3, sample all fibers randomly regardless of whether or not they
    are above --fibthresh.

    Parameters
    ----------
    outdir: str
        Directory where to output.
    subject_id: str
        Subject id used with FreeSurfer 'recon-all' command.
    lh_surf: str
        The left hemisphere surface.
    rh_surf: str
        The left hemisphere surface.
    nodif_brain: str
        Path to the preprocessed brain-only DWI volume.
    nodif_brain_mask: str
        Path to the brain binary mask.
    bedpostx_dir: str
        Bedpostx output directory.
    nsamples: int
        Number of samples per voxel to initiate in the seed mask.
    nsteps: int
        Maximum number of steps for a given sample.
    steplength: int
        Step size in mm.
    subjects_dir: str or None, default None
        Path to the FreeSurfer subjects directory. Required if the FreeSurfer
        environment variable (i.e. $SUBJECTS_DIR) is not set.
    cthr: float, optional
        Probtrackx2 option.
    fibthresh, distthresh, sampvox: float, optional
        Probtrackx2 options.
    loopcheck: bool, optional
        Probtrackx2 option.
    fs_sh: str, default NeuroSpin path
        Path to the Bash script setting the FreeSurfer environment
    fsl_sh: str, default NeuroSpin path
        Path to the Bash script setting the FSL environment.

    Returns
    ------
    coords: str
        The connectome coordinates.
    weights: str
        The connectome weights.
    """
    # -------------------------------------------------------------------------
    # STEP 0 - Check arguments

    # FreeSurfer subjects_dir
    subjects_dir = get_or_check_freesurfer_subjects_dir(subjects_dir)

    # Check input paths
    paths_to_check = [
        nodif_brain, nodif_brain_mask, bedpostx_dir, fs_sh, fsl_sh
    ]
    for p in paths_to_check:
        if not os.path.exists(p):
            raise ValueError("File or directory does not exist: %s" % p)

    # Create <outdir> if not existing
    if not os.path.isdir(outdir):
        os.makedirs(outdir)

    # -------------------------------------------------------------------------
    # STEP 1 - Compute T1 <-> DWI rigid transformation

    # FreeSurfer T1 to Nifti
    fs_t1_brain = os.path.join(subjects_dir, subject_id, "mri", "brain.mgz")
    t1_brain = os.path.join(outdir, "t1_brain.nii.gz")
    cmd_1a = ["mri_convert", fs_t1_brain, t1_brain]
    FSWrapper(cmd_1a, shfile=fs_sh)()

    # Register diffusion to T1
    dif2anat_dat = os.path.join(outdir, "dif2anat.dat")
    dif2anat_mat = os.path.join(outdir, "dif2anat.mat")
    nodif_brain_reg = os.path.join(outdir, "nodif_brain_to_t1.nii.gz")
    cmd_1b = [
        "bbregister", "--s", subject_id, "--mov", nodif_brain, "--reg",
        dif2anat_dat, "--fslmat", dif2anat_mat, "--dti", "--init-fsl", "--o",
        nodif_brain_reg
    ]
    FSWrapper(cmd_1b,
              subjects_dir=subjects_dir,
              shfile=fs_sh,
              add_fsl_env=True,
              fsl_sh=fsl_sh)()

    # Invert dif2anat transform
    m = numpy.loadtxt(dif2anat_mat)
    m_inv = numpy.linalg.inv(m)
    anat2dif_mat = os.path.join(outdir, "anat2dif.mat")
    numpy.savetxt(anat2dif_mat, m_inv)

    # -------------------------------------------------------------------------
    # STEP 2 - Create the masks for Probtrackx2

    # White matter mask
    aparc_aseg = os.path.join(subjects_dir, subject_id, "mri",
                              "aparc+aseg.mgz")
    wm_mask = os.path.join(outdir, "wm_mask.nii.gz")
    mri_binarize(inputfile=aparc_aseg,
                 outputfile=wm_mask,
                 match=None,
                 wm=True,
                 inv=False,
                 fsconfig=fs_sh)

    # Stop mask is inverse of white matter mask
    stop_mask = os.path.join(outdir, "inv_wm_mask.nii.gz")
    mri_binarize(inputfile=aparc_aseg,
                 outputfile=stop_mask,
                 match=None,
                 wm=True,
                 inv=True,
                 fsconfig=fs_sh)

    # Create seed mask
    seed_mask = wm_mask

    # Create target masks: the white surface
    white_surf = os.path.join(outdir, "white.asc")
    cmd_2a = ["mris_convert", "--combinesurfs", lh_surf, rh_surf, white_surf]
    FSWrapper(cmd_2a, subjects_dir=subjects_dir, shfile=fs_sh)()

    # -------------------------------------------------------------------------
    # STEP 7 - Run Probtrackx2
    probtrackx2(dir=outdir,
                forcedir=True,
                seedref=t1_brain,
                xfm=anat2dif_mat,
                invxfm=dif2anat_mat,
                samples=os.path.join(bedpostx_dir, "merged"),
                mask=nodif_brain_mask,
                seed=seed_mask,
                omatrix3=True,
                target3=white_surf,
                stop=stop_mask,
                nsamples=nsamples,
                nsteps=nsteps,
                steplength=steplength,
                loopcheck=loopcheck,
                cthr=cthr,
                fibthresh=fibthresh,
                distthresh=distthresh,
                sampvox=sampvox,
                pd=True,
                randfib=2,
                shfile=fsl_sh)
    coords = os.path.join(outdir, "coords_for_fdt_matrix3")
    weights = os.path.join(outdir, "fdt_matrix3.dot")

    return coords, weights
Пример #7
0
def freesurfer_bbregister_t1todif(outdir,
                                  subject_id,
                                  nodif_brain,
                                  subjects_dir=None,
                                  fs_sh=DEFAULT_FREESURFER_PATH,
                                  fsl_sh=DEFAULT_FSL_PATH):
    """ Compute DWI to T1 transformation and project the T1 to the diffusion
    space without resampling.

    Parameters
    ----------
    outdir: str
        Directory where to output.
    subject_id: str
        Subject id used with FreeSurfer 'recon-all' command.
    nodif_brain: str
        Path to the preprocessed brain-only DWI volume.
    subjects_dir: str or None, default None
        Path to the FreeSurfer subjects directory. Required if the FreeSurfer
        environment variable (i.e. $SUBJECTS_DIR) is not set.
    fs_sh: str, default NeuroSpin path
        Path to the Bash script setting the FreeSurfer environment
    fsl_sh: str, default NeuroSpin path
        Path to the Bash script setting the FSL environment.

    Returns
    -------
    t1_brain_to_dif: str
        The anatomical image in the diffusion space (without resampling).
    dif2anat_dat, dif2anat_mat: str
        The DWI to T1 transformation in FreeSurfer or FSL space respectivelly.
    """
    # -------------------------------------------------------------------------
    # STEP 0 - Check arguments

    # FreeSurfer subjects_dir
    subjects_dir = get_or_check_freesurfer_subjects_dir(subjects_dir)

    # Check input paths
    paths_to_check = [nodif_brain, fs_sh, fsl_sh]
    for p in paths_to_check:
        if not os.path.exists(p):
            raise ValueError("File or directory does not exist: %s" % p)

    # -------------------------------------------------------------------------
    # STEP 1 - Compute T1 <-> DWI rigid transformation

    # Register diffusion to T1
    dif2anat_dat = os.path.join(outdir, "dif2anat.dat")
    dif2anat_mat = os.path.join(outdir, "dif2anat.mat")
    cmd_1a = [
        "bbregister", "--s", subject_id, "--mov", nodif_brain, "--reg",
        dif2anat_dat, "--fslmat", dif2anat_mat, "--dti", "--init-fsl"
    ]
    FSWrapper(cmd_1a,
              subjects_dir=subjects_dir,
              shfile=fs_sh,
              add_fsl_env=True,
              fsl_sh=fsl_sh)()

    # Align FreeSurfer T1 brain to diffusion without downsampling
    fs_t1_brain = os.path.join(subjects_dir, subject_id, "mri", "brain.mgz")
    t1_brain_to_dif = os.path.join(outdir, "fs_t1_brain_to_dif.nii.gz")
    cmd_1b = [
        "mri_vol2vol", "--mov", nodif_brain, "--targ", fs_t1_brain, "--inv",
        "--no-resample", "--o", t1_brain_to_dif, "--reg", dif2anat_dat,
        "--no-save-reg"
    ]
    FSWrapper(cmd_1b, shfile=fs_sh)()

    return t1_brain_to_dif, dif2anat_dat, dif2anat_mat
Пример #8
0
def trac_all(outdir,
             subjects_dir=None,
             temp_dir=None,
             fsconfig=DEFAULT_FREESURFER_PATH,
             fslconfig=DEFAULT_FSL_PATH):
    """ Anisotropy and diffusivity along the trajectory of a pathway.

    Parameters
    ----------
    outdir: str
        Root directory where to create the subject's output directory.
        Created if not existing.
    subjects_dir: str, default None
        Path to the FreeSurfer subjects directory. Required if the
        environment variable $SUBJECTS_DIR is not set.
    temp_dir: str, default None
        Directory to use to store temporary files. By default OS tmp dir.
    fsconfig: str, default <pyfreesurfer.DEFAULT_FREESURFER_PATH>
        Path to the FreeSurfer configuration file.

    Returns
    -------
    statdir: str
        The directory containing the FreeSurfer summary files.
    outlierfile: str
        A file that contains the subjects flagged as outliers.
    """
    # FreeSurfer $SUBJECTS_DIR has to be passed or set as an env variable
    subjects_dir = get_or_check_freesurfer_subjects_dir(subjects_dir)

    # Find all the subjects with a pathway stat file
    statdirs = glob.glob(os.path.join(subjects_dir, "*", "dpath"))
    subjects = set([path.split(os.sep)[-2] for path in statdirs])
    subjects = " ".join(subjects)

    # Create directory for temporary files
    temp_dir = tempfile.mkdtemp(prefix="trac-all_", dir=temp_dir)

    # Create configuration file
    config_str = CONFIG_TEMPLATE.format(subjects_dir=subjects_dir,
                                        subjlist=subjects,
                                        dtroot=subjects_dir)
    path_config = os.path.join(temp_dir, "trac-all.dmrirc")
    with open(path_config, "w") as f:
        f.write(config_str)

    # Run Tracula preparation
    cmd_prep = ["trac-all", "-stat", "-c", path_config]
    FSWrapper(cmd_prep,
              shfile=fsconfig,
              subjects_dir=subjects_dir,
              add_fsl_env=True,
              fsl_sh=fslconfig)()

    # Move results to destination folder
    statdir = os.path.join(subjects_dir, "stats")
    shutil.move(statdir, outdir)

    # Clean tmp dir
    shutil.rmtree(temp_dir)

    # Detect outliers
    statdir = os.path.join(outdir, "stats")
    outlierfile = os.path.join(outdir, "outliers.json")
    logfiles = glob.glob(os.path.join(statdir, "*.log"))
    outliers = set()
    regex = r"^Found outlier path: .*"
    for path in logfiles:
        with open(path, "rt") as open_file:
            for match in re.findall(regex,
                                    open_file.read(),
                                    flags=re.MULTILINE):
                outliers.add(match.replace("Found outlier path: ", ""))
    with open(outlierfile, "wt") as open_file:
        json.dump(list(outliers), open_file, indent=4)

    return statdir, outlierfile
Пример #9
0
def recon_all_custom_wm_mask(subject_id,
                             wm_mask,
                             keep_orig=True,
                             subjects_dir=None,
                             temp_dir=None,
                             fsconfig=DEFAULT_FREESURFER_PATH):
    """
    Assuming you have run recon-all (at least upto wm.mgz creation), this
    function allows to rerun recon-all using a custom white matter mask. The
    mask has to be in the subject's FreeSurfer space (1mm iso + aligned with
    brain.mgz) with values in [0; 1] (i.e. probability of being white matter).

    Parameters
    ----------
    subject_id: str
        Identifier of subject.
    wm_mask: str
        Path to the custom white matter mask. It has to be in the subject's
        FreeSurfer space (1mm iso + aligned with brain.mgz) with values in
        [0; 1] (i.e. probability of being white matter).
        For example, tt can be the 'brain_pve_2.nii.gz" white matter
        probability map created by FSL Fast.
    keep_orig: bool, default True
        Save original 'wm.seg.mgz' as 'wm.seg.orig.mgz' instead of overwriting
        it.
    subjects_dir: str, default None
        Path to the FreeSurfer subjects directory. Required if the environment
        variable $SUBJECTS_DIR is not set.
    temp_dir: str, default None
        Directory to use to store temporary files. By default OS tmp dir.
    fsconfig: str, default <pyfreesurfer.DEFAULT_FREESURFER_PATH>
        The FreeSurfer configuration batch.
    """
    # FreeSurfer $SUBJECTS_DIR has to be passed or set as an env variable
    subjects_dir = get_or_check_freesurfer_subjects_dir(subjects_dir)

    # Check existence of the subject's directory
    subject_dir = os.path.join(subjects_dir, subject_id)
    if not os.path.isdir(subject_dir):
        ValueError("Directory does not exist: %s" % subject_dir)

    # Create temporary directory to store intermediate files
    temp_dir = tempfile.mkdtemp(prefix="recon_all_custom_wm_mask_",
                                dir=temp_dir)

    # Change input mask range of values: [0-1] to [0-110]
    wm_mask_0_110 = os.path.join(temp_dir, "wm_mask_0_110.nii.gz")
    cmd_1 = ["mris_calc", "-o", wm_mask_0_110, wm_mask, "mul", "110"]
    FSWrapper(cmd_1, shfile=fsconfig)()

    # If requested save original wm.seg.mgz as wm.seg.orig.mgz
    wm_seg_mgz = os.path.join(subject_dir, "mri", "wm.seg.mgz")
    if keep_orig:
        save_as = os.path.join(subject_dir, "mri", "wm.seg.orig.mgz")
        shutil.move(wm_seg_mgz, save_as)

    # Write the new wm.seg.mgz, FreeSurfer requires MRI_UCHAR type
    cmd_2 = ["mri_convert", wm_mask_0_110, wm_seg_mgz, "-odt", "uchar"]
    FSWrapper(cmd_2, shfile=fsconfig)()

    # Clean tmp dir
    shutil.rmtree(temp_dir)

    # Rerun recon-all
    cmd_3 = ["recon-all", "-autorecon2-wm", "-autorecon3", "-s", subject_id]
    FSWrapper(cmd_3, shfile=fsconfig, subjects_dir=subjects_dir)()

    return subject_dir
Пример #10
0
def mrtrix_tractogram(
        outdir,
        tempdir,
        subject_id,
        dwi,
        bvals,
        bvecs,
        nb_threads,
        global_tractography=False,
        mtracks=None,
        maxlength=None,
        cutoff=None,
        seed_gmwmi=False,
        sift_mtracks=None,
        sift2=False,
        nodif_brain=None,
        nodif_brain_mask=None,
        fast_t1_brain=None,
        subjects_dir=None,
        mif_gz=True,
        delete_raw_tracks=False,
        delete_dwi_mif=True,
        fs_sh=DEFAULT_FREESURFER_PATH,
        fsl_sh=DEFAULT_FSL_PATH):
    """
    Compute the connectome using MRtrix.

    Requirements:
        - FreeSurfer: result of recon-all on the T1.
        - a T1 parcellation that defines the nodes of the connectome, it has
          to be in the FreeSurfer space (i.e. aligned with
          <subjects dir>/<subject>/mri/brain.mgz), e.g. aparc+aseg from
          FreeSurfer.

    Parameters
    ----------
    outdir: str
        Path to directory where to output.
    tempdir: str
        Path to the directory where temporary directories should be written.
        It should be a partition with 5+ GB available.
    subject_id: str
        Subject identifier.
    dwi: str
        Path to the diffusion-weighted images (Nifti required).
    bvals: str
        Path to the bvalue list.
    bvecs: str
        Path to the list of diffusion-sensitized directions.
    nb_threads: int
        Number of threads.
    global_tractography: bool, default False
        If True run global tractography (tckglobal) instead of local (tckgen).
    mtracks: int, default None
        For non-global tractography only. Number of millions of tracks of the
        raw tractogram.
    maxlength: int, default None
        For non-global tractography only. Max fiber length in mm.
    cutoff: float, default None
        For non-global tractography only.
        FOD amplitude cutoff, stopping criteria.
    seed_gmwmi: bool, default False
        For non-global tractography only.
        Set this option if you want to activate the '-seed_gmwmi' option of
        MRtrix 'tckgen', to seed from the GM/WM interface. Otherwise, and by
        default, the seeding is done in white matter ('-seed_dynamic' option).
    sift_mtracks: int, default None
        For non-global tractography only.
        Number of millions of tracks to keep with SIFT.
        If not set, SIFT is not applied.
    sift2: bool, default False
        For non-global tractography only.
        To activate SIFT2.
    nodif_brain: str, default None
        Diffusion brain-only Nifti volume with bvalue ~ 0. If not passed, it is
        generated automatically by averaging all the b0 volumes of the DWI.
    nodif_brain_mask: str, default None
        Path to the Nifti brain binary mask in diffusion. If not passed, it is
        created with MRtrix 'dwi2mask'.
    fast_t1_brain: str, default None
        By default FSL FAST is run on the FreeSurfer 'brain.mgz'. If you want
        the WM probability map to be computed from another T1, pass the T1
        brain-only volume. Note that it has to be aligned with diffusion.
        This argument is useful for HCP, where some FreeSurfer 'brain.mgz'
        cannot be processed by FSL FAST.
    subjects_dir: str, default None
        Path to the FreeSurfer subjects directory. Required if the environment
        variable $SUBJECTS_DIR is not set.
    mif_gz: bool, default True
        Use compressed MIF files (.mif.gz) instead of .mif to save space.
    delete_raw_tracks: bool, default False
        Delete the raw tracks (<outdir>/<mtracks>M.tck) at the end of
        processing, to save space.
    delete_dwi_mif: bool, default True
        Delete <outdir>/DWI.mif(.gz) at the end of processing, which is a copy
        of the input <dwi> in the MIF format, to save space.
    fs_sh: str, default NeuroSpin path
        Path to the Bash script setting the FreeSurfer environment
    fsl_sh: str, default NeuroSpin path
        Path to the Bash script setting the FSL environment.

    Returns
    -------
    tracks: str
        The generated tractogram.
    sift_tracks: str
        The SIFT filtered tractogram.
    sift2_weights: str
        The SIFT2 tractogram associated weights.
    """
    # -------------------------------------------------------------------------
    # STEP 0 - Check arguments

    # FreeSurfer $SUBJECTS_DIR has to be passed or set as an env variable
    subjects_dir = get_or_check_freesurfer_subjects_dir(subjects_dir)

    # Use compressed MIF files or not
    MIF_EXT = ".mif.gz" if mif_gz else ".mif"

    # Is SIFT to be applied
    sift = (sift_mtracks is not None)

    # Check input and optional paths
    paths_to_check = [dwi, bvals, bvecs, fsl_sh]
    for p in [nodif_brain, nodif_brain_mask, fast_t1_brain]:
        if p is not None:
            paths_to_check.append(p)
    for p in paths_to_check:
        if not os.path.exists(p):
            raise ValueError("File or directory does not exist: %s" % p)

    # Identify whether the DWI acquisition is single or multi-shell
    _, _, nb_shells, _ = read_bvals_bvecs(bvals, bvecs, min_bval=200.)
    is_multi_shell = nb_shells > 1

    # Check compatibility of arguments
    if global_tractography:
        if not is_multi_shell:
            raise ValueError("MRtrix global tractography is only applicable "
                             "to multi shell data.")
        if seed_gmwmi:
            raise ValueError("'seed_gmwmi' cannot be applied when requesting "
                             "global tractography.")
        if sift or sift2:
            raise ValueError("SIFT or SIFT2 are not meant to be used with "
                             "global tractography.")
    else:
        value_of_required_arg = dict(mtracks=mtracks, maxlength=maxlength,
                                     cutoff=cutoff)
        for required_arg, value in value_of_required_arg.items():
            if value is None:
                raise ValueError("When 'global_tractography' is set to False "
                                 "%s is required." % required_arg)

    # Create <outdir> and/or <tempdir> if not existing
    for directory in [outdir, tempdir]:
        if not os.path.isdir(directory):
            os.makedirs(directory)

    # -------------------------------------------------------------------------
    # STEP 1 - Format DWI and compute nodif brain and nodif brain mask if
    # not provided

    # Convert DWI to MRtrix format
    dwi_mif = os.path.join(outdir, "DWI.mif")
    cmd_1a = ["mrconvert", dwi, dwi_mif, "-fslgrad", bvecs, bvals,
              "-datatype", "float32", "-stride", "0,0,0,1",
              "-nthreads", "%i" % nb_threads, "-failonwarn"]
    subprocess.check_call(cmd_1a)

    # If user has not provided a 'nodif_brain_mask', compute one with
    # MRtrix 'dwi2mask'
    if nodif_brain_mask is None:
        nodif_brain_mask = os.path.join(outdir, "nodif_brain_mask.nii.gz")
        cmd_1b = ["dwi2mask", dwi_mif, nodif_brain_mask]
        subprocess.check_call(cmd_1b)

    # If user has not provided a 'nodif_brain', apply 'nodif_brain_mask' to
    # mean b=0 volume
    if nodif_brain is None:
        # Extract b=0 volumes and compute mean b=0 volume
        b0s = os.path.join(outdir, "b0s.nii.gz")
        mean_b0 = os.path.join(outdir, "mean_b0.nii.gz")
        mrtrix_extract_b0s_and_mean_b0(dwi=dwi_mif, b0s=b0s, mean_b0=mean_b0,
                                       nb_threads=nb_threads)
        # Apply nodif_brain_mask to dwi
        nodif_brain = os.path.join(outdir, "nodif_brain.nii.gz")
        cmd_1b = ["mri_mask", mean_b0, nodif_brain_mask, nodif_brain]
        FSWrapper(cmd_1b, shfile=fs_sh)()

    # -------------------------------------------------------------------------
    # STEP 2 - Register DWI to T1 using FreeSurfer bbregister
    # - compute the rigid transformation
    # - apply transformation to align T1 without downsampling
    t1_brain_to_dif, dif2anat_dat, _ = freesurfer_bbregister_t1todif(
            outdir=outdir,
            subject_id=subject_id,
            nodif_brain=nodif_brain,
            subjects_dir=subjects_dir,
            fs_sh=fs_sh,
            fsl_sh=fsl_sh)

    # -------------------------------------------------------------------------
    # STEP 3 - "5 tissue types" segmentation
    # Generate the 5TT image based on a FSL FAST
    five_tissues = os.path.join(outdir, "5TT%s" % MIF_EXT)
    fast_t1_brain = t1_brain_to_dif if fast_t1_brain is None else fast_t1_brain
    cmd_3 = ["5ttgen", "fsl", fast_t1_brain, five_tissues, "-premasked",
             "-tempdir", tempdir, "-nthreads", "%i" % nb_threads]
    FSLWrapper(cmd_3, env=os.environ, shfile=fsl_sh)()

    # -------------------------------------------------------------------------
    # STEP 4 - Estimation of the response function of fibers in each voxel
    if is_multi_shell:
        rf_wm = os.path.join(outdir, "RF_WM.txt")
        rf_gm = os.path.join(outdir, "RF_GM.txt")
        rf_csf = os.path.join(outdir, "RF_CSF.txt")
        cmd_4 = ["dwi2response", "msmt_5tt", dwi_mif, five_tissues,
                 rf_wm, rf_gm, rf_csf]
    else:
        rf = os.path.join(outdir, "RF.txt")
        cmd_4 = ["dwi2response", "tournier", dwi_mif, rf]
    rf_voxels = os.path.join(outdir, "RF_voxels%s" % MIF_EXT)
    cmd_4 += ["-voxels", rf_voxels, "-tempdir", tempdir,
              "-nthreads", "%i" % nb_threads]
    subprocess.check_call(cmd_4)

    # -------------------------------------------------------------------------
    # STEP 5 - Compute FODs
    wm_fods = os.path.join(outdir, "WM_FODs%s" % MIF_EXT)
    if is_multi_shell:
        gm_mif = os.path.join(outdir, "GM%s" % MIF_EXT)
        csf_mif = os.path.join(outdir, "CSF%s" % MIF_EXT)
        cmd_5 = ["dwi2fod", "msmt_csd", dwi_mif, rf_wm, wm_fods,
                 rf_gm, gm_mif, rf_csf, csf_mif]
    else:
        cmd_5 = ["dwi2fod", "csd", dwi_mif, rf, wm_fods]
    cmd_5 += ["-mask", nodif_brain_mask, "-nthreads", "%i" % nb_threads,
              "-failonwarn"]
    subprocess.check_call(cmd_5)

    # -------------------------------------------------------------------------
    # STEP 6 - Image to visualize for multi-shell
    if is_multi_shell:
        wm_fods_vol0 = os.path.join(outdir, "WM_FODs_vol0%s" % MIF_EXT)
        cmd_6a = ["mrconvert", wm_fods, wm_fods_vol0, "-coord", "3", "0",
                  "-nthreads", "%i" % nb_threads]
        subprocess.check_call(cmd_6a)

        tissueRGB_mif = os.path.join(outdir, "tissueRGB%s" % MIF_EXT)
        cmd_6b = ["mrcat", csf_mif, gm_mif, wm_fods_vol0, tissueRGB_mif,
                  "-axis", "3", "-nthreads", "%i" % nb_threads, "-failonwarn"]
        subprocess.check_call(cmd_6b)

    # -------------------------------------------------------------------------
    # STEP 7 - Tractography: tckglobal or tckgen
    if global_tractography:
        tracks = os.path.join(outdir, "global.tck")
        global_fod = os.path.join(outdir, "fod%s" % MIF_EXT)
        fiso_mif = os.path.join(outdir, "fiso%s" % MIF_EXT)
        cmd_7 = ["tckglobal", dwi_mif, rf_wm, "-riso", rf_csf, "-riso", rf_gm,
                 "-mask", nodif_brain_mask, "-niter", "1e8",
                 "-fod", global_fod, "-fiso", fiso_mif, tracks]
    else:
        # Anatomically Constrained Tractography:
        # iFOD2 algorithm with backtracking and crop fibers at GM/WM interface
        tracks = os.path.join(outdir, "%iM.tck" % mtracks)
        cmd_7 = ["tckgen", wm_fods, tracks, "-act", five_tissues, "-backtrack",
                 "-crop_at_gmwmi", "-maxlength", "%i" % maxlength,
                 "-number", "%dM" % mtracks, "-cutoff", "%f" % cutoff]

        # Requested seeding strategy: -seed_gmwmi or -seed_dynamic
        if seed_gmwmi:
            gmwmi_mask = os.path.join(outdir, "gmwmi_mask%s" % MIF_EXT)
            cmd_7b = ["5tt2gmwmi", five_tissues, gmwmi_mask]
            subprocess.check_call(cmd_7b)
            cmd_7 += ["-seed_gmwmi", gmwmi_mask]
        else:
            cmd_7 += ["-seed_dynamic", wm_fods]
    cmd_7 += ["-nthreads", "%i" % nb_threads, "-failonwarn"]
    subprocess.check_call(cmd_7)

    # -------------------------------------------------------------------------
    # STEP 8 - Filter tracts with SIFT if requested
    sift_tracks = None
    if sift:
        sift_tracks = os.path.join(outdir, "%iM_SIFT.tck" % sift_mtracks)
        cmd_8 = ["tcksift", tracks, wm_fods, sift_tracks,
                 "-act", five_tissues, "-term_number", "%iM" % sift_mtracks,
                 "-nthreads", "%i" % nb_threads, "-failonwarn"]
        subprocess.check_call(cmd_8)

    # -------------------------------------------------------------------------
    # STEP 9 - run SIFT2 if requested (compute weights of fibers)
    sift2_weights = None
    if sift2:
        sift2_weights = os.path.join(outdir, "sift2_weights.txt")
        cmd_9 = ["tcksift2", tracks, wm_fods, sift2_weights,
                 "-act", five_tissues,
                 "-nthreads", "%i" % nb_threads, "-failonwarn"]
        subprocess.check_call(cmd_9)

    # -------------------------------------------------------------------------
    # STEP 10 - clean if requested
    if delete_raw_tracks:
        os.remove(tracks)
        tracks = None

    if delete_dwi_mif:
        os.remove(dwi_mif)
        dwi_mif = None
    else:
        if mif_gz:
            subprocess.check_call(["gzip", dwi_mif])
            dwi_mif += ".gz"

    return tracks, sift_tracks, sift2_weights
Пример #11
0
def trac_all_longitudinal(outdir,
                          subject_template_id,
                          subject_timepoint_ids,
                          dwis,
                          bvalss,
                          bvecss,
                          bedpostx_dirs,
                          subjects_dir=None,
                          do_eddy=False,
                          do_rotate_bvecs=True,
                          do_bbregister=True,
                          do_register_mni=True,
                          temp_dir=None,
                          fsconfig=DEFAULT_FREESURFER_PATH):
    """

    Parameters
    ----------
    outdir: str
        Root directory where to create the subject's output directories.
        Created if not existing.
    subject_template_id: str
        Identifier of the subject template.
    subject_timepoint_ids: str
        Identifiers of the subject for all the timepoints.
    dwis: list of str
        Paths to Nifti diffusion series. In the order corresponding to
        <subject_timepoint_ids>.
    bvalss: list of str
        Paths to b-values of diffusion series. In the order corresponding to
        <dwis>.
    bvecss: list of str
        Paths to diffusion-sensitized directions of diffusion series. In the
        order corresponding to <dwis>.
    bedpostx_dirs: list of str
        BedpostX output directories. In the order corresponding to <dwis>.
    subjects_dir: str, default None
        Path to the FreeSurfer longitudinal subjects directory. Required if
        the environment variable $SUBJECTS_DIR is not set.
    do_eddy: bool, default False
        Apply FSL eddy-current correction.
    do_rotate_bvecs: bool, default True
        Rotate bvecs to match eddy-current correction.
    do_bbregister: bool, default True
        Register diffusion to T1 using bbregister.
    do_register_mni:
        Register T1 to MNI.
    temp_dir: str, default None
        Set the root temporary directory.
    fsconfig: str, default <pyfreesurfer.DEFAULT_FREESURFER_PATH>
        Path to the FreeSurfer configuration file.

    Returns
    -------
    subject_long_outdirs: list of str
        Path to longitudinal subject's output directories.
    """
    # Check input arguments

    # Check that the user has passed non-empty lists of the same length
    list_args = [subject_timepoint_ids, dwis, bvalss, bvecss, bedpostx_dirs]
    are_all_lists = all(map(lambda x: isinstance(x, list), list_args))
    all_same_size = len(set(map(len, list_args))) == 1
    non_empty = len(subject_timepoint_ids) > 1
    if not (are_all_lists & all_same_size & non_empty):
        raise ValueError("'subject_timepoint_ids', 'dwis', 'bvals', 'bvecs' "
                         "and 'bedpostx_dirs' must be lists of IDs/paths.")

    # FreeSurfer $SUBJECTS_DIR has to be passed or set as an env variable
    subjects_dir = get_or_check_freesurfer_subjects_dir(subjects_dir)

    # Check existence of input files/directories
    input_paths = dwis + bvalss + bvecss + bedpostx_dirs + [fsconfig]
    for path in input_paths:
        if not os.path.exists(path):
            raise ValueError("File or directory does not exist: %s" % path)

    # Create directory for temporary files
    temp_dir = tempfile.mkdtemp(prefix="trac-all_longitudinal_", dir=temp_dir)

    # The bvecs can be stored as a 3 x N or N x 3 matrix (N directions).
    # FreeSurfer requires the 2nd convention (one row per direction).
    # read_bvals_bvecs() always loads the bvecs file as a N x 3 numpy array,
    # save this numpy array in a temporary directory and use it as the
    # bvecs file to be sure to be in the right convention.
    bvecss_Nx3 = []
    for tp_sid, bvals, bvecs in zip(subject_timepoint_ids, bvalss, bvecss):
        _, bvecs_array, _, _ = read_bvals_bvecs(bvals_path=bvals,
                                                bvecs_path=bvecs,
                                                min_bval=200.)
        bvecs_Nx3 = os.path.join(temp_dir, "bvecs_Nx3_%s" % tp_sid)
        numpy.savetxt(bvecs_Nx3, bvecs_array)
        bvecss_Nx3.append(bvecs_Nx3)

    # Create configuration file
    str_subjlist = " ".join(subject_timepoint_ids)
    str_baselist = " ".join([subject_template_id] * len(subject_timepoint_ids))
    str_dwis = " ".join(dwis)
    str_bveclist = " ".join(bvecss_Nx3)
    str_bvallist = " ".join(bvalss)
    config_str = LONG_CONFIG_TEMPLATE.format(subjects_dir=subjects_dir,
                                             subjlist=str_subjlist,
                                             baselist=str_baselist,
                                             dtroot=outdir,
                                             dcmlist=str_dwis,
                                             bveclist=str_bveclist,
                                             bvallist=str_bvallist,
                                             doeddy=do_eddy,
                                             dorotbvecs=do_rotate_bvecs,
                                             doregbbr=do_bbregister,
                                             doregmni=do_register_mni)
    path_config = os.path.join(temp_dir, "trac-all.long.dmrirc")
    with open(path_config, "w") as f:
        f.write(config_str)

    # For each timepoint of subject:
    subject_long_outdirs = []
    for tp_sid, bedpostx_dir in zip(subject_timepoint_ids, bedpostx_dirs):

        # Create <outdir>/<tp sid>.long.<template id> if not existing
        long_sid = "%s.long.%s" % (tp_sid, subject_template_id)
        long_outdir = os.path.join(outdir, long_sid)
        if not os.path.isdir(long_outdir):
            os.makedirs(long_outdir)
        subject_long_outdirs.append(long_outdir)

        # Tracula requires the bedpostX files to be stored in
        # <outdir>/<tp sid>.long.<template id>/dmri.bedpostX
        create_bedpostx_organization(subject_id=long_sid,
                                     subjects_dir=outdir,
                                     bedpostx_dir=bedpostx_dir)

    # Run Tracula preparation
    cmd_prep = ["trac-all", "-prep", "-c", path_config]
    FSWrapper(cmd_prep,
              shfile=fsconfig,
              subjects_dir=subjects_dir,
              add_fsl_env=True)()

    # Tracula pathways tractography
    cmd_path = ["trac-all", "-path", "-c", path_config]
    FSWrapper(cmd_path,
              shfile=fsconfig,
              subjects_dir=subjects_dir,
              add_fsl_env=True)()

    # Clean tmp dir
    shutil.rmtree(temp_dir)

    return subject_long_outdirs
Пример #12
0
def trac_all(outdir,
             subject_id,
             dwi,
             bvals,
             bvecs,
             bedpostx_dir,
             subjects_dir=None,
             do_eddy=False,
             do_rotate_bvecs=True,
             do_bbregister=True,
             do_register_mni=True,
             temp_dir=None,
             fsconfig=DEFAULT_FREESURFER_PATH):
    """

    Parameters
    ----------
    outdir: str
        Root directory where to create the subject's output directory.
        Created if not existing.
    subject_id: str
        Identifier of subject.
    dwi: str
        Path to input Nifti diffusion-weighted volumes.
    bvals: str
        Path to b-values of diffusion-weighted volumes.
    bvecs: str
        Path to diffusion-sensitized directions.
    bedpostx_dir: str
        BedpostX output directory.
    subjects_dir: str, default None
        Path to the FreeSurfer subjects directory. Required if the
        environment variable $SUBJECTS_DIR is not set.
    do_eddy: bool, default False
        Apply FSL eddy-current correction.
    do_rotate_bvecs: bool, default True
        Rotate bvecs to match eddy-current correction.
    do_bbregister: bool, default True
        Register diffusion to T1 using bbregister.
    do_register_mni:
        Register T1 to MNI.
    temp_dir: str, default None
        Directory to use to store temporary files. By default OS tmp dir.
    fsconfig: str, default <pyfreesurfer.DEFAULT_FREESURFER_PATH>
        Path to the FreeSurfer configuration file.

    Returns
    -------
    subject_outdir: str
        Path to subject's output directory.
    """
    # FreeSurfer $SUBJECTS_DIR has to be passed or set as an env variable
    subjects_dir = get_or_check_freesurfer_subjects_dir(subjects_dir)

    # Check existence of input files/directories
    for path in [dwi, bvals, bvecs, bedpostx_dir, fsconfig]:
        if not os.path.exists(path):
            raise ValueError("File or directory does not exist: %s" % path)

    # Load bvecs and number of b0 volumes
    _, bvecs_array, _, nb_b0s = read_bvals_bvecs(bvals_path=bvals,
                                                 bvecs_path=bvecs,
                                                 min_bval=200.)

    # Create directory <outdir>/<subject_id>
    subject_outdir = os.path.join(outdir, subject_id)
    if not os.path.isdir(subject_outdir):
        os.makedirs(subject_outdir)

    # Create directory for temporary files
    temp_dir = tempfile.mkdtemp(prefix="trac-all_", dir=temp_dir)

    # The bvecs can be stored as a 3 x N or N x 3 matrix (N directions).
    # FreeSurfer requires the 2nd convention (one row per direction).
    # read_bvals_bvecs() always loads the bvecs file as a N x 3 numpy array,
    # save this numpy array in a temporary directory and use it as the
    # bvecs file to be sure to be in the right convention.
    bvecs_Nx3 = os.path.join(temp_dir, "bvecs_Nx3")
    numpy.savetxt(bvecs_Nx3, bvecs_array)

    # Create configuration file
    config_str = CONFIG_TEMPLATE.format(subjects_dir=subjects_dir,
                                        subjlist=subject_id,
                                        dtroot=outdir,
                                        dcmlist=dwi,
                                        bveclist=bvecs_Nx3,
                                        bvallist=bvals,
                                        doeddy=do_eddy,
                                        dorotbvecs=do_rotate_bvecs,
                                        doregbbr=do_bbregister,
                                        doregmni=do_register_mni)
    path_config = os.path.join(temp_dir, "trac-all.dmrirc")
    with open(path_config, "w") as f:
        f.write(config_str)

    # Run Tracula preparation
    cmd_prep = ["trac-all", "-prep", "-c", path_config]
    FSWrapper(cmd_prep,
              shfile=fsconfig,
              subjects_dir=subjects_dir,
              add_fsl_env=True)()

    # Tracula requires the BedpostX files to be stored in
    # <outdir>/<subject_id>/dmri.bedpostX
    create_bedpostx_organization(subject_id=subject_id,
                                 subjects_dir=outdir,
                                 bedpostx_dir=bedpostx_dir)

    # Tracula pathways tractography
    cmd_path = ["trac-all", "-path", "-c", path_config]
    FSWrapper(cmd_path,
              shfile=fsconfig,
              subjects_dir=subjects_dir,
              add_fsl_env=True)()

    # Clean tmp dir
    shutil.rmtree(temp_dir)

    return subject_outdir
Пример #13
0
def trac_all_longitudinal(outdir, subject_template_id, subject_timepoint_ids,
                          dwis, bvalss, bvecss, bedpostx_dirs,
                          subjects_dir=None, do_eddy=False,
                          do_rotate_bvecs=True, do_bbregister=True,
                          do_register_mni=True, temp_dir=None,
                          fsconfig=DEFAULT_FREESURFER_PATH):
    """

    Parameters
    ----------
    outdir: str
        Root directory where to create the subject's output directories.
        Created if not existing.
    subject_template_id: str
        Identifier of the subject template.
    subject_timepoint_ids: str
        Identifiers of the subject for all the timepoints.
    dwis: list of str
        Paths to Nifti diffusion series. In the order corresponding to
        <subject_timepoint_ids>.
    bvalss: list of str
        Paths to b-values of diffusion series. In the order corresponding to
        <dwis>.
    bvecss: list of str
        Paths to diffusion-sensitized directions of diffusion series. In the
        order corresponding to <dwis>.
    bedpostx_dirs: list of str
        BedpostX output directories. In the order corresponding to <dwis>.
    subjects_dir: str, default None
        Path to the FreeSurfer longitudinal subjects directory. Required if
        the environment variable $SUBJECTS_DIR is not set.
    do_eddy: bool, default False
        Apply FSL eddy-current correction.
    do_rotate_bvecs: bool, default True
        Rotate bvecs to match eddy-current correction.
    do_bbregister: bool, default True
        Register diffusion to T1 using bbregister.
    do_register_mni:
        Register T1 to MNI.
    temp_dir: str, default None
        Set the root temporary directory.
    fsconfig: str, default <pyfreesurfer.DEFAULT_FREESURFER_PATH>
        Path to the FreeSurfer configuration file.

    Returns
    -------
    subject_long_outdirs: list of str
        Path to longitudinal subject's output directories.
    """
    # Check input arguments

    # Check that the user has passed non-empty lists of the same length
    list_args = [subject_timepoint_ids, dwis, bvalss, bvecss, bedpostx_dirs]
    are_all_lists = all(map(lambda x: isinstance(x, list), list_args))
    all_same_size = len(set(map(len, list_args))) == 1
    non_empty = len(subject_timepoint_ids) > 1
    if not (are_all_lists & all_same_size & non_empty):
        raise ValueError("'subject_timepoint_ids', 'dwis', 'bvals', 'bvecs' "
                         "and 'bedpostx_dirs' must be lists of IDs/paths.")

    # FreeSurfer $SUBJECTS_DIR has to be passed or set as an env variable
    subjects_dir = get_or_check_freesurfer_subjects_dir(subjects_dir)

    # Check existence of input files/directories
    input_paths = dwis + bvalss + bvecss + bedpostx_dirs + [fsconfig]
    for path in input_paths:
        if not os.path.exists(path):
            raise ValueError("File or directory does not exist: %s" % path)

    # Create directory for temporary files
    temp_dir = tempfile.mkdtemp(prefix="trac-all_longitudinal_", dir=temp_dir)

    # The bvecs can be stored as a 3 x N or N x 3 matrix (N directions).
    # FreeSurfer requires the 2nd convention (one row per direction).
    # read_bvals_bvecs() always loads the bvecs file as a N x 3 numpy array,
    # save this numpy array in a temporary directory and use it as the
    # bvecs file to be sure to be in the right convention.
    bvecss_Nx3 = []
    for tp_sid, bvals, bvecs in zip(subject_timepoint_ids, bvalss, bvecss):
        _, bvecs_array, _, _ = read_bvals_bvecs(bvals_path=bvals,
                                                bvecs_path=bvecs,
                                                min_bval=200.)
        bvecs_Nx3 = os.path.join(temp_dir, "bvecs_Nx3_%s" % tp_sid)
        numpy.savetxt(bvecs_Nx3, bvecs_array)
        bvecss_Nx3.append(bvecs_Nx3)

    # Create configuration file
    str_subjlist = " ".join(subject_timepoint_ids)
    str_baselist = " ".join([subject_template_id] * len(subject_timepoint_ids))
    str_dwis = " ".join(dwis)
    str_bveclist = " ".join(bvecss_Nx3)
    str_bvallist = " ".join(bvalss)
    config_str = LONG_CONFIG_TEMPLATE.format(subjects_dir=subjects_dir,
                                             subjlist=str_subjlist,
                                             baselist=str_baselist,
                                             dtroot=outdir,
                                             dcmlist=str_dwis,
                                             bveclist=str_bveclist,
                                             bvallist=str_bvallist,
                                             doeddy=do_eddy,
                                             dorotbvecs=do_rotate_bvecs,
                                             doregbbr=do_bbregister,
                                             doregmni=do_register_mni)
    path_config = os.path.join(temp_dir, "trac-all.long.dmrirc")
    with open(path_config, "w") as f:
        f.write(config_str)

    # For each timepoint of subject:
    subject_long_outdirs = []
    for tp_sid, bedpostx_dir in zip(subject_timepoint_ids, bedpostx_dirs):

        # Create <outdir>/<tp sid>.long.<template id> if not existing
        long_sid = "%s.long.%s" % (tp_sid, subject_template_id)
        long_outdir = os.path.join(outdir, long_sid)
        if not os.path.isdir(long_outdir):
            os.makedirs(long_outdir)
        subject_long_outdirs.append(long_outdir)

        # Tracula requires the bedpostX files to be stored in
        # <outdir>/<tp sid>.long.<template id>/dmri.bedpostX
        create_bedpostx_organization(subject_id=long_sid, subjects_dir=outdir,
                                     bedpostx_dir=bedpostx_dir)

    # Run Tracula preparation
    cmd_prep = ["trac-all", "-prep", "-c", path_config]
    FSWrapper(cmd_prep, shfile=fsconfig, subjects_dir=subjects_dir,
              add_fsl_env=True)()

    # Tracula pathways tractography
    cmd_path = ["trac-all", "-path", "-c", path_config]
    FSWrapper(cmd_path, shfile=fsconfig, subjects_dir=subjects_dir,
              add_fsl_env=True)()

    # Clean tmp dir
    shutil.rmtree(temp_dir)

    return subject_long_outdirs
Пример #14
0
def trac_all(outdir, subject_id, dwi, bvals, bvecs, bedpostx_dir,
             subjects_dir=None, do_eddy=False, do_rotate_bvecs=True,
             do_bbregister=True, do_register_mni=True, temp_dir=None,
             fsconfig=DEFAULT_FREESURFER_PATH):
    """ Pathway reconstruction.

    Parameters
    ----------
    outdir: str
        Root directory where to create the subject's output directory.
        Created if not existing.
    subject_id: str
        Identifier of subject.
    dwi: str
        Path to input Nifti diffusion-weighted volumes.
    bvals: str
        Path to b-values of diffusion-weighted volumes.
    bvecs: str
        Path to diffusion-sensitized directions.
    bedpostx_dir: str
        BedpostX output directory.
    subjects_dir: str, default None
        Path to the FreeSurfer subjects directory. Required if the
        environment variable $SUBJECTS_DIR is not set.
    do_eddy: bool, default False
        Apply FSL eddy-current correction.
    do_rotate_bvecs: bool, default True
        Rotate bvecs to match eddy-current correction.
    do_bbregister: bool, default True
        Register diffusion to T1 using bbregister.
    do_register_mni:
        Register T1 to MNI.
    temp_dir: str, default None
        Directory to use to store temporary files. By default OS tmp dir.
    fsconfig: str, default <pyfreesurfer.DEFAULT_FREESURFER_PATH>
        Path to the FreeSurfer configuration file.

    Returns
    -------
    subject_outdir: str
        Path to subject's output directory.
    """
    # FreeSurfer $SUBJECTS_DIR has to be passed or set as an env variable
    subjects_dir = get_or_check_freesurfer_subjects_dir(subjects_dir)

    # Check existence of input files/directories
    for path in [dwi, bvals, bvecs, bedpostx_dir, fsconfig]:
        if not os.path.exists(path):
            raise ValueError("File or directory does not exist: %s" % path)

    # Load bvecs and number of b0 volumes
    _, bvecs_array, _, nb_b0s = read_bvals_bvecs(bvals_path=bvals,
                                                 bvecs_path=bvecs,
                                                 min_bval=200.)

    # Create directory <outdir>/<subject_id>
    subject_outdir = os.path.join(outdir, subject_id)
    if not os.path.isdir(subject_outdir):
        os.makedirs(subject_outdir)

    # Create directory for temporary files
    temp_dir = tempfile.mkdtemp(prefix="trac-all_", dir=temp_dir)

    # The bvecs can be stored as a 3 x N or N x 3 matrix (N directions).
    # FreeSurfer requires the 2nd convention (one row per direction).
    # read_bvals_bvecs() always loads the bvecs file as a N x 3 numpy array,
    # save this numpy array in a temporary directory and use it as the
    # bvecs file to be sure to be in the right convention.
    bvecs_Nx3 = os.path.join(temp_dir, "bvecs_Nx3")
    numpy.savetxt(bvecs_Nx3, bvecs_array)

    # Create configuration file
    config_str = CONFIG_TEMPLATE.format(subjects_dir=subjects_dir,
                                        subjlist=subject_id,
                                        dtroot=outdir,
                                        dcmlist=dwi,
                                        bveclist=bvecs_Nx3,
                                        bvallist=bvals,
                                        doeddy=do_eddy,
                                        dorotbvecs=do_rotate_bvecs,
                                        doregbbr=do_bbregister,
                                        doregmni=do_register_mni)
    path_config = os.path.join(temp_dir, "trac-all.dmrirc")
    with open(path_config, "w") as f:
        f.write(config_str)

    # Run Tracula preparation
    cmd_prep = ["trac-all", "-prep", "-c", path_config]
    FSWrapper(cmd_prep, shfile=fsconfig, subjects_dir=subjects_dir,
              add_fsl_env=True)()

    # Tracula requires the BedpostX files to be stored in
    # <outdir>/<subject_id>/dmri.bedpostX
    create_bedpostx_organization(subject_id=subject_id, subjects_dir=outdir,
                                 bedpostx_dir=bedpostx_dir)

    # Tracula pathways tractography
    cmd_path = ["trac-all", "-path", "-c", path_config]
    FSWrapper(cmd_path, shfile=fsconfig, subjects_dir=subjects_dir,
              add_fsl_env=True)()

    # Clean tmp dir
    shutil.rmtree(temp_dir)

    return subject_outdir
Пример #15
0
def probtrackx2_connectome(
        outdir,
        tempdir,
        subject_id,
        t1_parc,
        t1_parc_lut,
        connectome_lut,
        nodif_brain,
        nodif_brain_mask,
        bedpostx_dir,
        nsamples,
        nsteps,
        steplength,
        fix_freesurfer_subcortical=False,
        subjects_dir=None,
        loopcheck=True,
        cthr=0.2,
        fibthresh=0.01,
        distthresh=0.0,
        sampvox=0.0,
        snapshots=True,
        fs_sh=DEFAULT_FREESURFER_PATH,
        fsl_sh=DEFAULT_FSL_PATH):
    """
    Compute the connectome of a given parcellation, like the FreeSurfer
    aparc+aseg segmentation, using ProbTrackx2.

    Requirements:
        - brain masks for the preprocessed DWI: nodif_brain and
          nodif_brain_mask.
        - FreeSurfer: result of recon-all on the T1.
        - FSL Bedpostx: computed for the preprocessed DWI.
        - a T1 parcellation that defines the nodes of the connectome, it has
          to be in the FreeSurfer space (i.e. aligned with
          <subjects dir>/<subject>/mri/brain.mgz), e.g. aparc+aseg from
          FreeSurfer.

    Connectome construction strategy:
        - Pathways are constructed from 'constitutive points' and not from
          endpoints. A pathway is the result of 2 samples propagating in
          opposite directions from a seed point. It is done using the
          --omatrix3 option of Probtrackx2.
        - The seed mask is the mask of WM voxels that are neighbors
          (12-connexity) of nodes.
        - The stop mask is the inverse of white matter, i.e. a sample stops
          propagating as soon as it leaves the white matter.

    Parameters
    ----------
    outdir: str
        Directory where to output.
    tempdir: str
        Path to the directory where temporary directories should be written.
        It should be a partition with 5+ GB available.
    subject_id: str
        Subject id used with FreeSurfer 'recon-all' command.
    t1_parc: str
        Path to the parcellation that defines the nodes of the connectome, e.g.
        aparc+aseg.mgz from FreeSurfer.
    t1_parc_lut: str
        Path to the Look Up Table for the passed parcellation in the
        FreeSurfer LUT format. If you T1 parcellation is from FreeSurfer, this
        will most likely be <$FREESURFER_HOME>/FreeSurferColorLUT.txt.
    connectome_lut: str
        Path to a Look Up Table in the FreeSurfer LUT format, listing the
        regions from the parcellation to use as nodes in the connectome. The
        region names should match the ones used in the <t1_parc_lut> LUT and
        the integer labels should be the row/col positions in the connectome.
        Alternatively it can be set to 'Lausanne2008' to use the predefined
        LUT for the Lausanne 2008 atlas, which is based on the FreeSurfer
        aparc+aseg parcellation.
    nodif_brain: str
        Path to the preprocessed brain-only DWI volume.
    nodif_brain_mask: str
        Path to the brain binary mask.
    bedpostx_dir: str
        Bedpostx output directory.
    nsamples: int
        Number of samples per voxel to initiate in the seed mask.
    nsteps: int
        Maximum number of steps for a given sample.
    steplength: int
        Step size in mm.
    fix_freesurfer_subcortical: bool, default False
        If the <t1_parc> is aparc+aseg or aparc.a2009s+aseg from FreeSurfer,
        set this option to True, to recompute the subcortical segmentations
        of the 5 structures that are uncorrectly segmented by FreeSurfer,
        using FSL FIRST.
    subjects_dir: str or None, default None
        Path to the FreeSurfer subjects directory. Required if the FreeSurfer
        environment variable (i.e. $SUBJECTS_DIR) is not set.
    cthr: float, optional
        Probtrackx2 option.
    fibthresh, distthresh, sampvox: float, optional
        Probtrackx2 options.
    loopcheck: bool, optional
        Probtrackx2 option.
    snapshots: bool, default True
        If True, create PNG snapshots for QC.
    fs_sh: str, default NeuroSpin path
        Path to the Bash script setting the FreeSurfer environment
    fsl_sh: str, default NeuroSpin path
        Path to the Bash script setting the FSL environment.

    Returns
    -------
    connectome_file: str
        The generated connectome.
    labels_file: str
        The coonectome associated labels.
    connectome_snap_file: str
        A grphical representation of the connectome.
    """
    # -------------------------------------------------------------------------
    # STEP 0 - Check arguments

    # FreeSurfer subjects_dir
    subjects_dir = get_or_check_freesurfer_subjects_dir(subjects_dir)

    if connectome_lut.lower() == "lausanne2008":
        module_dir = os.path.dirname(os.path.abspath(__file__))
        connectome_lut = os.path.join(module_dir, "Lausanne2008LUT.txt")

    # Check input paths
    paths_to_check = [t1_parc, t1_parc_lut, connectome_lut, nodif_brain,
                      nodif_brain_mask, bedpostx_dir, fs_sh, fsl_sh]
    for p in paths_to_check:
        if not os.path.exists(p):
            raise ValueError("File or directory does not exist: %s" % p)

    # Create <outdir> and/or <tempdir> if not existing
    for directory in [outdir, tempdir]:
        if not os.path.isdir(directory):
            os.makedirs(directory)

    # -------------------------------------------------------------------------
    # STEP 1 - Compute T1 <-> DWI rigid transformation

    # FreeSurfer T1 to Nifti
    fs_t1_brain = os.path.join(subjects_dir, subject_id, "mri", "brain.mgz")
    t1_brain = os.path.join(outdir, "t1_brain.nii.gz")
    cmd_1a = ["mri_convert", fs_t1_brain, t1_brain]
    FSWrapper(cmd_1a, shfile=fs_sh)()

    # Register diffusion to T1
    _, dif2anat_dat, dif2anat_mat = freesurfer_bbregister_t1todif(
            outdir=outdir,
            subject_id=subject_id,
            nodif_brain=nodif_brain,
            subjects_dir=subjects_dir,
            fs_sh=fs_sh,
            fsl_sh=fsl_sh)

    # Invert dif2anat transform
    m = numpy.loadtxt(dif2anat_mat)
    m_inv = numpy.linalg.inv(m)
    anat2dif_mat = os.path.join(outdir, "anat2dif.mat")
    numpy.savetxt(anat2dif_mat, m_inv)

    # -------------------------------------------------------------------------
    # STEP 2 - Convert LUT
    # Change integer labels in the LUT so that the each label corresponds
    # to the row/col position in the connectome
    nodes = os.path.join(outdir, "nodes.nii.gz")
    cmd_2 = ["labelconvert", t1_parc, t1_parc_lut, connectome_lut, nodes,
             "-nthreads", "0", "-failonwarn"]
    subprocess.check_call(cmd_2)

    # -------------------------------------------------------------------------
    # STEP 3 - If the T1 parcellation is aparc+aseg or aparc.a2009s+aseg
    # from FreeSurfer, this option allows the recompute the subcortical
    # segmentations of 5 structures that are uncorrectly segmented by
    # FreeSurfer, using FSL FIRST
    if fix_freesurfer_subcortical:
        fixed_nodes = os.path.join(outdir, "nodes_fixSGM.nii.gz")
        nodes = fix_freesurfer_subcortical_parcellation(parc=nodes,
                                                        t1_brain=t1_brain,
                                                        lut=connectome_lut,
                                                        output=fixed_nodes,
                                                        tempdir=tempdir,
                                                        nb_threads=0,
                                                        fsl_sh=fsl_sh)

    # -------------------------------------------------------------------------
    # STEP 4 - Create the masks for Probtrackx2

    # White matter mask
    aparc_aseg = os.path.join(subjects_dir, subject_id, "mri",
                              "aparc+aseg.mgz")
    wm_mask = os.path.join(outdir, "wm_mask.nii.gz")
    cmd_4a = ["mri_binarize",
              "--i", aparc_aseg,
              "--o", wm_mask,
              "--wm"]
    FSWrapper(cmd_4a, shfile=fs_sh)()

    # Stop mask is inverse of white matter mask
    stop_mask = os.path.join(outdir, "inv_wm_mask.nii.gz")
    cmd_4b = ["mri_binarize",
              "--i", aparc_aseg,
              "--o", stop_mask,
              "--wm", "--inv"]
    FSWrapper(cmd_4b, shfile=fs_sh)()

    # Create target mask: a mask of all nodes
    target_mask = os.path.join(outdir, "target_mask.nii.gz")
    cmd_4c = ["mri_binarize",
              "--i",   nodes,
              "--o",   target_mask,
              "--min", "1"]
    FSWrapper(cmd_4c, shfile=fs_sh)()

    # Dilate target mask by one voxel (12-connexity)
    target_mask_dil = os.path.join(outdir, "target_mask_dilated.nii.gz")
    cmd_4d = ["mri_morphology", target_mask, "dilate", "1", target_mask_dil]
    FSWrapper(cmd_4d, shfile=fs_sh)()

    # Create seed mask: white matter voxels near nodes (target regions)
    # Intersect dilated target mask and white matter mask
    # -> white matter voxels neighbor (12-connectivity) to node voxels
    seed_mask = os.path.join(outdir, "wm_nodes_interface_mask.nii.gz")
    cmd_4e = ["mri_and", wm_mask, target_mask_dil, seed_mask]
    FSWrapper(cmd_4e, shfile=fs_sh)()

    # -------------------------------------------------------------------------
    # STEP 7 - Run Probtrackx2
    probtrackx2_dir = os.path.join(outdir, "probtrackx2")
    probtrackx2(dir=probtrackx2_dir,
                forcedir=True,
                seedref=t1_brain,
                xfm=anat2dif_mat,
                invxfm=dif2anat_mat,
                samples=os.path.join(bedpostx_dir, "merged"),
                mask=nodif_brain_mask,
                seed=seed_mask,
                omatrix3=True,
                target3=nodes,
                stop=stop_mask,
                nsamples=nsamples,
                nsteps=nsteps,
                steplength=steplength,
                loopcheck=loopcheck,
                cthr=cthr,
                fibthresh=fibthresh,
                distthresh=distthresh,
                sampvox=sampvox,
                shfile=fsl_sh)

    # ------------------------------------------------------------------------
    # STEP 8 - Create NODExNODE connectivity matrix for nodes from <t1_parc>
    connectome_file, labels_file = voxel_to_node_connectivity(
        probtrackx2_dir=probtrackx2_dir,
        nodes=nodes,
        connectome_lut=connectome_lut,
        outdir=outdir)

    # ------------------------------------------------------------------------
    # STEP 9 - Create a connectome snapshot if requested
    if snapshots:
        connectome_snap_file = os.path.join(outdir, "connectome.png")
        connectome_snapshot(connectome_file, connectome_snap_file,
                            labels=labels_file, transform=numpy.log1p, dpi=300,
                            labels_size=4, colorbar_title="log(# of tracks)")

    return connectome_file, labels_file, connectome_snap_file