WhipperSnapPy is a Python/OpenGL tool to render triangular surface meshes with color overlays or parcellations and generate screenshots — from the command line, in Jupyter notebooks, or via a desktop GUI.
It works with FreeSurfer and FastSurfer brain surfaces as well as any
triangle mesh in OFF, legacy ASCII VTK PolyData, ASCII PLY, or GIfTI
(.gii, .surf.gii) format, or passed directly as a NumPy
(vertices, faces) tuple.
pip install whippersnappyFor rotation video support (MP4/WebM — GIF works without this):
pip install 'whippersnappy[video]'For the interactive desktop GUI:
pip install 'whippersnappy[gui]'For interactive 3D in Jupyter notebooks:
pip install 'whippersnappy[notebook]'Off-screen (headless) rendering on Linux is supported natively via
OSMesa — no xvfb or GPU required. On Windows, GLFW creates an
invisible window; a GPU driver is sufficient — no display needed. On
macOS, a real display connection is required (NSGL does not support
headless rendering).
See the Docker guide for headless Linux usage.
After installation the following commands are available:
Renders one view of any triangular surface mesh:
whippersnap1 --mesh $SUBJECT_DIR/surf/lh.white \
--overlay $LH_OVERLAY \
--bg-map $SUBJECT_DIR/surf/lh.curv \
--roi $SUBJECT_DIR/label/lh.cortex.label \
--view left \
-o snap1.png
# Also works with OFF / VTK / PLY / GIfTI and plain-text overlays
whippersnap1 --mesh mesh.off --overlay values.txt -o snap1.png
whippersnap1 --mesh surface.surf.gii --overlay overlay.func.gii -o snap1.pngRenders lateral and medial views of both hemispheres into a single composed image:
whippersnap4 -lh $LH_OVERLAY \
-rh $RH_OVERLAY \
-sd $SUBJECT_DIR \
--fmax 4 --fthresh 2 \
--caption "Cortical Thickness" \
-o snap4.pngRenders a 360° animation of any triangular surface mesh. GIF output uses
pure PIL (no extra install); MP4/WebM requires pip install 'whippersnappy[video]'.
whippersnap1 --mesh $SUBJECT_DIR/surf/lh.white \
--overlay $LH_OVERLAY \
--rotate \
-o rotation.mp4
whippersnap1 --mesh $SUBJECT_DIR/surf/lh.white \
--rotate \
-o rotation.gifLaunches an interactive Qt window with live threshold controls and
mouse-driven rotation, pan, and zoom. Requires
pip install 'whippersnappy[gui]'.
General mode — any triangular mesh:
whippersnap --mesh mesh.off --overlay values.txt
whippersnap --mesh lh.white --overlay lh.thickness --bg-map lh.curvFreeSurfer shortcut — derive all paths from a subject directory:
whippersnap -sd $SUBJECT_DIR --hemi lh -lh $LH_OVERLAY
whippersnap -sd $SUBJECT_DIR --hemi rh --annot rh.aparc.annotFor all options run whippersnap1 --help, whippersnap4 --help, or whippersnap --help.
from whippersnappy import snap1, snap4, snap_rotate, ViewType, get_view_matrix
from whippersnappy import plot3d # requires whippersnappy[notebook]| Function / Class | Description |
|---|---|
snap1 |
Single-view snapshot of any triangular mesh → PIL Image |
snap4 |
Four-view composed image (FreeSurfer subject, lateral/medial both hemispheres) |
snap_rotate |
360° rotation video of any triangular surface mesh (MP4, WebM, or GIF) |
plot3d |
Interactive 3D WebGL viewer for Jupyter notebooks |
ViewType |
Enum of camera presets used by snap1 and snap_rotate |
get_view_matrix |
Return the 4×4 view matrix for a ViewType preset |
ViewType values — pass to the view parameter of snap1 or the
start_view parameter of snap_rotate:
| Value | Description |
|---|---|
ViewType.LEFT |
Left lateral view (default) |
ViewType.RIGHT |
Right lateral view |
ViewType.FRONT |
Frontal / anterior view |
ViewType.BACK |
Posterior view |
ViewType.TOP |
Superior / dorsal view |
ViewType.BOTTOM |
Inferior / ventral view |
Both snap1 and snap_rotate also accept a raw 4×4 NumPy matrix for
view / start_view. Use get_view_matrix to start from a preset and
modify it:
import numpy as np
import pyrr
from whippersnappy import snap1, snap_rotate, ViewType, get_view_matrix
# Tilt the left-lateral preset slightly downward
mat = get_view_matrix(ViewType.LEFT).copy()
tilt = np.array(pyrr.Matrix44.from_x_rotation(np.radians(10)), dtype=np.float32)
mat = tilt @ mat
img = snap1('lh.white', overlay='lh.thickness', view=mat)
snap_rotate('lh.white', 'rotation.mp4', start_view=mat)Supported mesh inputs (for snap1, snap_rotate, and plot3d):
FreeSurfer binary surfaces (e.g. lh.white), OFF (.off), legacy ASCII
VTK PolyData (.vtk), ASCII PLY (.ply), GIfTI surface
(.gii, .surf.gii), or a (vertices, faces) NumPy array tuple.
Supported overlay / label inputs:
FreeSurfer morph (.curv, .thickness), MGH/MGZ (.mgh, .mgz),
plain text / CSV (.txt, .csv), NumPy (.npy, .npz),
GIfTI functional/label (.func.gii, .label.gii).
from whippersnappy import snap1, snap4, ViewType
# FreeSurfer surface with overlay — default left lateral view
img = snap1('lh.white',
overlay='lh.thickness',
bg_map='lh.curv',
roi='lh.cortex.label')
img.save('snap1.png')
# Specific view
img = snap1('lh.white', overlay='lh.thickness', view=ViewType.FRONT)
img.save('snap1_front.png')
# Four-view overview (FreeSurfer subject directory)
img = snap4(lh_overlay='/path/to/lh.thickness',
rh_overlay='/path/to/rh.thickness',
sdir='/path/to/subject',
colorbar=True,
caption='Cortical Thickness (mm)')
img.save('snap4.png')
# OFF / VTK / PLY / GIfTI mesh with a plain-text overlay
img = snap1('mesh.off', overlay='values.txt')
img = snap1('surface.surf.gii', overlay='overlay.func.gii')
# Array inputs (e.g. from LaPy or trimesh)
import numpy as np
v = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]], dtype=np.float32)
f = np.array([[0,2,1],[0,1,3],[0,3,2],[1,2,3]], dtype=np.uint32)
overlay = np.array([0.1, 0.5, 0.9, 0.3], dtype=np.float32)
img = snap1((v, f), overlay=overlay)See tutorials/whippersnappy_tutorial.ipynb for complete notebook examples.
The Docker image provides a fully headless OSMesa rendering environment — no
display server, xvfb, or GPU required. See DOCKER.md for details.
https://deep-mi.org/WhipperSnapPy
Lab webpage: https://deep-mi.org