Skip to content

mesh API

yabplot.mesh.load_bmesh(bmesh)

Transforms the bmesh parameter into a standardized dictionary of PyVista PolyData meshes.

Parameters:

Name Type Description Default
bmesh None, str, dict, or pyvista.PolyData
  • None: Returns an empty dictionary (disables background mesh).
  • str: Fetches standard meshes from the registry (e.g., 'midthickness').
  • dict: Maps custom meshes to 'L' and 'R' keys. Values can be pre-loaded PyVista PolyData objects or string file paths (which are auto-loaded).
  • pyvista.PolyData: A single unified mesh, mapped to the 'both' key.
required

Returns:

Type Description
dict

Standardized dictionary containing the loaded PyVista meshes.

Source code in yabplot/mesh.py
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
def load_bmesh(bmesh):
    """
    Transforms the `bmesh` parameter into a standardized dictionary of PyVista PolyData meshes.

    Parameters
    ----------
    bmesh : None, str, dict, or pyvista.PolyData
        - None: Returns an empty dictionary (disables background mesh).
        - str: Fetches standard meshes from the registry (e.g., 'midthickness').
        - dict: Maps custom meshes to 'L' and 'R' keys. Values can be pre-loaded 
          PyVista PolyData objects or string file paths (which are auto-loaded).
        - pyvista.PolyData: A single unified mesh, mapped to the 'both' key.

    Returns
    -------
    dict
        Standardized dictionary containing the loaded PyVista meshes.
    """
    from .data import get_surface_paths
    from .utils import load_gii2pv

    if bmesh is None:
        return {}
    if isinstance(bmesh, str):
        lh_path, rh_path = get_surface_paths(bmesh, 'bmesh')
        return {'L': load_gii2pv(lh_path), 'R': load_gii2pv(rh_path)}
    if isinstance(bmesh, dict):
        clean_dict = {}
        for k, v in bmesh.items():
            if isinstance(v, str):
                if v.endswith('.gii') or v.endswith('.gii.gz'):
                    v = load_gii2pv(v)
                else:
                    v = pv.read(v)
            if k.upper() in ['L', 'LEFT']: clean_dict['L'] = v
            elif k.upper() in ['R', 'RIGHT']: clean_dict['R'] = v
            else: clean_dict[k] = v
        return clean_dict

    return {'both': bmesh}

yabplot.mesh.make_cortical_mesh(verts, faces, scalars, scalar_name='Data')

Converts standard triangle face arrays into pyvista's specific padded format and injects per-vertex data.

Parameters:

Name Type Description Default
verts ndarray

(N, 3) float array of spatial vertex coordinates (x, y, z).

required
faces ndarray

(M, 3) int array of triangle face indices.

required
scalars ndarray

(N,) float array of per-vertex scalar values.

required
scalar_name str

the string key to store the data under. default is 'Data'.

'Data'

Returns:

Name Type Description
mesh PolyData

the instantiated pyvista mesh with attached scalar data.

Source code in yabplot/mesh.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
def make_cortical_mesh(verts, faces, scalars, scalar_name='Data'):
    """
    Converts standard triangle face arrays into pyvista's specific padded format 
    and injects per-vertex data.

    Parameters
    ----------
    verts : numpy.ndarray
        (N, 3) float array of spatial vertex coordinates (x, y, z).
    faces : numpy.ndarray
        (M, 3) int array of triangle face indices.
    scalars : numpy.ndarray
        (N,) float array of per-vertex scalar values.
    scalar_name : str, optional
        the string key to store the data under. default is 'Data'.

    Returns
    -------
    mesh : pyvista.PolyData
        the instantiated pyvista mesh with attached scalar data.
    """
    faces_pv = np.hstack([np.full((faces.shape[0], 1), 3), faces]).flatten().astype(int)
    mesh = pv.PolyData(verts, faces_pv)
    mesh[scalar_name] = scalars
    return mesh

yabplot.mesh.load_nii_as_mesh(nii_path, threshold=0.5, blur_sigma=1.5, smooth_i=10, smooth_f=0.1)

Build a surface mesh from a 3D NIfTI volume using marching cubes, with optional Gaussian blurring and mesh smoothing.

Parameters:

Name Type Description Default
nii_path str

Absolute path to a NIfTI file representing a 3D volume. If 4D, only the first volume will be used.

required
threshold float

Threshold applied after optional blur. Voxels > threshold are kept.

0.5
blur_sigma float

Gaussian blur (voxel units) before thresholding.

1.5
smooth_i int

Number of PyVista smoothing iterations after surface extraction.

10
smooth_f float

Relaxation factor for mesh smoothing.

0.1

Returns:

Name Type Description
mesh PolyData

The extracted and smoothed surface mesh ready for plotting.

Source code in yabplot/mesh.py
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
def load_nii_as_mesh(
    nii_path,
    threshold=0.5,
    blur_sigma=1.5,
    smooth_i=10,
    smooth_f=0.1
):
    """
    Build a surface mesh from a 3D NIfTI volume using marching cubes, with optional Gaussian blurring and mesh smoothing.

    Parameters
    ----------
    nii_path : str
        Absolute path to a NIfTI file representing a 3D volume. If 4D, only the first volume will be used.
    threshold : float, optional
        Threshold applied after optional blur. Voxels ``> threshold`` are kept.
    blur_sigma : float, optional
        Gaussian blur (voxel units) before thresholding.
    smooth_i : int, optional
        Number of PyVista smoothing iterations after surface extraction.
    smooth_f : float, optional
        Relaxation factor for mesh smoothing.

    Returns
    -------
    mesh : pyvista.PolyData
        The extracted and smoothed surface mesh ready for plotting.
    """

    img = nib.load(nii_path)
    vol = img.get_fdata()

    if vol.ndim > 3:
        warnings.warn(
            f"[WARNING] detected {vol.ndim}d nifti volume. using the first volume (index 0)."
        )
        vol = vol[..., 0]

    vol = np.nan_to_num(vol, nan=0.0)

    if blur_sigma and blur_sigma > 0:
        vol = gaussian_filter(vol, sigma=float(blur_sigma))

    mask = vol > float(threshold)

    if not np.any(mask):
        raise ValueError("Mask is empty after thresholding. Adjust threshold/blur_sigma.")

    verts_vox, faces, _, _ = measure.marching_cubes(mask.astype(np.float32), level=0.5)
    verts_world = nib.affines.apply_affine(img.affine, verts_vox)

    faces_pv = np.hstack([
        np.full((faces.shape[0], 1), 3, dtype=np.int64),
        faces.astype(np.int64)
    ]).ravel()
    mesh = pv.PolyData(verts_world.astype(np.float32), faces_pv)

    if smooth_i and smooth_i > 0:
        mesh = mesh.smooth(n_iter=int(smooth_i), relaxation_factor=float(smooth_f))

    if mesh.n_points == 0:
        raise ValueError("Extracted mesh has no vertices. Check input mask and parameters.")

    # fill topological holes in the extracted meshes
    try:
        mesh = mesh.fill_holes(1000)
    except Exception as e:
        warnings.warn(f"Mesh hole filling failed: {e}. Continuing with unfilled meshes.")

    return mesh

yabplot.mesh.load_vertexwise_mesh(lh_mesh_path, rh_mesh_path, lh_data, rh_data, scalar_name='Data')

Loads GIfTI geometry files (i.e. brain mesh), converts them to pyvista meshes, and injects the provided 1D data arrays into them.

Parameters:

Name Type Description Default
lh_mesh_path str

absolute path to the left hemisphere geometry file (e.g., .surf.gii).

required
rh_mesh_path str

absolute path to the right hemisphere geometry file (e.g., .surf.gii).

required
lh_data ndarray

1D array of scalar values for the left hemisphere vertices.

required
rh_data ndarray

1D array of scalar values for the right hemisphere vertices.

required
scalar_name str

the string key to store the data under in the pyvista point data dictionary. default is 'Data'.

'Data'

Returns:

Type Description
lh_mesh, rh_mesh : tuple of pyvista.PolyData

left and right hemisphere meshes ready for yabplot.plotting.plot_vertexwise.

Source code in yabplot/mesh.py
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
def load_vertexwise_mesh(lh_mesh_path, rh_mesh_path, lh_data, rh_data, scalar_name='Data'):
    """
    Loads GIfTI geometry files (i.e. brain mesh), converts them to pyvista meshes, and injects 
    the provided 1D data arrays into them.

    Parameters
    ----------
    lh_mesh_path : str
        absolute path to the left hemisphere geometry file (e.g., .surf.gii).
    rh_mesh_path : str
        absolute path to the right hemisphere geometry file (e.g., .surf.gii).
    lh_data : numpy.ndarray
        1D array of scalar values for the left hemisphere vertices.
    rh_data : numpy.ndarray
        1D array of scalar values for the right hemisphere vertices.
    scalar_name : str, optional
        the string key to store the data under in the pyvista point data dictionary. 
        default is 'Data'.

    Returns
    -------
    lh_mesh, rh_mesh : tuple of pyvista.PolyData
        left and right hemisphere meshes ready for `yabplot.plotting.plot_vertexwise`.
    """
    from .utils import load_gii
    lh = make_cortical_mesh(*load_gii(lh_mesh_path), lh_data, scalar_name)
    rh = make_cortical_mesh(*load_gii(rh_mesh_path), rh_data, scalar_name)
    return lh, rh