Exemple #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
Exemple #2
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
Exemple #3
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