Table of Contents
- Features
- Installation
- Quick Start
- Atlas vs Personal Scans
- CLI Reference
- Python API
- Output Formats
- Visualization
- Package Layout
- Citation
- License
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
.niior.nii.gzformat 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.
Orthogonal slice view (--vizTarget)
3-panel sagittal / coronal / axial slices centered on the brain target, with the projected scalp entry point overlaid.
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.
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)
| File | Size | Uploaded | |
|---|---|---|---|
| brain2scalp-0.2.0.tar.gz | 47.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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
|