Plot Data with Alpha Values

It is often useful to plot a primary map (the “data” you are interested in) masked or attenuated by a secondary map (a “confidence” or “weight” map). For example, an encoding model’s tuning maps are interpretable where the model fits well, so one could plot tuning maps with opacity proportional to the per-voxel/per-vertex prediction accuracy. Voxels/vertices where the model fits poorly fade into the gray curvature underlay; voxels/vertices where the model fits well are fully opaque.

pycortex supports three patterns for this:

  1. Scalar data with an alpha map – use Volume2D / Vertex2D with a 2D colormap whose second axis encodes alpha (the colormap LUT itself goes from transparent to opaque along dim2). No extra arithmetic is needed; the alpha channel is composited correctly by both the matplotlib (quickshow) and the WebGL renderers.

  2. RGB data with an alpha map – pass alpha= directly to VolumeRGB / VertexRGB. The alpha can be any per-voxel/per-vertex array in [0, 1], or a Volume/Vertex whose own vmin/vmax set the range.

  3. 2D data with an alpha map – Volume2D / Vertex2D also accept alpha=, which is multiplied into the colormap alpha. This lets you combine any 2D colormap (e.g. a covariance map) with a separate opacity map.

In every case, NaN anywhere at a voxel/vertex (in the data, in either dimension of a 2D view, in any RGB channel, or in the alpha map itself) renders fully transparent, so the curvature shows through.

Below, we illustrate all three patterns with a synthetic “model accuracy” mask – a 3D Gaussian bump for the volume case and a vertex-distance falloff for the surface case – so cortex near the bump centre stays opaque while the periphery fades into the curvature. Pattern 3 adds a separate opacity map and a slab of NaN data.

import cortex
import cortex.polyutils
import numpy as np
import matplotlib.pyplot as plt

subject = "S1"
xfm = "fullhead"

Synthesize the data and alpha maps

All the patterns below reuse the same synthetic inputs, so we set everything up once here and only show the plotting call in each pattern’s cell. In a real analysis these would come from your model fits (e.g. data = regression coefficients, accuracy = cross-validated prediction r^2).

# --- Volumetric data + alpha -------------------------------------------------
# Signed gradient across the brain stands in for a regression coefficient
# or tuning preference.
zz, yy, xx = np.mgrid[0:31, 0:100, 0:100]
data_vol = (xx - 50) / 50.0  # range ~ [-1, 1]

# A 3D Gaussian bump centered in the volume stands in for a per-voxel
# model accuracy / prediction r in [0, 1].
center = np.array([15, 50, 50])
sigma = 25.0
dist2 = (zz - center[0]) ** 2 + (yy - center[1]) ** 2 + (xx - center[2]) ** 2
accuracy_vol = np.exp(-dist2 / (2 * sigma**2))  # in [0, 1]

# RGB volumetric channels: anatomical x/y/z normalized to [0, 1]. Three
# smoothly-varying volumetric channels stand in for, e.g., three latent
# RGB tuning axes from a model.
red_vol = np.clip(xx / 99.0, 0, 1)
green_vol = np.clip(yy / 99.0, 0, 1)
blue_vol = np.clip(zz / 30.0, 0, 1)

# --- Surface (vertex) data + alpha -------------------------------------------
# Encode by *spatial coordinate*, not vertex index: vertex indices on the
# cortical surface are not arranged by spatial neighborhood, so a
# vertex-index ramp would render as visual noise.
surfs = [cortex.polyutils.Surface(*d) for d in cortex.db.get_surf(subject, "fiducial")]
num_verts = [s.pts.shape[0] for s in surfs]
total_verts = sum(num_verts)
pts = np.vstack([surfs[0].pts, surfs[1].pts])  # (total_verts, 3)

# Scalar surface data: anterior-posterior gradient (anatomical y),
# centered at zero so a diverging colormap reads naturally.
y_centered = pts[:, 1] - pts[:, 1].mean()
data_vtx = y_centered / np.abs(y_centered).max()  # in [-1, 1]

# RGB surface channels: anatomical x/y/z normalized to [0, 1].
xyz_norm = (pts - pts.min(axis=0)) / (pts.max(axis=0) - pts.min(axis=0))


# Surface alpha: a soft bump centered at a particular vertex in each hemi.
def _bump(surf, seed, sigma):
    d = np.linalg.norm(surf.pts - surf.pts[seed], axis=1)
    return np.exp(-(d**2) / (2 * sigma**2))


accuracy_vtx = np.hstack(
    [
        _bump(surfs[0], num_verts[0] // 2, sigma=40.0),
        _bump(surfs[1], num_verts[1] // 2, sigma=40.0),
    ]
)

Pattern 1a: scalar Volume + alpha via Volume2D + 2D alpha colormap

The 2D colormap "RdBu_r_alpha" maps (data, alpha) -> RGBA: along the first axis, blue-white-red diverging; along the second axis, transparent-to-opaque. So passing the data as dim1 and the accuracy as dim2 yields exactly “diverging colormap, opacity = accuracy”.

Other 2D alpha colormaps shipped with pycortex include "fire_alpha" (sequential, perceptually uniform), "PU_RdBu_covar_alpha" (diverging, perceptually uniform), "plasma_alpha", and "autumn_alpha".

v2d = cortex.Volume2D(
    data_vol,
    accuracy_vol,
    subject,
    xfm,
    cmap="RdBu_r_alpha",
    vmin=-1,
    vmax=1,  # range for the data (dim1)
    vmin2=0,
    vmax2=1,  # range for the alpha (dim2)
)
cortex.quickshow(v2d, with_colorbar=True, with_curvature=True)
plt.suptitle("Volume2D + RdBu_r_alpha: data masked by 'accuracy'")
plt.show()
Volume2D + RdBu_r_alpha: data masked by 'accuracy'
Generating curvature surface info...

Pattern 1b: scalar Vertex + alpha via Vertex2D + 2D alpha colormap

Same idea on the surface.

vtx2d = cortex.Vertex2D(
    data_vtx,
    accuracy_vtx,
    subject,
    cmap="RdBu_r_alpha",
    vmin=-1,
    vmax=1,
    vmin2=0,
    vmax2=1,
)
cortex.quickshow(vtx2d, with_colorbar=True, with_curvature=True)
plt.suptitle("Vertex2D + RdBu_r_alpha: data masked by 'accuracy'")
plt.show()
Vertex2D + RdBu_r_alpha: data masked by 'accuracy'

Pattern 2a: RGB Volume + alpha via VolumeRGB(alpha=…)

When the “data” is itself three independent channels, use VolumeRGB and pass the accuracy as the alpha= argument.

red = cortex.Volume(red_vol, subject, xfm, vmin=0, vmax=1)
green = cortex.Volume(green_vol, subject, xfm, vmin=0, vmax=1)
blue = cortex.Volume(blue_vol, subject, xfm, vmin=0, vmax=1)
alpha_vol = cortex.Volume(accuracy_vol, subject, xfm, vmin=0, vmax=1)

vrgb = cortex.VolumeRGB(red, green, blue, subject, alpha=alpha_vol)
cortex.quickshow(vrgb, with_colorbar=False, with_curvature=True)
plt.suptitle("VolumeRGB(alpha=accuracy): RGB tuning masked by 'accuracy'")
plt.show()
VolumeRGB(alpha=accuracy): RGB tuning masked by 'accuracy'

Pattern 2b: RGB Vertex + alpha via VertexRGB(alpha=…)

Same idea on the surface.

red_v = cortex.Vertex(xyz_norm[:, 0], subject, vmin=0, vmax=1)
green_v = cortex.Vertex(xyz_norm[:, 1], subject, vmin=0, vmax=1)
blue_v = cortex.Vertex(xyz_norm[:, 2], subject, vmin=0, vmax=1)
alpha_v = cortex.Vertex(accuracy_vtx, subject, vmin=0, vmax=1)

vrgb_vtx = cortex.VertexRGB(red_v, green_v, blue_v, subject, alpha=alpha_v)
cortex.quickshow(vrgb_vtx, with_colorbar=False, with_curvature=True)
plt.suptitle("VertexRGB(alpha=accuracy): RGB channels masked by 'accuracy'")
plt.show()
VertexRGB(alpha=accuracy): RGB channels masked by 'accuracy'

Pattern 3: 2D data + a separate alpha map via Volume2D(alpha=…)

A 2D colormap can encode two quantities (here a covariance-style map of data against accuracy) while a third map controls opacity. The alpha= map is multiplied into the colormap’s own alpha channel. Here we fade out the inferior part of the volume. NaNs in the data are always transparent, whatever alpha says: the posterior slab below is NaN and renders as bare curvature.

data_vol_nan = data_vol.copy()
data_vol_nan[:, :25, :] = np.nan  # posterior slab: undefined
alpha_inferior_fade = np.clip(zz / 30.0, 0, 1)  # 0 at the bottom, 1 at the top

v2d_alpha = cortex.Volume2D(
    data_vol_nan,
    accuracy_vol,
    subject,
    xfm,
    cmap="RdBu_covar",
    vmin=-1,
    vmax=1,
    vmin2=0,
    vmax2=1,
    alpha=alpha_inferior_fade,
)
cortex.quickshow(v2d_alpha, with_colorbar=True, with_curvature=True)
plt.suptitle("Volume2D(alpha=...): 2D colormap, separate opacity, NaN slab")
plt.show()
Volume2D(alpha=...): 2D colormap, separate opacity, NaN slab
/home/runner/work/pycortex/pycortex/cortex/dataset/view2D.py:480: UserWarning: Colormap 'RdBu_covar' is not perceptually uniform. Consider using 'PU_RdBu_covar' instead.
  warnings.warn("Colormap %r is not perceptually uniform. Consider using"

Notes

  • All patterns produce the same composite formula at the pixel level: out = alpha * data + (1 - alpha) * curvature_underlay. Choose based on what the “data” is: scalar (use Pattern 1), RGB (use Pattern 2) or two quantities plus an opacity (use Pattern 3).

  • NaN anywhere at a voxel/vertex – data, either 2D dimension, any RGB channel, or the alpha map – is rendered fully transparent.

  • The same objects work in the WebGL viewer: cortex.webgl.show(v2d) etc.; NaN and opacity are honored identically, also when switching between datasets.

  • The deprecated Vertex.blend_curvature(alpha) helper produced a pre-blended VertexRGB that lost cmap/vmin/vmax editability. The Pattern 1 Vertex2D route above is the recommended replacement: it keeps the colormap parameters editable on the resulting object and renders identically.

Total running time of the script: (0 minutes 12.800 seconds)

Gallery generated by Sphinx-Gallery