Maskel
Vessel Skeletonization and Graph-Based Phenotype Analysis in Retinal Fundus Images and other tubular structures.
Maskel is the core algorithm package: thinning, feature extraction, and the batch CLI. For the napari plugin, see napari-maskel. For benchmarks and the HRF-based analysis notebooks, see maskel-evaluations.
Installation
uv sync # core only
uv sync --extra dev # + test tools
CLI
maskel init config.json
maskel validate config.json
maskel run --input /path/to/images --config config.json --out outputs
A config JSON can also be exported from the napari-maskel plugin's Save Config button and used here directly — both consume the same schema (see below).
Input images can be either a plain binary segmentation mask, or a multi-object instance segmentation map (more than one distinct nonzero value). In the latter case, every object is skeletonized independently — touching-but-distinct objects are correctly kept separate rather than merged into one skeleton — and every output row is tagged with the object_id it came from (the mask's own label value; 1 for a plain binary mask).
CLI outputs:
outputs/summary.csvwith one feature row per object per image- Optional per-image skeleton outputs (default:
.npy) - Optional per-image branch tables when
output.write_branch_csv=true(includes anobject_idcolumn) - Optional per-image node tables when
output.write_node_csv=true(includes anobject_idcolumn) - Optional per-object skeleton graphs when
output.write_graphml=true(one_<object_id>_graph.graphmlfile per object) - Optional per-object pickled networkx graphs when
output.write_networkx_graph=true(one_<object_id>_graph.pklfile per object; richer than GraphML - keeps NaN attributes and native Python types)
Configuration
Extraction and output settings are defined in a JSON config file (e.g. the one exported from napari or written by hand).
{
"schema_version": 6,
"extraction": {
"branches": false,
"branch_color_property": "tortuosity",
"branch_text": false,
"nodes": false,
"summary": true,
"fractal_dimension": false,
"mask_radius": false,
"junction_cleanup": false,
"cleanup_threshold_factor": 2.5,
"prune_spurs": false,
"min_spur_length": 10.0,
"spur_iterations": 1,
"closing_iterations": 0,
"fill_holes": false,
"max_hole_size": 0,
"show_preprocessed": false,
"spacing": null
},
"output": {
"write_skeleton_npy": true,
"write_skeleton_png": false,
"write_summary_csv": true,
"write_branch_csv": false,
"write_node_csv": false,
"write_radius": false,
"write_graphml": false,
"write_networkx_graph": false
}
}
| Key | Type | Default | Description |
|---|---|---|---|
extraction.branches |
bool | false |
Extract per-branch features for CSV export or napari visualization |
extraction.branch_color_property |
str | "tortuosity" |
Branch property used to color the napari shapes layer; one of object_id, tortuosity, straightness, mean_radius, std_radius, volume, surface_area, ... |
extraction.branch_text |
bool | false |
Display branch ID, length, and tortuosity labels on the napari branch layer |
extraction.nodes |
bool | false |
Extract per-node features for CSV export or napari visualization |
extraction.summary |
bool | true |
Compute summary features |
extraction.fractal_dimension |
bool | false |
Compute fractal dimension of the skeleton |
extraction.mask_radius |
bool | false |
Estimate mask radius using EDT from the segmentation |
extraction.junction_cleanup |
bool | false |
Clean up ambiguous junction pixels after thinning |
extraction.cleanup_threshold_factor |
float | 2.5 |
Sensitivity for junction cleanup (higher = larger cycles get collapsed) |
extraction.prune_spurs |
bool | false |
Remove short endpoint-to-junction branches (thinning spur artifacts) after skeletonization |
extraction.min_spur_length |
float | 10.0 |
Branches shorter than this (in pixels) qualify as spurs when prune_spurs is true |
extraction.spur_iterations |
int | 1 |
How often pruning is repeated on its own output, since removing a spur can expose new ones |
extraction.closing_iterations |
int | 0 |
Morphological closing iterations applied before thinning (0 = disabled) |
extraction.fill_holes |
bool | false |
Fill holes in the binary segmentation before thinning |
extraction.max_hole_size |
int | 0 |
Maximum hole area (px) to fill when fill_holes is true; 0 = fill all |
extraction.show_preprocessed |
bool | false |
Show preprocessed binary layer (after closing and hole filling) in the napari viewer |
extraction.spacing |
list[float] or null |
null |
Per-axis physical pixel/voxel size (length must match the image's dimensionality: 2 for 2D, 3 for 3D). When set, length/area/volume features come out in physical units instead of pixel units. A per-image dimensionality mismatch falls back to null (isotropic pixel units) with a warning rather than failing the batch. Box-counting fractal_dimension is only valid for isotropic voxels, so it's forced to 0.0 (with a warning) whenever spacing is set and anisotropic. |
output.write_skeleton_npy |
bool | true |
Save skeleton as .npy (NumPy array) per image |
output.write_skeleton_png |
bool | false |
Save binary skeleton mask as .png per image |
output.write_summary_csv |
bool | true |
Write aggregated per-image features to summary.csv |
output.write_branch_csv |
bool | false |
Write per-branch CSV tables (requires extraction.branches) |
output.write_node_csv |
bool | false |
Write per-node CSV tables (requires extraction.nodes) |
output.write_radius |
bool | false |
Write per-pixel radius matrix as .npy (requires extraction.mask_radius) |
output.write_graphml |
bool | false |
Write skeleton graph as .graphml per image (nodes = graph nodes, edges = branches) |
output.write_networkx_graph |
bool | false |
Write skeleton graph as a pickled networkx.MultiGraph per image - same node/edge/graph attributes as write_graphml, but keeps NaN values and native Python types rather than GraphML/yEd-safe ones |
Shell completions
# zsh
eval "$(maskel completions zsh)"
# bash
eval "$(maskel completions bash)"
# PowerShell
maskel completions powershell | Out-String | Invoke-Expression
Add the appropriate line to your shell rc for persistent tab-completion.
Tests
uv sync --extra dev && pytest # all tests
uv sync --extra dev && pytest -m "not slow" # skip the slow 3D comparison test
- 3D comparison - maskel
lee94_thinvsskimage.morphology.skeletonizeon a brain volume (from scikit-image), asserting identical output
Real-data regression tests against the HRF dataset (2D thinning + feature extraction on all 45 samples) live in maskel-evaluations, since they depend on that external dataset.
License
Maskel is released under the MIT License. See LICENSE for details.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file maskel-1.1.0.tar.gz.
File metadata
- Download URL: maskel-1.1.0.tar.gz
- Upload date:
- Size: 57.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
719008e4184b4c3f4c8de435eefbfd8fb9de8fdb492005324e573c221b38fcf1
|
|
| MD5 |
a02a6c8cf66aa525d05f5e68d1e69aec
|
|
| BLAKE2b-256 |
7600e1d2e0598223d391307a7846d6f1534cf3c0cc4e02bc288c9eee3089c2cc
|
Provenance
The following attestation bundles were made for maskel-1.1.0.tar.gz:
Publisher:
release.yml on bionetslab/maskel
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
maskel-1.1.0.tar.gz -
Subject digest:
719008e4184b4c3f4c8de435eefbfd8fb9de8fdb492005324e573c221b38fcf1 - Sigstore transparency entry: 2545430252
- Sigstore integration time:
-
Permalink:
bionetslab/maskel@8c9161ce4942c96933c40f49946e9a1b2ecb619e -
Branch / Tag:
refs/heads/main - Owner: https://github.com/bionetslab
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@8c9161ce4942c96933c40f49946e9a1b2ecb619e -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file maskel-1.1.0-py3-none-any.whl.
File metadata
- Download URL: maskel-1.1.0-py3-none-any.whl
- Upload date:
- Size: 41.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
52051e8dc7450614d79e4ba62670e09c79575c513f19d0f9e30f77b88e53c1ae
|
|
| MD5 |
8e7b70a1a1e3c43ff76a685a01e567fe
|
|
| BLAKE2b-256 |
d295af0303e61f2853a7e199d06c383756a8c8287a5ed00ce138e8b149384b97
|
Provenance
The following attestation bundles were made for maskel-1.1.0-py3-none-any.whl:
Publisher:
release.yml on bionetslab/maskel
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
maskel-1.1.0-py3-none-any.whl -
Subject digest:
52051e8dc7450614d79e4ba62670e09c79575c513f19d0f9e30f77b88e53c1ae - Sigstore transparency entry: 2545430783
- Sigstore integration time:
-
Permalink:
bionetslab/maskel@8c9161ce4942c96933c40f49946e9a1b2ecb619e -
Branch / Tag:
refs/heads/main - Owner: https://github.com/bionetslab
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@8c9161ce4942c96933c40f49946e9a1b2ecb619e -
Trigger Event:
workflow_dispatch
-
Statement type: