Esempio n. 1
0
def create_spherical_roi_volumes(node_size, coords, template_mask):
    """
    Create volume ROI mask of spheres from a given set of coordinates and radius.

    Parameters
    ----------
    node_size : int
        Spherical centroid node size in the case that coordinate-based centroids
        are used as ROI's for tracking.
    coords : list
        List of (x, y, z) tuples in mm-space corresponding to a coordinate atlas used or
        which represent the center-of-mass of each parcellation node.
    template_mask : str
        Path to binarized version of standard (MNI)-space template Nifti1Image file.

    Returns
    -------
    parcel_list : list
        List of 3D boolean numpy arrays or binarized Nifti1Images corresponding to ROI masks.
    par_max : int
        The maximum label intensity in the parcellation image.
    node_size : int
        Spherical centroid node size in the case that coordinate-based centroids
        are used as ROI's for tracking.
    parc : bool
        Indicates whether to use the raw parcels as ROI nodes instead of coordinates at their center-of-mass.
    """
    from pynets.core.nodemaker import get_sphere, mmToVox
    mask_img = nib.load(template_mask)
    mask_aff = mask_img.affine
    mask_shape = mask_img.shape
    mask_img.uncache()

    print("%s%s" % ('Creating spherical ROI atlas with radius: ', node_size))

    coords_vox = []
    for i in coords:
        coords_vox.append(mmToVox(mask_aff, i))
    coords_vox = list(set(list(tuple(x) for x in coords_vox)))

    x_vox = np.diagonal(mask_aff[:3, 0:3])[0]
    y_vox = np.diagonal(mask_aff[:3, 0:3])[1]
    z_vox = np.diagonal(mask_aff[:3, 0:3])[2]
    sphere_vol = np.zeros(mask_shape, dtype=bool)
    parcel_list = []
    i = 0
    for coord in coords_vox:
        sphere_vol[tuple(get_sphere(coord, node_size, (np.abs(x_vox), y_vox, z_vox), mask_shape).T)] = i * 1
        parcel_list.append(nib.Nifti1Image(sphere_vol.astype('uint16'), affine=mask_aff))
        i = i + 1

    par_max = len(coords)
    if par_max > 0:
        parc = True
    else:
        raise ValueError('Number of nodes is zero.')

    return parcel_list, par_max, node_size, parc
Esempio n. 2
0
def test_get_sphere():
    """
    Test get_sphere functionality
    """
    base_dir = str(Path(__file__).parent / "examples")
    img_file = f"{base_dir}/BIDS/sub-0025427/ses-1/func/sub-0025427_ses-1_task-rest_space-MNI152NLin2009cAsym_desc-brain_mask.nii.gz"
    img = nib.load(img_file)
    r = 4
    vox_dims = (2.0, 2.0, 2.0)
    coords = [[0, 0, 0], [-5, -5, -5], [5, 5, 5], [-10, -10, -10],
              [10, 10, 10]]
    neighbors = []
    for coord in coords:
        neighbors.append(
            nodemaker.get_sphere(coord, r, vox_dims, img.shape[0:3]))
    neighbors = [i for i in neighbors if len(i) > 0]
    assert len(neighbors) == 3
Esempio n. 3
0
def test_get_sphere():
    """
    Test get_sphere functionality
    """
    img_file = pkg_resources.resource_filename(
        "pynets", f"templates/standard/MNI152_T1_brain_2mm.nii.gz")
    img = nib.load(img_file)
    r = 4
    vox_dims = (2.0, 2.0, 2.0)
    coords = [[0, 0, 0], [-5, -5, -5], [5, 5, 5], [-10, -10, -10],
              [10, 10, 10]]
    neighbors = []
    for coord in coords:
        neighbors.append(
            nodemaker.get_sphere(coord, r, vox_dims, img.shape[0:3]))
    neighbors = [i for i in neighbors if len(i) > 0]
    assert len(neighbors) == 3
Esempio n. 4
0
def test_get_sphere():
    """
    Test get_sphere functionality
    """
    base_dir = str(Path(__file__).parent/"examples")
    dir_path = base_dir + '/002/dmri'
    img_file = dir_path + '/nodif_b0_bet.nii.gz'
    img = nib.load(img_file)
    r = 4
    vox_dims = (2.0, 2.0, 2.0)
    coords_file = dir_path + '/DesikanKlein2012/Default_coords_rsn.pkl'
    with open(coords_file, 'rb') as file_:
        coords = pickle.load(file_)
    neighbors = []
    for coord in coords:
        neighbors.append(nodemaker.get_sphere(coord, r, vox_dims, img.shape[0:3]))
    neighbors = [i for i in neighbors if len(i)>0]
    assert len(neighbors) == 4
Esempio n. 5
0
def streams2graph(atlas_mni, streams, dir_path, track_type, target_samples,
                  conn_model, network, node_size, dens_thresh, ID, roi,
                  min_span_tree, disp_filt, parc, prune, atlas, uatlas, labels,
                  coords, norm, binary, directget, warped_fa, min_length,
                  error_margin):
    """
    Use tracked streamlines as a basis for estimating a structural connectome.

    Parameters
    ----------
    atlas_mni : str
        File path to atlas parcellation Nifti1Image in T1w-warped MNI space.
    streams : str
        File path to streamline array sequence in .trk format.
    dir_path : str
        Path to directory containing subject derivative data for a given
        pynets run.
    track_type : str
        Tracking algorithm used (e.g. 'local' or 'particle').
    target_samples : int
        Total number of streamline samples specified to generate streams.
    conn_model : str
        Connectivity reconstruction method (e.g. 'csa', 'tensor', 'csd').
    network : str
        Resting-state network based on Yeo-7 and Yeo-17 naming (e.g. 'Default')
        used to filter nodes in the study of brain subgraphs.
    node_size : int
        Spherical centroid node size in the case that coordinate-based
        centroids are used as ROI's for tracking.
    dens_thresh : bool
        Indicates whether a target graph density is to be used as the basis for
        thresholding.
    ID : str
        A subject id or other unique identifier.
    roi : str
        File path to binarized/boolean region-of-interest Nifti1Image file.
    min_span_tree : bool
        Indicates whether local thresholding from the Minimum Spanning Tree
        should be used.
    disp_filt : bool
        Indicates whether local thresholding using a disparity filter and
        'backbone network' should be used.
    parc : bool
        Indicates whether to use parcels instead of coordinates as ROI nodes.
    prune : bool
        Indicates whether to prune final graph of disconnected nodes/isolates.
    atlas : str
        Name of atlas parcellation used.
    uatlas : str
        File path to atlas parcellation Nifti1Image in MNI template space.
    labels : list
        List of string labels corresponding to graph nodes.
    coords : list
        List of (x, y, z) tuples corresponding to a coordinate atlas used or
        which represent the center-of-mass of each parcellation node.
    norm : int
        Indicates method of normalizing resulting graph.
    binary : bool
        Indicates whether to binarize resulting graph edges to form an
        unweighted graph.
    directget : str
        The statistical approach to tracking. Options are:
        det (deterministic), closest (clos), boot (bootstrapped),
        and prob (probabilistic).
    warped_fa : str
        File path to MNI-space warped FA Nifti1Image.
    min_length : int
        Minimum fiber length threshold in mm to restrict tracking.
    error_margin : int
        Euclidean margin of error for classifying a streamline as a connection
         to an ROI. Default is 2 voxels.

    Returns
    -------
    atlas_mni : str
        File path to atlas parcellation Nifti1Image in T1w-warped MNI space.
    streams : str
        File path to streamline array sequence in .trk format.
    conn_matrix : array
        Adjacency matrix stored as an m x n array of nodes and edges.
    track_type : str
        Tracking algorithm used (e.g. 'local' or 'particle').
    target_samples : int
        Total number of streamline samples specified to generate streams.
    dir_path : str
        Path to directory containing subject derivative data for given run.
    conn_model : str
        Connectivity reconstruction method (e.g. 'csa', 'tensor', 'csd').
    network : str
        Resting-state network based on Yeo-7 and Yeo-17 naming (e.g. 'Default')
        used to filter nodes in the study of brain subgraphs.
    node_size : int
        Spherical centroid node size in the case that coordinate-based
        centroids are used as ROI's for tracking.
    dens_thresh : bool
        Indicates whether a target graph density is to be used as the basis for
        thresholding.
    ID : str
        A subject id or other unique identifier.
    roi : str
        File path to binarized/boolean region-of-interest Nifti1Image file.
    min_span_tree : bool
        Indicates whether local thresholding from the Minimum Spanning Tree
        should be used.
    disp_filt : bool
        Indicates whether local thresholding using a disparity filter and
        'backbone network' should be used.
    parc : bool
        Indicates whether to use parcels instead of coordinates as ROI nodes.
    prune : bool
        Indicates whether to prune final graph of disconnected nodes/isolates.
    atlas : str
        Name of atlas parcellation used.
    uatlas : str
        File path to atlas parcellation Nifti1Image in MNI template space.
    labels : list
        List of string labels corresponding to graph nodes.
    coords : list
        List of (x, y, z) tuples corresponding to a coordinate atlas used or
        which represent the center-of-mass of each parcellation node.
    norm : int
        Indicates method of normalizing resulting graph.
    binary : bool
        Indicates whether to binarize resulting graph edges to form an
        unweighted graph.
    directget : str
        The statistical approach to tracking. Options are: det (deterministic),
        closest (clos), boot (bootstrapped), and prob (probabilistic).
    min_length : int
        Minimum fiber length threshold in mm to restrict tracking.
    error_margin : int
        Euclidean margin of error for classifying a streamline as a connection
         to an ROI. Default is 2 voxels.

    References
    ----------
    .. [1] Sporns, O., Tononi, G., & Kötter, R. (2005). The human connectome:
      A structural description of the human brain. PLoS Computational Biology.
      https://doi.org/10.1371/journal.pcbi.0010042
    .. [2] Sotiropoulos, S. N., & Zalesky, A. (2019). Building connectomes
      using diffusion MRI: why, how and but. NMR in Biomedicine.
      https://doi.org/10.1002/nbm.3752
    .. [3] Chung, M. K., Hanson, J. L., Adluru, N., Alexander, A. L., Davidson,
      R. J., & Pollak, S. D. (2017). Integrative Structural Brain Network
      Analysis in Diffusion Tensor Imaging. Brain Connectivity.
      https://doi.org/10.1089/brain.2016.0481
    """
    import gc
    import time
    import pkg_resources
    import sys
    import yaml
    from dipy.tracking.streamline import Streamlines, values_from_volume
    from dipy.tracking._utils import _mapping_to_voxel, _to_voxel_coordinates
    import networkx as nx
    from itertools import combinations
    from collections import defaultdict
    from pynets.core import utils, nodemaker
    from pynets.dmri.dmri_utils import generate_sl
    from dipy.io.streamline import load_tractogram
    from dipy.io.stateful_tractogram import Space, Origin

    with open(pkg_resources.resource_filename("pynets", "runconfig.yaml"),
              "r") as stream:
        hardcoded_params = yaml.load(stream)
        fa_wei = hardcoded_params["StructuralNetworkWeighting"][
            "fa_weighting"][0]
        fiber_density = hardcoded_params["StructuralNetworkWeighting"][
            "fiber_density"][0]
        overlap_thr = hardcoded_params["StructuralNetworkWeighting"][
            "overlap_thr"][0]
        roi_neighborhood_tol = \
        hardcoded_params['tracking']["roi_neighborhood_tol"][0]
    stream.close()

    start = time.time()

    if float(roi_neighborhood_tol) <= float(error_margin):
        try:
            raise ValueError('roi_neighborhood_tol preset cannot be less than '
                             'the value of the structural connectome error'
                             '_margin parameter.')
        except ValueError:
            import sys
            sys.exit(1)
    else:
        print(f"Using fiber-roi intersection tolerance: {error_margin}...")

    # Load FA
    fa_img = nib.load(warped_fa)

    # Load parcellation
    roi_img = nib.load(atlas_mni)
    atlas_data = np.around(np.asarray(roi_img.dataobj))
    roi_zooms = roi_img.header.get_zooms()
    roi_shape = roi_img.shape

    # Read Streamlines
    streamlines = [
        i.astype(np.float32) for i in Streamlines(
            load_tractogram(
                streams, fa_img, to_origin=Origin.NIFTI,
                to_space=Space.VOXMM).streamlines)
    ]

    # from fury import actor, window
    # renderer = window.Renderer()
    # template_actor = actor.contour_from_roi(roi_img.get_fdata(),
    #                                         color=(50, 50, 50), opacity=0.05)
    # renderer.add(template_actor)
    # lines_actor = actor.streamtube(streamlines, window.colors.orange,
    #                                linewidth=0.3)
    # renderer.add(lines_actor)
    # window.show(renderer)

    roi_img.uncache()

    if fa_wei is True:
        fa_weights = values_from_volume(
            np.asarray(fa_img.dataobj, dtype=np.float32), streamlines,
            np.eye(4))
        global_fa_weights = list(utils.flatten(fa_weights))
        min_global_fa_wei = min([i for i in global_fa_weights if i > 0])
        max_global_fa_wei = max(global_fa_weights)
        fa_weights_norm = []
        # Here we normalize by global FA
        for val_list in fa_weights:
            fa_weights_norm.append(
                np.nanmean((val_list - min_global_fa_wei) /
                           (max_global_fa_wei - min_global_fa_wei)))

    # Make streamlines into generators to keep memory at a minimum
    total_streamlines = len(streamlines)
    sl = [generate_sl(i) for i in streamlines]
    del streamlines

    # Instantiate empty networkX graph object & dictionary and create
    # voxel-affine mapping
    lin_T, offset = _mapping_to_voxel(np.eye(4))
    mx = len(np.unique(atlas_data.astype("uint16"))) - 1
    g = nx.Graph(ecount=0, vcount=mx)
    edge_dict = defaultdict(int)
    node_dict = dict(
        zip(np.unique(atlas_data.astype("uint16"))[1:],
            np.arange(mx) + 1))

    # Add empty vertices with label volume attributes
    for node in range(1, mx + 1):
        g.add_node(node,
                   roi_volume=np.sum(atlas_data.astype("uint16") == node))

    # Build graph
    pc = 0
    bad_idxs = []
    fiberlengths = {}
    fa_weights_dict = {}
    print(f"Quantifying fiber-ROI intersection for {atlas}:")
    for ix, s in enumerate(sl):
        # Percent counter
        pcN = int(round(100 * float(ix / total_streamlines)))
        if pcN % 10 == 0 and ix > 0 and pcN > pc:
            pc = pcN
            print(f"{pcN}%")

        # Map the streamlines coordinates to voxel coordinates and get labels
        # for label_volume
        vox_coords = _to_voxel_coordinates(Streamlines(s), lin_T, offset)

        lab_coords = [
            nodemaker.get_sphere(coord, error_margin, roi_zooms, roi_shape)
            for coord in vox_coords
        ]
        [i, j, k] = np.vstack(np.array(lab_coords)).T

        # get labels for label_volume
        lab_arr = atlas_data[i, j, k]
        # print(lab_arr)
        endlabels = []
        for jx, lab in enumerate(np.unique(lab_arr).astype("uint32")):
            if (lab > 0) and (np.sum(lab_arr == lab) >= overlap_thr):
                try:
                    endlabels.append(node_dict[lab])
                except BaseException:
                    bad_idxs.append(jx)
                    print(f"Label {lab} missing from parcellation. Check "
                          f"registration and ensure valid input parcellation "
                          f"file.")

        edges = combinations(endlabels, 2)
        for edge in edges:
            # Get fiber lengths along edge
            if fiber_density is True:
                if not (edge[0], edge[1]) in fiberlengths.keys():
                    fiberlengths[(edge[0], edge[1])] = [len(vox_coords)]
                else:
                    fiberlengths[(edge[0], edge[1])].append(len(vox_coords))

            # Get FA values along edge
            if fa_wei is True:
                if not (edge[0], edge[1]) in fa_weights_dict.keys():
                    fa_weights_dict[(edge[0], edge[1])] = [fa_weights_norm[ix]]
                else:
                    fa_weights_dict[(edge[0],
                                     edge[1])].append(fa_weights_norm[ix])

            lst = tuple([int(node) for node in edge])
            edge_dict[tuple(sorted(lst))] += 1

        edge_list = [(k[0], k[1], count) for k, count in edge_dict.items()]

        g.add_weighted_edges_from(edge_list)

        del lab_coords, lab_arr, endlabels, edges, edge_list

    gc.collect()

    # Add fiber density attributes for each edge
    # Adapted from the nnormalized fiber-density estimation routines of
    # Sebastian Tourbier.
    if fiber_density is True:
        print("Weighting edges by fiber density...")
        # Summarize total fibers and total label volumes
        total_fibers = 0
        total_volume = 0
        u_start = -1
        for u, v, d in g.edges(data=True):
            total_fibers += len(d)
            if u != u_start:
                total_volume += g.nodes[int(u)]['roi_volume']
            u_start = u

        ix = 0
        for u, v, d in g.edges(data=True):
            if d['weight'] > 0:
                edge_fiberlength_mean = np.nanmean(fiberlengths[(u, v)])
                fiber_density = (float(
                    ((float(d['weight']) / float(total_fibers)) /
                     float(edge_fiberlength_mean)) *
                    ((2.0 * float(total_volume)) /
                     (g.nodes[int(u)]['roi_volume'] +
                      g.nodes[int(v)]['roi_volume'])))) * 1000
            else:
                fiber_density = 0
            g.edges[u, v].update({"fiber_density": fiber_density})
            ix += 1

    if fa_wei is True:
        print("Weighting edges by FA...")
        # Add FA attributes for each edge
        ix = 0
        for u, v, d in g.edges(data=True):
            if d['weight'] > 0:
                edge_average_fa = np.nanmean(fa_weights_dict[(u, v)])
            else:
                edge_average_fa = np.nan
            g.edges[u, v].update({"fa_weight": edge_average_fa})
            ix += 1

    # Summarize weights
    if fa_wei is True and fiber_density is True:
        for u, v, d in g.edges(data=True):
            g.edges[u, v].update(
                {"final_weight": (d['fa_weight']) * d['fiber_density']})
    elif fiber_density is True and fa_wei is False:
        for u, v, d in g.edges(data=True):
            g.edges[u, v].update({"final_weight": d['fiber_density']})
    elif fa_wei is True and fiber_density is False:
        for u, v, d in g.edges(data=True):
            g.edges[u,
                    v].update({"final_weight": d['fa_weight'] * d['weight']})
    else:
        for u, v, d in g.edges(data=True):
            g.edges[u, v].update({"final_weight": d['weight']})

    # Convert weighted graph to numpy matrix
    conn_matrix_raw = nx.to_numpy_array(g, weight='final_weight')

    # Enforce symmetry
    conn_matrix = np.maximum(conn_matrix_raw, conn_matrix_raw.T)

    print("Structural graph completed:\n", str(time.time() - start))

    if len(bad_idxs) > 0:
        bad_idxs = sorted(list(set(bad_idxs)), reverse=True)
        for j in bad_idxs:
            del labels[j], coords[j]

    coords = np.array(coords)
    labels = np.array(labels)

    assert len(coords) == len(labels) == conn_matrix.shape[0]

    return (atlas_mni, streams, conn_matrix, track_type, target_samples,
            dir_path, conn_model, network, node_size, dens_thresh, ID, roi,
            min_span_tree, disp_filt, parc, prune, atlas, uatlas, labels,
            coords, norm, binary, directget, min_length, error_margin)
Esempio n. 6
0
def coords_masker(roi, coords, labels, error):
    """
    Evaluate the affinity of any arbitrary list of coordinate nodes for a user-specified ROI mask.

    Parameters
    ----------
    roi : str
        File path to binarized/boolean region-of-interest Nifti1Image file.
    coords : list
        List of (x, y, z) tuples in mm-space corresponding to a coordinate atlas used or
        which represent the center-of-mass of each parcellation node.
    labels : list
        List of string labels corresponding to ROI nodes.
    error : int
        Rounded euclidean distance, in units of voxel number, to use as a spatial error cushion in the case of
        evaluating the spatial affinity of a given list of coordinates to the given ROI mask.

    Returns
    -------
    coords : list
        Filtered list of (x, y, z) tuples in mm-space with a spatial affinity for the specified ROI mask.
    labels : list
        Filtered list of string labels corresponding to ROI nodes with a spatial affinity for the specified ROI mask.
    """
    from nilearn import masking
    from pynets.core.nodemaker import mmToVox

    mask_data, mask_aff = masking._load_mask_img(roi)
    x_vox = np.diagonal(mask_aff[:3, 0:3])[0]
    y_vox = np.diagonal(mask_aff[:3, 0:3])[1]
    z_vox = np.diagonal(mask_aff[:3, 0:3])[2]

    #    mask_coords = list(zip(*np.where(mask_data == True)))
    coords_vox = []
    for i in coords:
        coords_vox.append(mmToVox(mask_aff, i))
    coords_vox = list(
        tuple(map(lambda y: isinstance(y, float) and int(round(y, 0)), x))
        for x in coords_vox)
    #coords_vox = list(set(list(tuple(x) for x in coords_vox)))
    bad_coords = []
    for coord_vox in coords_vox:
        sphere_vol = np.zeros(mask_data.shape, dtype=bool)
        sphere_vol[tuple(coord_vox)] = 1
        if (mask_data & sphere_vol).any():
            print(f"{coord_vox}{' falls within mask...'}")
            continue
        inds = get_sphere(coord_vox, error, (np.abs(x_vox), y_vox, z_vox),
                          mask_data.shape)
        sphere_vol[tuple(inds.T)] = 1
        if (mask_data & sphere_vol).any():
            print(
                f"{coord_vox}{' is within a + or - '}{float(error):.2f}{' mm neighborhood...'}"
            )
            continue
        bad_coords.append(coord_vox)

    bad_coords = [x for x in bad_coords if x is not None]
    indices = []
    for bad_coords in bad_coords:
        indices.append(coords_vox.index(bad_coords))

    labels = list(labels)
    coords = list(tuple(x) for x in coords)
    try:
        for ix in sorted(indices, reverse=True):
            print(f"{'Removing: '}{labels[ix]}{' at '}{coords[ix]}")
            del labels[ix], coords[ix]

    except RuntimeError:
        print(
            'ERROR: Restrictive masking. No coords remain after masking with brain mask/roi...'
        )

    if len(coords) <= 1:
        raise ValueError(
            '\nERROR: ROI mask was likely too restrictive and yielded < 2 remaining coords'
        )

    return coords, labels
Esempio n. 7
0
def get_node_membership(network,
                        infile,
                        coords,
                        labels,
                        parc,
                        parcel_list,
                        perc_overlap=0.75,
                        error=4):
    """
    Evaluate the affinity of any arbitrary list of coordinate or parcel nodes for a user-specified RSN based on
    Yeo-7 or Yeo-17 definitions.

    Parameters
    ----------
    network : str
        Resting-state network based on Yeo-7 and Yeo-17 naming (e.g. 'Default') used to filter nodes in the study of
        brain subgraphs.
    infile : str
        File path to Nifti1Image object whose affine will provide sampling reference for evaluation spatial proximity.
    coords : list
        List of (x, y, z) tuples in mm-space corresponding to a coordinate atlas used or
        which represent the center-of-mass of each parcellation node.
    labels : list
        List of string labels corresponding to ROI nodes.
    parc : bool
        Indicates whether to use parcels instead of coordinates as ROI nodes.
    parcel_list : list
        List of 3D boolean numpy arrays or binarized Nifti1Images corresponding to ROI masks.
    perc_overlap : float
        Value 0-1 indicating a threshold of spatial overlap to use as a spatial error cushion in the case of
        evaluating RSN membership from a given list of parcel masks. Default is 0.75.
    error : int
        Rounded euclidean distance, in units of voxel number, to use as a spatial error cushion in the case of
        evaluating RSN membership from a given list of coordinates. Default is 4.

    Returns
    -------
    coords_mm : list
        Filtered list of (x, y, z) tuples in mm-space with a spatial affinity for the specified RSN.
    RSN_parcels : list
        Filtered list of 3D boolean numpy arrays or binarized Nifti1Images corresponding to ROI masks with a spatial
        affinity for the specified RSN.
    net_labels : list
        Filtered list of string labels corresponding to ROI nodes with a spatial affinity for the specified RSN.
    network : str
        Resting-state network based on Yeo-7 and Yeo-17 naming (e.g. 'Default') used to filter nodes in the study of
        brain subgraphs.

    References
    ----------
    .. [1] Thomas Yeo, B. T., Krienen, F. M., Sepulcre, J., Sabuncu, M. R., Lashkari, D.,
      Hollinshead, M., … Buckner, R. L. (2011). The organization of the human cerebral
      cortex estimated by intrinsic functional connectivity. Journal of Neurophysiology.
      https://doi.org/10.1152/jn.00338.2011
    .. [2] Schaefer A, Kong R, Gordon EM, Laumann TO, Zuo XN, Holmes AJ, Eickhoff SB, Yeo BTT.
      Local-Global parcellation of the human cerebral cortex from intrinsic functional
      connectivity MRI, Cerebral Cortex, 29:3095-3114, 2018.

    """
    from nilearn.image import resample_img
    from pynets.core.nodemaker import get_sphere, mmToVox, VoxTomm
    import pkg_resources
    import pandas as pd

    # Determine whether input is from 17-networks or 7-networks
    seven_nets = [
        'Vis', 'SomMot', 'DorsAttn', 'SalVentAttn', 'Limbic', 'Cont', 'Default'
    ]
    seventeen_nets = [
        'VisCent', 'VisPeri', 'SomMotA', 'SomMotB', 'DorsAttnA', 'DorsAttnB',
        'SalVentAttnA', 'SalVentAttnB', 'LimbicOFC', 'LimbicTempPole', 'ContA',
        'ContB', 'ContC', 'DefaultA', 'DefaultB', 'DefaultC', 'TempPar'
    ]

    # Load subject func data
    bna_img = nib.load(infile)
    bna_aff = bna_img.affine
    bna_img.uncache()
    x_vox = np.diagonal(bna_aff[:3, 0:3])[0]
    y_vox = np.diagonal(bna_aff[:3, 0:3])[1]
    z_vox = np.diagonal(bna_aff[:3, 0:3])[2]

    if network in seventeen_nets:
        if x_vox <= 1 and y_vox <= 1 and z_vox <= 1:
            par_file = pkg_resources.resource_filename(
                "pynets", "rsnrefs/BIGREF1mm.nii.gz")
        else:
            par_file = pkg_resources.resource_filename(
                "pynets", "rsnrefs/BIGREF2mm.nii.gz")

        # Grab RSN reference file
        nets_ref_txt = pkg_resources.resource_filename(
            "pynets", "rsnrefs/Schaefer2018_1000_17nets_ref.txt")
    elif network in seven_nets:
        if x_vox <= 1 and y_vox <= 1 and z_vox <= 1:
            par_file = pkg_resources.resource_filename(
                "pynets", "rsnrefs/SMALLREF1mm.nii.gz")
        else:
            par_file = pkg_resources.resource_filename(
                "pynets", "rsnrefs/SMALLREF2mm.nii.gz")

        # Grab RSN reference file
        nets_ref_txt = pkg_resources.resource_filename(
            "pynets", "rsnrefs/Schaefer2018_1000_7nets_ref.txt")
    else:
        nets_ref_txt = None

    if not nets_ref_txt:
        raise ValueError(
            f"Network: {str(network)} not found!\nSee valid network names using the `--help` flag with "
            f"`pynets`")

    # Create membership dictionary
    dict_df = pd.read_csv(nets_ref_txt,
                          sep="\t",
                          header=None,
                          names=["Index", "Region", "X", "Y", "Z"])
    dict_df.Region.unique().tolist()
    ref_dict = {v: k for v, k in enumerate(dict_df.Region.unique().tolist())}
    par_img = nib.load(par_file)
    RSN_ix = list(ref_dict.keys())[list(ref_dict.values()).index(network)]
    RSNmask = np.asarray(par_img.dataobj)[:, :, :, RSN_ix]

    coords_vox = []
    for i in coords:
        coords_vox.append(mmToVox(bna_aff, i))
    coords_vox = list(
        tuple(map(lambda y: isinstance(y, float) and int(round(y, 0)), x))
        for x in coords_vox)
    #coords_vox = list(set(list(tuple(x) for x in coords_vox)))
    if parc is False:
        i = -1
        RSN_parcels = None
        RSN_coords_vox = []
        net_labels = []
        for coords in coords_vox:
            sphere_vol = np.zeros(RSNmask.shape, dtype=bool)
            sphere_vol[tuple(coords)] = 1
            i = i + 1
            if (RSNmask.astype('bool') & sphere_vol).any():
                print(f"{coords}{' coords falls within '}{network}{'...'}")
                RSN_coords_vox.append(coords)
                net_labels.append(labels[i])
                continue
            else:
                inds = get_sphere(coords, error, (np.abs(x_vox), y_vox, z_vox),
                                  RSNmask.shape)
                sphere_vol[tuple(inds.T)] = 1
                if (RSNmask.astype('bool') & sphere_vol).any():
                    print(
                        f"{coords} coords is within a + or - {float(error):.2f} mm neighborhood of {network}..."
                    )
                    RSN_coords_vox.append(coords)
                    net_labels.append(labels[i])

        coords_mm = []
        for i in RSN_coords_vox:
            coords_mm.append(VoxTomm(bna_aff, i))
        coords_mm = list(set(list(tuple(x) for x in coords_mm)))
    else:
        i = 0
        RSN_parcels = []
        coords_with_parc = []
        net_labels = []
        for parcel in parcel_list:
            parcel_vol = np.zeros(RSNmask.shape, dtype=bool)
            parcel_vol[np.asarray(
                resample_img(parcel,
                             target_affine=par_img.affine,
                             target_shape=RSNmask.shape).dataobj) == 1] = 1

            # Count number of unique voxels where overlap of parcel and mask occurs
            overlap_count = len(
                np.unique(
                    np.where((RSNmask.astype('uint16') == 1)
                             & (parcel_vol.astype('uint16') == 1))))

            # Count number of total unique voxels within the parcel
            total_count = len(
                np.unique(np.where((parcel_vol.astype('uint16') == 1))))

            # Calculate % overlap
            try:
                overlap = float(overlap_count / total_count)
            except RuntimeWarning:
                print('\nWarning: No overlap with roi mask!\n')
                overlap = float(0)

            if overlap >= perc_overlap:
                print(
                    f"{100 * overlap:.2f}% of parcel {labels[i]} falls within {str(network)} mask..."
                )
                RSN_parcels.append(parcel)
                coords_with_parc.append(coords[i])
                net_labels.append(labels[i])
            i = i + 1
        coords_mm = list(set(list(tuple(x) for x in coords_with_parc)))

    par_img.uncache()

    if len(coords_mm) <= 1:
        raise ValueError(
            f"\nERROR: No coords from the specified atlas found within {network} network."
        )

    return coords_mm, RSN_parcels, net_labels, network
Esempio n. 8
0
def streams2graph(atlas_mni,
                  streams,
                  overlap_thr,
                  dir_path,
                  track_type,
                  target_samples,
                  conn_model,
                  network,
                  node_size,
                  dens_thresh,
                  ID,
                  roi,
                  min_span_tree,
                  disp_filt,
                  parc,
                  prune,
                  atlas,
                  uatlas,
                  labels,
                  coords,
                  norm,
                  binary,
                  directget,
                  warped_fa,
                  error_margin,
                  min_length,
                  fa_wei=True):
    '''
    Use tracked streamlines as a basis for estimating a structural connectome.

    Parameters
    ----------
    atlas_mni : str
        File path to atlas parcellation Nifti1Image in T1w-warped MNI space.
    streams : str
        File path to streamline array sequence in .trk format.
    overlap_thr : int
        Number of voxels for which a given streamline must intersect with an ROI
        for an edge to be counted.
    dir_path : str
        Path to directory containing subject derivative data for a given pynets run.
    track_type : str
        Tracking algorithm used (e.g. 'local' or 'particle').
    target_samples : int
        Total number of streamline samples specified to generate streams.
    conn_model : str
        Connectivity reconstruction method (e.g. 'csa', 'tensor', 'csd').
    network : str
        Resting-state network based on Yeo-7 and Yeo-17 naming (e.g. 'Default')
        used to filter nodes in the study of brain subgraphs.
    node_size : int
        Spherical centroid node size in the case that coordinate-based centroids
        are used as ROI's for tracking.
    dens_thresh : bool
        Indicates whether a target graph density is to be used as the basis for
        thresholding.
    ID : str
        A subject id or other unique identifier.
    roi : str
        File path to binarized/boolean region-of-interest Nifti1Image file.
    min_span_tree : bool
        Indicates whether local thresholding from the Minimum Spanning Tree
        should be used.
    disp_filt : bool
        Indicates whether local thresholding using a disparity filter and
        'backbone network' should be used.
    parc : bool
        Indicates whether to use parcels instead of coordinates as ROI nodes.
    prune : bool
        Indicates whether to prune final graph of disconnected nodes/isolates.
    atlas : str
        Name of atlas parcellation used.
    uatlas : str
        File path to atlas parcellation Nifti1Image in MNI template space.
    labels : list
        List of string labels corresponding to graph nodes.
    coords : list
        List of (x, y, z) tuples corresponding to a coordinate atlas used or
        which represent the center-of-mass of each parcellation node.
    norm : int
        Indicates method of normalizing resulting graph.
    binary : bool
        Indicates whether to binarize resulting graph edges to form an
        unweighted graph.
    directget : str
        The statistical approach to tracking. Options are: det (deterministic), closest (clos), boot (bootstrapped),
        and prob (probabilistic).
    warped_fa : str
        File path to MNI-space warped FA Nifti1Image.
    error_margin : int
        Euclidean margin of error for classifying a streamline as a connection to an ROI. Default is 2 voxels.
    min_length : int
        Minimum fiber length threshold in mm to restrict tracking.
    fa_wei :  bool
        Scale streamline count edges by fractional anistropy (FA). Default is False.

    Returns
    -------
    atlas_mni : str
        File path to atlas parcellation Nifti1Image in T1w-warped MNI space.
    streams : str
        File path to streamline array sequence in .trk format.
    conn_matrix : array
        Adjacency matrix stored as an m x n array of nodes and edges.
    track_type : str
        Tracking algorithm used (e.g. 'local' or 'particle').
    target_samples : int
        Total number of streamline samples specified to generate streams.
    dir_path : str
        Path to directory containing subject derivative data for given run.
    conn_model : str
        Connectivity reconstruction method (e.g. 'csa', 'tensor', 'csd').
    network : str
        Resting-state network based on Yeo-7 and Yeo-17 naming (e.g. 'Default')
        used to filter nodes in the study of brain subgraphs.
    node_size : int
        Spherical centroid node size in the case that coordinate-based centroids
        are used as ROI's for tracking.
    dens_thresh : bool
        Indicates whether a target graph density is to be used as the basis for
        thresholding.
    ID : str
        A subject id or other unique identifier.
    roi : str
        File path to binarized/boolean region-of-interest Nifti1Image file.
    min_span_tree : bool
        Indicates whether local thresholding from the Minimum Spanning Tree
        should be used.
    disp_filt : bool
        Indicates whether local thresholding using a disparity filter and
        'backbone network' should be used.
    parc : bool
        Indicates whether to use parcels instead of coordinates as ROI nodes.
    prune : bool
        Indicates whether to prune final graph of disconnected nodes/isolates.
    atlas : str
        Name of atlas parcellation used.
    uatlas : str
        File path to atlas parcellation Nifti1Image in MNI template space.
    labels : list
        List of string labels corresponding to graph nodes.
    coords : list
        List of (x, y, z) tuples corresponding to a coordinate atlas used or
        which represent the center-of-mass of each parcellation node.
    norm : int
        Indicates method of normalizing resulting graph.
    binary : bool
        Indicates whether to binarize resulting graph edges to form an
        unweighted graph.
    directget : str
        The statistical approach to tracking. Options are: det (deterministic),
        closest (clos), boot (bootstrapped), and prob (probabilistic).
    min_length : int
        Minimum fiber length threshold in mm to restrict tracking.

    References
    ----------
    .. [1] Sporns, O., Tononi, G., & Kötter, R. (2005). The human connectome:
      A structural description of the human brain. PLoS Computational Biology.
      https://doi.org/10.1371/journal.pcbi.0010042
    .. [2] Sotiropoulos, S. N., & Zalesky, A. (2019). Building connectomes
      using diffusion MRI: why, how and but. NMR in Biomedicine.
      https://doi.org/10.1002/nbm.3752
    .. [3] Chung, M. K., Hanson, J. L., Adluru, N., Alexander, A. L., Davidson,
      R. J., & Pollak, S. D. (2017). Integrative Structural Brain Network
      Analysis in Diffusion Tensor Imaging. Brain Connectivity.
      https://doi.org/10.1089/brain.2016.0481

    '''
    import gc
    import time
    from dipy.tracking.streamline import Streamlines, values_from_volume
    from dipy.tracking._utils import (_mapping_to_voxel, _to_voxel_coordinates)
    import networkx as nx
    from itertools import combinations
    from collections import defaultdict
    from pynets.core import utils, nodemaker
    from pynets.dmri.dmri_utils import generate_sl
    from dipy.io.streamline import load_tractogram
    from dipy.io.stateful_tractogram import Space, Origin

    start = time.time()

    # Load parcellation
    roi_img = nib.load(atlas_mni)
    atlas_data = np.around(np.asarray(roi_img.dataobj))
    roi_zooms = roi_img.header.get_zooms()
    roi_shape = roi_img.shape

    # Read Streamlines
    streamlines = [
        i.astype(np.float32) for i in Streamlines(
            load_tractogram(streams,
                            roi_img,
                            to_space=Space.RASMM,
                            to_origin=Origin.TRACKVIS,
                            bbox_valid_check=False).streamlines)
    ]
    roi_img.uncache()

    if fa_wei is True:
        fa_weights = values_from_volume(
            np.asarray(nib.load(warped_fa).dataobj), streamlines, np.eye(4))
        global_fa_weights = list(utils.flatten(fa_weights))
        min_global_fa_wei = min(i for i in global_fa_weights if i > 0)
        max_global_fa_wei = max(global_fa_weights)
        fa_weights_norm = []
        # Here we normalize by global FA
        for val_list in fa_weights:
            fa_weights_norm.append(
                np.nanmean((val_list - min_global_fa_wei) /
                           (max_global_fa_wei - min_global_fa_wei)))

    # Make streamlines into generators to keep memory at a minimum
    sl = [generate_sl(i) for i in streamlines]
    del streamlines

    # Instantiate empty networkX graph object & dictionary and create voxel-affine mapping
    lin_T, offset = _mapping_to_voxel(np.eye(4))
    mx = len(np.unique(atlas_data.astype('uint16'))) - 1
    g = nx.Graph(ecount=0, vcount=mx)
    edge_dict = defaultdict(int)
    node_dict = dict(
        zip(np.unique(atlas_data.astype('uint16'))[1:],
            np.arange(mx) + 1))

    # Add empty vertices
    for node in range(1, mx + 1):
        g.add_node(node)

    # Build graph
    ix = 0
    bad_idxs = []
    for s in sl:
        # Map the streamlines coordinates to voxel coordinates and get labels for label_volume
        vox_coords = _to_voxel_coordinates(Streamlines(s), lin_T, offset)
        lab_coords = [
            nodemaker.get_sphere(coord, error_margin, roi_zooms, roi_shape)
            for coord in vox_coords
        ]
        [i, j, k] = np.vstack(np.array(lab_coords)).T

        # get labels for label_volume
        lab_arr = atlas_data[i, j, k]
        endlabels = []
        for ix, lab in enumerate(np.unique(lab_arr).astype('uint32')):
            if (lab > 0) and (np.sum(lab_arr == lab) >= overlap_thr):
                try:
                    endlabels.append(node_dict[lab])
                except:
                    bad_idxs.append(ix)
                    print(
                        f"Label {lab} missing from parcellation. Check registration and ensure valid input "
                        f"parcellation file.")

        edges = combinations(endlabels, 2)
        for edge in edges:
            lst = tuple([int(node) for node in edge])
            edge_dict[tuple(sorted(lst))] += 1

        edge_list = [(k[0], k[1], v) for k, v in edge_dict.items()]

        if fa_wei is True:
            # Add edgelist to g, weighted by average fa of the streamline
            g.add_weighted_edges_from(edge_list, weight=fa_weights_norm[ix])
        else:
            g.add_weighted_edges_from(edge_list)
        ix = ix + 1

        del lab_coords, lab_arr, endlabels, edges, edge_list

    gc.collect()

    if fa_wei is True:
        # Add average fa weights to streamline counts
        for u, v in list(g.edges):
            h = g.get_edge_data(u, v)
            edge_att_dict = {}
            for e, w in h.items():
                if w not in edge_att_dict.keys():
                    edge_att_dict[w] = []
                else:
                    edge_att_dict[w].append(e)
            for key in edge_att_dict.keys():
                edge_att_dict[key] = np.nanmean(edge_att_dict[key])
            vals = []
            for e2, w2 in edge_att_dict.items():
                vals.append(float(e2) * float(w2))
            g.edges[u, v].update({'weight': np.nanmean(vals)})

    # Convert to numpy matrix
    conn_matrix_raw = nx.to_numpy_array(g)

    # Impose symmetry
    conn_matrix = np.maximum(conn_matrix_raw, conn_matrix_raw.T)

    print('Graph Building Complete:\n', str(time.time() - start))

    if len(bad_idxs) > 0:
        bad_idxs = sorted(list(set(bad_idxs)), reverse=True)
        for j in bad_idxs:
            del labels[j], coords[j]

    coords = np.array(coords)
    labels = np.array(labels)

    return (atlas_mni, streams, conn_matrix, track_type, target_samples,
            dir_path, conn_model, network, node_size, dens_thresh, ID, roi,
            min_span_tree, disp_filt, parc, prune, atlas, uatlas, labels,
            coords, norm, binary, directget, min_length)
Esempio n. 9
0
def streams2graph(atlas_mni,
                  streams,
                  overlap_thr,
                  dir_path,
                  track_type,
                  target_samples,
                  conn_model,
                  network,
                  node_size,
                  dens_thresh,
                  ID,
                  roi,
                  min_span_tree,
                  disp_filt,
                  parc,
                  prune,
                  atlas,
                  uatlas,
                  labels,
                  coords,
                  norm,
                  binary,
                  directget,
                  warped_fa,
                  error_margin,
                  max_length,
                  fa_wei=True):
    '''
    Use tracked streamlines as a basis for estimating a structural connectome.

    Parameters
    ----------
    atlas_mni : str
        File path to atlas parcellation Nifti1Image in T1w-warped MNI space.
    streams : str
        File path to streamline array sequence in .trk format.
    overlap_thr : int
        Number of voxels for which a given streamline must intersect with an ROI
        for an edge to be counted.
    dir_path : str
        Path to directory containing subject derivative data for a given pynets run.
    track_type : str
        Tracking algorithm used (e.g. 'local' or 'particle').
    target_samples : int
        Total number of streamline samples specified to generate streams.
    conn_model : str
        Connectivity reconstruction method (e.g. 'csa', 'tensor', 'csd').
    network : str
        Resting-state network based on Yeo-7 and Yeo-17 naming (e.g. 'Default')
        used to filter nodes in the study of brain subgraphs.
    node_size : int
        Spherical centroid node size in the case that coordinate-based centroids
        are used as ROI's for tracking.
    dens_thresh : bool
        Indicates whether a target graph density is to be used as the basis for
        thresholding.
    ID : str
        A subject id or other unique identifier.
    roi : str
        File path to binarized/boolean region-of-interest Nifti1Image file.
    min_span_tree : bool
        Indicates whether local thresholding from the Minimum Spanning Tree
        should be used.
    disp_filt : bool
        Indicates whether local thresholding using a disparity filter and
        'backbone network' should be used.
    parc : bool
        Indicates whether to use parcels instead of coordinates as ROI nodes.
    prune : bool
        Indicates whether to prune final graph of disconnected nodes/isolates.
    atlas : str
        Name of atlas parcellation used.
    uatlas : str
        File path to atlas parcellation Nifti1Image in MNI template space.
    labels : list
        List of string labels corresponding to graph nodes.
    coords : list
        List of (x, y, z) tuples corresponding to a coordinate atlas used or
        which represent the center-of-mass of each parcellation node.
    norm : int
        Indicates method of normalizing resulting graph.
    binary : bool
        Indicates whether to binarize resulting graph edges to form an
        unweighted graph.
    directget : str
        The statistical approach to tracking. Options are: det (deterministic), closest (clos), boot (bootstrapped),
        and prob (probabilistic).
    warped_fa : str
        File path to MNI-space warped FA Nifti1Image.
    error_margin : int
        Euclidean margin of error for classifying a streamline as a connection to an ROI. Default is 2 voxels.
    max_length : int
        Maximum fiber length threshold in mm to restrict tracking.
    fa_wei :  bool
        Scale streamline count edges by fractional anistropy (FA). Default is False.

    Returns
    -------
    atlas_mni : str
        File path to atlas parcellation Nifti1Image in T1w-warped MNI space.
    streams : str
        File path to streamline array sequence in .trk format.
    conn_matrix : array
        Adjacency matrix stored as an m x n array of nodes and edges.
    track_type : str
        Tracking algorithm used (e.g. 'local' or 'particle').
    target_samples : int
        Total number of streamline samples specified to generate streams.
    dir_path : str
        Path to directory containing subject derivative data for given run.
    conn_model : str
        Connectivity reconstruction method (e.g. 'csa', 'tensor', 'csd').
    network : str
        Resting-state network based on Yeo-7 and Yeo-17 naming (e.g. 'Default')
        used to filter nodes in the study of brain subgraphs.
    node_size : int
        Spherical centroid node size in the case that coordinate-based centroids
        are used as ROI's for tracking.
    dens_thresh : bool
        Indicates whether a target graph density is to be used as the basis for
        thresholding.
    ID : str
        A subject id or other unique identifier.
    roi : str
        File path to binarized/boolean region-of-interest Nifti1Image file.
    min_span_tree : bool
        Indicates whether local thresholding from the Minimum Spanning Tree
        should be used.
    disp_filt : bool
        Indicates whether local thresholding using a disparity filter and
        'backbone network' should be used.
    parc : bool
        Indicates whether to use parcels instead of coordinates as ROI nodes.
    prune : bool
        Indicates whether to prune final graph of disconnected nodes/isolates.
    atlas : str
        Name of atlas parcellation used.
    uatlas : str
        File path to atlas parcellation Nifti1Image in MNI template space.
    labels : list
        List of string labels corresponding to graph nodes.
    coords : list
        List of (x, y, z) tuples corresponding to a coordinate atlas used or
        which represent the center-of-mass of each parcellation node.
    norm : int
        Indicates method of normalizing resulting graph.
    binary : bool
        Indicates whether to binarize resulting graph edges to form an
        unweighted graph.
    directget : str
        The statistical approach to tracking. Options are: det (deterministic), closest (clos), boot (bootstrapped),
        and prob (probabilistic).
    max_length : int
        Maximum fiber length threshold in mm to restrict tracking.
    '''
    from dipy.tracking.streamline import Streamlines, values_from_volume
    from dipy.tracking._utils import (_mapping_to_voxel, _to_voxel_coordinates)
    import networkx as nx
    from itertools import combinations
    from collections import defaultdict
    from pynets.core import utils, nodemaker
    from dipy.io.streamline import load_tractogram
    from dipy.io.stateful_tractogram import Space, Origin
    import time

    # Load parcellation
    roi_img = nib.load(atlas_mni)
    atlas_data = np.around(roi_img.get_fdata())
    roi_zooms = roi_img.header.get_zooms()
    roi_shape = roi_img.shape

    # Read Streamlines
    streamlines = Streamlines(
        load_tractogram(streams,
                        roi_img,
                        to_space=Space.RASMM,
                        to_origin=Origin.TRACKVIS,
                        bbox_valid_check=False).streamlines)
    roi_img.uncache()

    fa_weights = values_from_volume(
        nib.load(warped_fa).get_fdata(), streamlines, np.eye(4))
    global_fa_weights = list(utils.flatten(fa_weights))
    min_global_fa_wei = min(global_fa_weights)
    max_global_fa_wei = max(global_fa_weights)
    fa_weights_norm = []
    for val_list in fa_weights:
        fa_weights_norm.append((val_list - min_global_fa_wei) /
                               (max_global_fa_wei - min_global_fa_wei))

    # Instantiate empty networkX graph object & dictionary and create voxel-affine mapping
    lin_T, offset = _mapping_to_voxel(np.eye(4))
    mx = len(np.unique(atlas_data.astype('uint16'))) - 1
    g = nx.Graph(ecount=0, vcount=mx)
    edge_dict = defaultdict(int)
    node_dict = dict(
        zip(np.unique(atlas_data.astype('uint16')) + 1,
            np.arange(mx) + 1))

    # Add empty vertices
    for node in range(1, mx + 1):
        g.add_node(node)

    # Build graph
    start_time = time.time()

    ix = 0
    for s in streamlines:
        # Map the streamlines coordinates to voxel coordinates and get labels for label_volume
        i, j, k = np.vstack(
            np.array([
                nodemaker.get_sphere(coord, error_margin, roi_zooms, roi_shape)
                for coord in _to_voxel_coordinates(s, lin_T, offset)
            ])).T

        # get labels for label_volume
        lab_arr = atlas_data[i, j, k]
        endlabels = []
        for lab in np.unique(lab_arr).astype('uint32'):
            if (lab > 0) and (np.sum(lab_arr == lab) >= overlap_thr):
                try:
                    endlabels.append(node_dict[lab])
                except UserWarning:
                    print("%s%s%s" % (
                        'Label ', lab,
                        ' missing from parcellation. Check registration and ensure valid '
                        'input parcellation file.'))

        edges = combinations(endlabels, 2)
        for edge in edges:
            lst = tuple([int(node) for node in edge])
            edge_dict[tuple(sorted(lst))] += 1

        edge_list = [(k[0], k[1], v) for k, v in edge_dict.items()]

        if fa_wei is True:
            # Add edgelist to g, weighted by average fa of the streamline
            g.add_weighted_edges_from(edge_list,
                                      weight=np.nanmean(fa_weights_norm[ix]))
        else:
            g.add_weighted_edges_from(edge_list)
        ix = ix + 1

    print("%s%s%s" % ('Graph construction runtime: ',
                      np.round(time.time() - start_time, 1), 's'))
    del streamlines

    if fa_wei is True:
        # Add average fa weights to streamline counts
        for u, v in list(g.edges):
            h = g.get_edge_data(u, v)
            edge_att_dict = {}
            for e, w in h.items():
                if w not in edge_att_dict.keys():
                    edge_att_dict[w] = []
                else:
                    edge_att_dict[w].append(e)
            for key in edge_att_dict.keys():
                edge_att_dict[key] = np.nanmean(edge_att_dict[key])
            vals = []
            for e2, w2 in edge_att_dict.items():
                vals.append(float(e2) * float(w2))
            g.edges[u, v].update({'weight': np.nanmean(vals)})

    # Convert to numpy matrix
    conn_matrix_raw = nx.to_numpy_matrix(g)

    # Enforce symmetry
    conn_matrix = np.maximum(conn_matrix_raw, conn_matrix_raw.T)

    return atlas_mni, streams, conn_matrix, track_type, target_samples, dir_path, conn_model, network, node_size, dens_thresh, ID, roi, min_span_tree, disp_filt, parc, prune, atlas, uatlas, labels, coords, norm, binary, directget, max_length