Metadata-Version: 2.4
Name: whippersnappy
Version: 2.1.0
Summary: A package to plot and capture publication-ready surfaces with overlays, for example, FastSurfer/FreeSurfer brain data.
Author-email: Martin Reuter <martin.reuter@dzne.de>
Maintainer-email: Martin Reuter <martin.reuter@dzne.de>
License-Expression: MIT
Project-URL: homepage, https://github.com/Deep-MI/WhipperSnapPy
Project-URL: documentation, https://github.com/Deep-MI/WhipperSnapPy
Project-URL: source, https://github.com/Deep-MI/WhipperSnapPy
Project-URL: tracker, https://github.com/Deep-MI/WhipperSnapPy/issues
Keywords: python,FreeSurfer,Brain MRI,Cortical Surface
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: Unix
Classifier: Operating System :: MacOS
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Natural Language :: English
Classifier: Intended Audience :: Science/Research
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: glfw
Requires-Dist: numpy>=1.21
Requires-Dist: pyrr
Requires-Dist: pillow>=9.1
Requires-Dist: pyopengl>=3.1.8
Requires-Dist: nibabel
Requires-Dist: psutil
Provides-Extra: build
Requires-Dist: build; extra == "build"
Requires-Dist: twine; extra == "build"
Provides-Extra: doc
Requires-Dist: furo!=2023.8.17; extra == "doc"
Requires-Dist: matplotlib; extra == "doc"
Requires-Dist: memory-profiler; extra == "doc"
Requires-Dist: myst-parser; extra == "doc"
Requires-Dist: numpydoc; extra == "doc"
Requires-Dist: sphinx!=7.2.*; extra == "doc"
Requires-Dist: sphinxcontrib-bibtex; extra == "doc"
Requires-Dist: sphinx-copybutton; extra == "doc"
Requires-Dist: sphinx-design; extra == "doc"
Requires-Dist: sphinx-issues; extra == "doc"
Requires-Dist: pypandoc; extra == "doc"
Requires-Dist: nbsphinx; extra == "doc"
Requires-Dist: IPython; extra == "doc"
Requires-Dist: ipykernel; extra == "doc"
Requires-Dist: pooch>=1.6; extra == "doc"
Requires-Dist: pythreejs; extra == "doc"
Requires-Dist: ipywidgets; extra == "doc"
Requires-Dist: imageio>=2.28; extra == "doc"
Provides-Extra: notebook
Requires-Dist: pythreejs; extra == "notebook"
Requires-Dist: ipywidgets; extra == "notebook"
Requires-Dist: pooch>=1.6; extra == "notebook"
Provides-Extra: gui
Requires-Dist: PyQt6; extra == "gui"
Provides-Extra: video
Requires-Dist: imageio>=2.28; extra == "video"
Requires-Dist: imageio-ffmpeg>=0.4.9; extra == "video"
Provides-Extra: style
Requires-Dist: bibclean; extra == "style"
Requires-Dist: codespell; extra == "style"
Requires-Dist: pydocstyle[toml]; extra == "style"
Requires-Dist: ruff; extra == "style"
Provides-Extra: test
Requires-Dist: pytest; extra == "test"
Requires-Dist: pytest-cov; extra == "test"
Requires-Dist: pytest-timeout; extra == "test"
Provides-Extra: all
Requires-Dist: whippersnappy[build]; extra == "all"
Requires-Dist: whippersnappy[doc]; extra == "all"
Requires-Dist: whippersnappy[style]; extra == "all"
Requires-Dist: whippersnappy[test]; extra == "all"
Requires-Dist: whippersnappy[notebook]; extra == "all"
Requires-Dist: whippersnappy[gui]; extra == "all"
Requires-Dist: whippersnappy[video]; extra == "all"
Provides-Extra: full
Requires-Dist: whippersnappy[all]; extra == "full"
Dynamic: license-file

# WhipperSnapPy

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.

## Installation

```bash
pip install whippersnappy
```

For rotation video support (MP4/WebM — GIF works without this):

```bash
pip install 'whippersnappy[video]'
```

For the interactive desktop GUI:

```bash
pip install 'whippersnappy[gui]'
```

For interactive 3D in Jupyter notebooks:

```bash
pip install 'whippersnappy[notebook]'
```

Off-screen (headless) rendering on **Linux** uses **EGL** with Mesa's llvmpipe
CPU software renderer — no GPU or display server required.  The log reports:
```
EGL context active — CPU software rendering (llvmpipe (...), ...)
```
When a GPU is accessible (native install, Docker with `--gpus all`, or
Singularity with `--nv`), EGL selects it automatically:
```
EGL context active — GPU rendering (...)
```
OSMesa (`libosmesa6`) is a last-resort CPU fallback used only when EGL
itself cannot initialise (e.g. `libegl1` not installed).

On **Windows**, GLFW creates an invisible window; a GPU driver is sufficient.
On **macOS**, a real display connection is required (NSGL does not support
headless rendering).
See the <a href="DOCKER.md">Docker/Singularity guide</a> for container usage.

## Command-Line Usage

After installation the following commands are available:

### Single-view snapshot (`whippersnap1`)

Renders one view of any triangular surface mesh:

```bash
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.png
```

### Four-view snapshot (`whippersnap4`)

Renders lateral and medial views of both hemispheres into a single composed image:

```bash
whippersnap4 -lh $LH_OVERLAY \
             -rh $RH_OVERLAY \
             -sd $SUBJECT_DIR \
             --fmax 4 --fthresh 2 \
             --caption "Cortical Thickness" \
             -o snap4.png
```

### Rotation video (`whippersnap1 --rotate`)

Renders a 360° animation of any triangular surface mesh.  GIF output uses
pure PIL (no extra install); MP4/WebM requires `pip install 'whippersnappy[video]'`.

```bash
whippersnap1 --mesh $SUBJECT_DIR/surf/lh.white \
             --overlay $LH_OVERLAY \
             --rotate \
             -o rotation.mp4

whippersnap1 --mesh $SUBJECT_DIR/surf/lh.white \
             --rotate \
             -o rotation.gif
```

### Desktop GUI (`whippersnap`)

Launches 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:

```bash
whippersnap --mesh mesh.off --overlay values.txt
whippersnap --mesh lh.white --overlay lh.thickness --bg-map lh.curv
```

**FreeSurfer shortcut** — derive all paths from a subject directory:

```bash
whippersnap -sd $SUBJECT_DIR --hemi lh -lh $LH_OVERLAY
whippersnap -sd $SUBJECT_DIR --hemi rh --annot rh.aparc.annot
```

For all options run `whippersnap1 --help`, `whippersnap4 --help`, or `whippersnap --help`.

## Python API

```python
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:

```python
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`).

### Examples

```python
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.


## Docker

The Docker image provides a fully headless rendering environment using EGL —
CPU software rendering by default, GPU rendering with `--gpus all` (NVIDIA).
See <a href="DOCKER.md"><strong>DOCKER.md</strong></a> for details.

## API Documentation

https://deep-mi.org/WhipperSnapPy

## Links

Lab webpage: https://deep-mi.org
