"""Transforms for datasets."""
import numpy as np
import nibabel as nib
from netneurotools.interface.cifti import extract_cifti_surface_labels
from netneurotools.interface.freesurfer import extract_annot_labels
from netneurotools.interface.gifti import extract_gifti_labels
from netneurotools.interface.interface_utils import PARCIGNORE
def _check_vertices_to_parcels_parc_file(parc_file, hemi):
if hemi == "both":
if not isinstance(parc_file, (tuple, list)):
if not str(parc_file).endswith("dlabel.nii"):
raise ValueError(
"parc_file should be tuple or list unless it is a dlabel.nii file"
)
elif hemi in ["L", "R"]:
pass
else:
raise ValueError(f"Unknown hemisphere: {hemi}")
def _check_file_type(path):
if not str(path).endswith(("gii", "dlabel.nii", "annot")):
raise ValueError(
"Unsupported parcellation file format, "
"only gii, dlabel.nii, and annot are supported."
)
if isinstance(parc_file, (tuple, list)):
_check_file_type(parc_file[0])
_check_file_type(parc_file[1])
else:
_check_file_type(parc_file)
def load_surf_parc_file(parc_file, cifti_structure=None, parc_ignore=PARCIGNORE):
"""
Load surface parcellation file with vertices and labels.
Parameters
----------
parc_file : str or Path
Path to parcellation file.
cifti_structure : str, optional
CIFTI structure for dlabel.nii files.
parc_ignore : list, optional
List of labels to ignore.
Returns
-------
surf_data : np.ndarray
Surface data.
keys : tuple
Keys.
labels : tuple
Labels.
"""
if str(parc_file).endswith("gii"):
surf_data, keys, labels = extract_gifti_labels(
parc_file, parc_ignore=parc_ignore
)
elif str(parc_file).endswith("dlabel.nii"):
if cifti_structure is None:
raise ValueError("cifti_structure must be specified for dlabel.nii files")
else:
cifti = nib.load(parc_file)
surf_data, keys, labels = extract_cifti_surface_labels(
cifti.get_fdata(),
cifti.header.get_axis(0),
cifti.header.get_axis(1),
cifti_structure,
parc_ignore=parc_ignore,
)
elif str(parc_file).endswith("annot"):
surf_data, keys, labels = extract_annot_labels(
parc_file, parc_ignore=parc_ignore
)
else:
raise ValueError(
"Unsupported parcellation file format, "
"only gii, dlabel.nii, and annot are supported."
)
return surf_data, keys, labels
def _vertices_to_parcels_single_hemi(
vert_data, parc_file, cifti_structure=None, background=None, parc_ignore=PARCIGNORE
):
vertices, keys, labels = load_surf_parc_file(
parc_file, cifti_structure=cifti_structure, parc_ignore=parc_ignore
)
if vertices.shape[0] != len(vert_data):
raise ValueError(
"Number of vertices in provided annotation files "
"differs from size of vertex-level data array.\n"
" EXPECTED: {} vertices\n"
" RECEIVED: {} vertices".format(vertices.shape[0], len(vert_data))
)
if background is not None:
vert_data[vert_data == background] = np.nan
reduced = []
for idx in keys:
found = vertices == idx
if not found.any():
reduced.append(np.empty_like(vert_data[0]) * np.nan)
else:
reduced.append(np.nanmean(vert_data[found], axis=0))
return np.array(reduced), keys, labels
[docs]
def vertices_to_parcels(
vert_data, parc_file, hemi="both", background=None, parc_ignore=PARCIGNORE
):
"""
Convert vertex-level data to parcel-level data.
Parameters
----------
vert_data : np.ndarray or tuple or list
Vertex-level data.
parc_file : str or Path or tuple or list
Path to parcellation file.
hemi : str, optional
Hemisphere to process. Can be 'both', 'L', or 'R'.
background : int, optional
Background value in vert_data to ignore.
parc_ignore : list, optional
List of labels to ignore.
Returns
-------
reduced : np.ndarray or tuple
Reduced data.
keys : tuple
Keys.
labels : tuple
Labels.
Notes
-----
If hemi is 'both':
* ``vert_data`` can be a tuple or list of two arrays. It can also be a single
array with left then right hemisphere data.
* ``parc_file`` should be a tuple or list of two paths, unless it is a dlabel.nii
file, in which case it can be a single path.
* Returns tuples of reduced data, keys, and labels.
If hemi is 'L' or 'R':
* ``vert_data`` should be a single array.
* ``parc_file`` should be a single path.
* Returns reduced data, keys, and labels.
"""
# _check_vertices_to_parcels_parc_file(parc_file, hemi)
# deal with parc_file
if hemi == "both": # both hemispheres
if isinstance(parc_file, (tuple, list)):
cifti_structure_lh = cifti_structure_rh = None
else:
cifti_structure_lh = "CIFTI_STRUCTURE_CORTEX_LEFT"
cifti_structure_rh = "CIFTI_STRUCTURE_CORTEX_RIGHT"
parc_file = (parc_file, parc_file)
if len(vert_data) != 2: # not a tuple or list of two arrays
vert_data = (
vert_data[: len(vert_data) // 2],
vert_data[len(vert_data) // 2 :],
)
reduced_lh, keys_lh, labels_lh = _vertices_to_parcels_single_hemi(
vert_data[0],
parc_file[0],
cifti_structure=cifti_structure_lh,
background=background,
parc_ignore=parc_ignore,
)
reduced_rh, keys_rh, labels_rh = _vertices_to_parcels_single_hemi(
vert_data[1],
parc_file[1],
cifti_structure=cifti_structure_rh,
background=background,
parc_ignore=parc_ignore,
)
reduced = (reduced_lh, reduced_rh)
keys = (keys_lh, keys_rh)
labels = (labels_lh, labels_rh)
else:
# single hemisphere
if hemi == "L":
cifti_structure_lh = "CIFTI_STRUCTURE_CORTEX_LEFT"
elif hemi == "R":
cifti_structure_lh = "CIFTI_STRUCTURE_CORTEX_RIGHT"
else:
raise ValueError(f"Unknown hemisphere: {hemi}")
reduced, keys, labels = _vertices_to_parcels_single_hemi(
vert_data,
parc_file,
cifti_structure=cifti_structure_lh,
background=background,
parc_ignore=parc_ignore,
)
return reduced, keys, labels
def _parcels_to_vertices_single_hemi(
parc_data, parc_file, cifti_structure=None, fill=np.nan, parc_ignore=PARCIGNORE
):
vertices, keys, labels = load_surf_parc_file(
parc_file, cifti_structure=cifti_structure, parc_ignore=parc_ignore
)
# check if keys is ordered
if not np.array_equal(keys, np.sort(keys)):
raise RuntimeWarning(f"Keys are not ordered: {keys}, {labels}")
projected = np.full(
(vertices.shape[0],) + parc_data.shape[1:], fill, dtype=np.float64
)
for i_idx, idx in enumerate(keys):
found = vertices == idx
if not found.any():
continue
projected[found] = parc_data[i_idx]
return projected, keys, labels
[docs]
def parcels_to_vertices(
parc_data, parc_file, hemi="both", fill=np.nan, parc_ignore=PARCIGNORE
):
"""
Convert parcel-level data to vertex-level data.
Parameters
----------
parc_data : np.ndarray or tuple or list
Parcel-level data.
parc_file : str or Path or tuple or list
Path to parcellation file.
hemi : str, optional
Hemisphere to process. Can be 'both', 'L', or 'R'.
fill : int or float, optional
Fill value for vertices not in parcels. Default is np.nan.
parc_ignore : list, optional
List of labels to ignore.
Returns
-------
projected : np.ndarray or tuple
Projected data.
keys : tuple
Keys.
labels : tuple
Labels.
Notes
-----
If hemi is 'both':
* ``parc_data`` can be a tuple or list of two arrays. It can also be a single
array with left then right hemisphere data.
* ``parc_file`` should be a tuple or list of two paths, unless it is a dlabel.nii
file, in which case it can be a single path.
* Returns tuples of projected data, keys, and labels.
If hemi is 'L' or 'R':
* ``parc_data`` should be a single array.
* ``parc_file`` should be a single path.
* Returns projected data, keys, and labels.
"""
if hemi == "both":
if isinstance(parc_file, (tuple, list)):
cifti_structure_lh = cifti_structure_rh = None
else:
cifti_structure_lh = "CIFTI_STRUCTURE_CORTEX_LEFT"
cifti_structure_rh = "CIFTI_STRUCTURE_CORTEX_RIGHT"
parc_file = (parc_file, parc_file)
projected_lh, keys_lh, labels_lh = _parcels_to_vertices_single_hemi(
parc_data[0],
parc_file[0],
cifti_structure=cifti_structure_lh,
fill=fill,
parc_ignore=parc_ignore,
)
projected_rh, keys_rh, labels_rh = _parcels_to_vertices_single_hemi(
parc_data[1],
parc_file[1],
cifti_structure=cifti_structure_rh,
fill=fill,
parc_ignore=parc_ignore,
)
projected = (projected_lh, projected_rh)
keys = (keys_lh, keys_rh)
labels = (labels_lh, labels_rh)
else:
if hemi == "L":
cifti_structure_lh = "CIFTI_STRUCTURE_CORTEX_LEFT"
elif hemi == "R":
cifti_structure_lh = "CIFTI_STRUCTURE_CORTEX_RIGHT"
else:
raise ValueError(f"Unknown hemisphere: {hemi}")
projected, keys, labels = _parcels_to_vertices_single_hemi(
parc_data,
parc_file,
cifti_structure=cifti_structure_lh,
fill=fill,
parc_ignore=parc_ignore,
)
return projected, keys, labels