Skip to main content
brain2scalp logo

Map a brain target to its nearest scalp entry point

Python 3.10+ License: MIT


Table of Contents


Features

  • Local-normal ray-cast - queries the k nearest scalp surface voxels to the brain target, takes their centroid to estimate the local outward normal, then casts a ray along that direction to find the scalp entry point. Falls back to directed nearest-neighbour if the ray misses the surface.
  • MNI and Talairach input - Talairach auto-converted to MNI152 via Lancaster et al. (2007) piecewise-affine transform, with distinct left/right hemisphere matrices.
  • Multiple targets - batch coordinate list in one pass.
  • Multi-format output - plain text table, JSON, or CSV.
  • Optional visualization - 6-panel pipeline-steps view, 3-panel orthogonal slices, and interactive Plotly 3D viewer.

Installation

1. Install Python

Requires Python >= 3.10.

Check if you already have it:

python --version
# or
python3 --version

If not Python 3.10.x or higher, download from https://www.python.org/downloads/


2. Create and activate environment (recommended)

venv

python -m venv env_name

# Activate it
# Windows:
env_name\Scripts\activate
# macOS / Linux:
source env_name/bin/activate

conda

conda create -n env_name python=3.10

# Activate it
conda activate env_name

3. Install brain2scalp

From PyPI (recommended):

# Core + CLI (no visualization)
pip install brain2scalp

# With visualization (matplotlib + plotly)
pip install "brain2scalp[viz]"

# Development (adds pytest, ruff, mypy)
pip install "brain2scalp[dev]"

From GitHub:

You need Git installed (git-scm.com). Then:

git clone https://github.com/LeonardoPoliti/brain2scalp.git
cd brain2scalp

# Core + CLI
pip install .

# With visualization
pip install ".[viz]"

Verify the install:

brain2scalp --help

You should see the CLI help text.


Requirements

Dependency Minimum Notes
Python 3.10
nibabel 5.0 NIfTI I/O
numpy 1.24
scipy 1.10 KD-tree, morphology
scikit-image 0.21 Convex hull filling
matplotlib 3.7 [viz] only
plotly 5.18 [viz] only

All dependencies are installed automatically by pip.


Quick Start

Command line examples

# Single MNI target
brain2scalp --nii mni_icbm152_t1_tal_nlin_sym_09c.nii --target -46 20 32

# Talairach target
brain2scalp --nii mni_icbm152_t1_tal_nlin_sym_09c.nii --target -46 20 32 --space talairach

# Multiple targets on one line
brain2scalp --nii head.nii --target "-46 20 32, 10 -20 45"

# Multiple targets from a CSV file
brain2scalp --nii head.nii --target-file targets.csv

# JSON output to file (format inferred from extension)
brain2scalp --nii head.nii --target -46 20 32 --output result.json

# CSV output to file
brain2scalp --nii head.nii --target -46 20 32 --output result.csv

# 3-panel orthogonal slice view of the target
brain2scalp --nii head.nii --target -46 20 32 --vizTarget

# 6-panel pipeline steps figure
brain2scalp --nii head.nii --target -46 20 32 --vizSteps

# Interactive 3D viewer
brain2scalp --nii head.nii --target -46 20 32 --viz3d

# Save all figures without displaying
brain2scalp --nii head.nii --target -46 20 32 --vizTarget --vizSteps --save-fig ./figs --no-plot

Python API examples

from brain2scalp.core import run

# Single target (coordinates only)
result = run(
    nii_path="mni_icbm152_t1_tal_nlin_sym_09c.nii",
    targets=[-46.0, 20.0, 32.0],
)

print(result.scalp_entry_mni)    # [x, y, z] in MNI mm
print(result.distance_mm)        # brain-to-scalp distance (mm)
print(result.projection_method)  # 'ray_cast' or 'nearest_neighbor'
print(result.to_dict())          # JSON-serializable dict

# Single target with volumetric arrays (for visualization)
state = run(
    nii_path="head.nii",
    targets=[-46.0, 20.0, 32.0],
    full=True,
    surface_thickness_mm=1.5,
    k_local=100,
    verbose=True,
)

# Multiple targets
results = run(
    nii_path="head.nii",
    targets=[[-46.0, 20.0, 32.0], [10.0, -20.0, 45.0]],
)

# Multiple targets with volumetric arrays
states = run(
    nii_path="head.nii",
    targets=[[-46.0, 20.0, 32.0], [10.0, -20.0, 45.0]],
    full=True,
    is_talairach=False,
    surface_thickness_mm=1.5,
)

Atlas vs Personal Scans

Using an MNI Atlas

An MNI152 full-head atlas gives a reasonable approximation of scalp topology and is sufficient for group-level studies or when no individual scan is available.

Example atlas: mni_icbm152_t1_tal_nlin_sym_09c.nii
Download from: https://nist.mni.mcgill.ca/icbm-152-nonlinear-atlases-2009/

When to use:

  • You have MNI or Talairach coordinates from a literature study or group activation map.
  • No individual scan is available.
  • You need a quick estimate of scalp position for target planning.

Limitations:

  • Results reflect average anatomy, not the individual's head shape.
  • Accuracy degrades for targets far from group-mean anatomy (e.g. atypical skull shape, lesions).

Using a Personal Head Scan

Scan requirements:

  • Must be a full-head scan
  • .nii or .nii.gz format with valid qform/sform header.
  • 4D volumes are accepted; only the first frame is used.

What to avoid:

  • Brain-extracted images (FSL BET output, FreeSurfer brain.mgz) - detected and rejected.
  • Label/parcellation atlases - also detected and rejected.
  • Files with missing or corrupt affine - rejected at load time.

Option A - Native space

Use this when your target coordinates come from the same scan.

Option B - Register to MNI first

Use this when your target is in MNI space but you want subject-specific scalp topology. Register the full-head scan to the MNI152 template with a linear (12-DOF) registration, then pass the registered scan with --space mni.

Note: Use a full-head MNI template as the registration target to preserve scalp geometry in the output.


Coordinate Space Summary

--space Input type Conversion applied
mni (default) MNI152 mm coordinates None
talairach Talairach mm coordinates Lancaster 2007 piecewise-affine → MNI
native mm in the scan's own space None (use with unregistered personal scans)

CLI Reference

brain2scalp --nii PATH --target COORDS [options]

Input

Flag Default Description
--nii PATH required Full-head .nii or .nii.gz file
--target COORDS required* Target coordinates in mm. Single: '-46 20 32'. Multiple (comma-separated): '-46 20 32, 10 -20 45'
--target-file PATH - CSV file with one target per row (x,y,z). Optional header auto-detected. Combined with --target; duplicates silently dropped.
--space {mni,talairach,native} mni Coordinate space of the input target

* Either --target or --target-file is required.

Processing

Flag Default Description
--threshold FLOAT auto (Otsu) Override head-mask intensity threshold
--surface-thickness MM 1.5 Surface shell erosion depth in mm
--k-local K 100 k-nearest scalp voxels used to estimate local ray direction

Output

Flag Default Description
--output PATH stdout (text) Save result to file. Format is inferred from extension: .txt, .json, or .csv
--quiet off Suppress progress messages

Visualization (requires [viz])

Flag Default Description
--vizSteps [IDX ...] off 6-panel pipeline steps figure. Optionally pass target indices to limit which are shown, e.g. --vizSteps 0 2
--step-axis {0,1,2} auto Slice axis for --vizSteps: 0=sagittal, 1=coronal, 2=axial
--vizTarget [IDX ...] off 3-panel orthogonal slice view centered on target + entry
--viz3d [IDX ...] off Interactive Plotly 3D viewer
--volume-stride N 3 Subsample every Nth voxel for the 3D point cloud. Lower = denser but slower
--save-fig DIR display Save all figures to this directory (created if missing)
--no-plot off Suppress display. Combine with --save-fig to save without showing

Python API

from brain2scalp.core import run

run()

Parameter Type Default Description
nii_path str | Path required Full-head .nii or .nii.gz
targets list[float] or list[list[float]] required Single [x, y, z] or list of triplets in mm
full bool False If True, return PipelineState(s) with volumetric arrays
threshold float | None None (auto) Override head-mask threshold
is_talairach bool False Convert input from Talairach to MNI
is_native bool False Treat target as native scan mm space
surface_thickness_mm float 1.5 Erosion depth for surface shell
k_local int 100 k-nearest scalp voxels for ray direction
verbose bool False Print progress to stdout

Return type mirrors input shape:

targets full Return type
[x, y, z] False ScalpResult
[x, y, z] True PipelineState
[[x,y,z], ...] False list[ScalpResult]
[[x,y,z], ...] True list[PipelineState]
# Single target → ScalpResult
result = run(
    nii_path="mni_icbm152_t1_tal_nlin_sym_09c.nii",
    targets=[-46.0, 20.0, 32.0],
    verbose=True,
)

# Multiple targets → list[ScalpResult] 
results = run(
    nii_path="head.nii",
    targets=[[-46.0, 20.0, 32.0], [10.0, -20.0, 45.0]],
)

# With volumetric arrays for visualization
state = run("head.nii", [-46.0, 20.0, 32.0], full=True)
states = run("head.nii", [[-46.0, 20.0, 32.0], [10.0, -20.0, 45.0]], full=True)

ScalpResult attributes

Attribute Type Description
brain_target_mni list[float] Input target in MNI space
scalp_entry_mni list[float] Scalp entry point in MNI space
distance_mm float Euclidean distance brain→scalp
projection_method str 'ray_cast' or 'nearest_neighbor'
coordinate_space str 'mni', 'talairach', or 'native'
is_talairach bool Whether input was Talairach
tal_target list[float] | None Original Talairach coords (if applicable)
nii_path str Path to the NIfTI file used
atlas_shape tuple NIfTI volume shape
voxel_size_mm tuple Voxel dimensions in mm
threshold_used float Intensity threshold applied
surface_thickness_mm float Erosion depth used
to_dict() dict JSON-serializable representation

PipelineState - additional attributes (full=True)

Attribute Description
data Raw NIfTI volume (float64)
affine NIfTI affine matrix (4×4)
mask Binary head mask (uint8)
filled Hole-filled mask (uint8)
eroded Eroded mask (uint8)
surface Surface shell: filled XOR eroded (uint8)
scalp_surface_mni (N, 3) float64 — all surface voxel centers in MNI mm

Output Formats

Output format is determined by the file extension passed to --output. Without --output, results are printed as text to stdout.

Text (.txt)

When Talairach input is used, tal_x/y/z columns are added automatically.

  target  mni_x   mni_y   mni_z   entry_x  entry_y  entry_z  dist_mm
       0  -46.0    20.0    32.0    -62.3     35.7     47.5     32.5
       1   10.0   -20.0    45.0     14.8    -28.1     58.3     19.2

JSON (.json)

A single result is returned as an object; multiple results as an array. When Talairach input is used, a tal_original field is added.

{
  "target": 0,
  "mni":  { "x": -46.0, "y": 20.0,  "z": 32.0 },
  "entry": { "x": -62.3, "y": 35.7,  "z": 47.5 },
  "distance_mm": 32.5
}

CSV (.csv)

Same columns as text. tal_x/y/z columns included when Talairach input was used.

target,mni_x,mni_y,mni_z,entry_x,entry_y,entry_z,dist_mm
0,-46.0,20.0,32.0,-62.3,35.7,47.5,32.5

Target file format (--target-file)

Plain CSV, one target per row. Header row is auto-detected and skipped.

x,y,z
-46,20,32
10,-20,45

Visualization

Requires pip install "brain2scalp[viz]".

Pipeline steps (--vizSteps)

6-panel figure showing each stage of the head-mask pipeline: raw data → binary mask → hole-filled → eroded → surface shell → overlay with target and entry point.

Pipeline steps


Orthogonal slice view (--vizTarget)

3-panel sagittal / coronal / axial slices centered on the brain target, with the projected scalp entry point overlaid.

Orthogonal slice view


Interactive 3D viewer (--viz3d)

Plotly HTML viewer with isosurface rendering of the head, scalp surface point cloud, and brain target / scalp entry markers. Includes threshold and opacity sliders. Supports up to 8 targets. Stride (settable via --stride) controls voxel subsampling for the isosurface: lower values give finer geometry but increase file size and browser render time; higher values are faster and lighter.

Interactive 3D viewer


Package Layout

brain2scalp/
├── brain2scalp/
│   ├── core/
│   │   ├── models.py        ScalpResult, PipelineState
│   │   ├── transform.py     Talairach ↔ MNI (Lancaster 2007)
│   │   ├── atlas.py         Load, reorient, validate NIfTI
│   │   ├── mask.py          Threshold → fill → erode → surface
│   │   ├── projection.py    Ray-cast + nearest-neighbour fallback
│   │   └── pipeline.py      run() 
│   ├── cli/
│   │   ├── main.py          argparse entry point
│   │   └── formatters.py    text / JSON / CSV output
│   └── viz/
│       ├── slice2d_view.py  Pipeline steps + orthogonal slice figures
│       └── view_3d.py       Plotly interactive 3D viewer
├── tests/
│   ├── test_transform.py
│   ├── test_mask.py
│   ├── test_projection.py
│   ├── test_pipeline.py
│   ├── test_cli_helpers.py
│   └── test_formatters.py
└── assets/
    └── b2s_logo.png

[!WARNING] Research use only. This tool has not been validated for clinical use and must not be used to guide patient treatment, clinical diagnosis or medical decision-making. For any clinical application, always rely on a qualified clinician and a certified neuronavigation system.


Citation

If you use brain2scalp in your research, please cite:

APA: Politi, L. (2026). brain2scalp (v0.2.0). Zenodo. https://doi.org/10.5281/zenodo.20708694

@software{politi_brain2scalp_2026,
  author    = {Politi, Leonardo},
  title     = {brain2scalp},
  year      = {2026},
  publisher = {Zenodo},
  doi       = {10.5281/zenodo.20708694},
  url       = {https://doi.org/10.5281/zenodo.20708694}
}

License

MIT - see LICENSE.

Copyright 2026 Leonardo Politi.

Metadata

Release files for brain2scalp 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for brain2scalp 0.2.0
File Size Uploaded
brain2scalp-0.2.0.tar.gz 47.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for brain2scalp 0.2.0
File Interpreter ABI Platform
brain2scalp-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 96.2 kB

Release files / brain2scalp-0.2.0.tar.gz

Download URL brain2scalp-0.2.0.tar.gz
Size 47.6 kB
Tags Source
SHA-256 checksum
How to use checksums
79a85e4ffd9e1cb1cbbd56f596b908957b34f14f29c542340f13dcaccc2a133b
BLAKE2b-256 checksum
How to use checksums
9938a382eca548d3d5d85b4fb99dd00a4f8493397c8c4ccd9b78a2e40d6960ec
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.5

Release files / brain2scalp-0.2.0-py3-none-any.whl

Download URL brain2scalp-0.2.0-py3-none-any.whl
Size 48.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e39fdca2128c16f7956c0b5cb794ae345cef23e52c228fb444cd4bcd01da2c41
BLAKE2b-256 checksum
How to use checksums
a2601201546a248a5dd008668bcb0accc287880be075db83650df2d71db71487
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.5

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page