This is a Python package designed to take a 3D closed mesh surface (obj format, triangle-faced mesh) and the results of a mean curvature flow calculation (MCF) (polylines format) and produce an SWC model.
Polylines text format is
N x1 y1 z1 x2 y2 z2 ... xN yN zN
for each branch of the model.
The package provides three main classes for working with neuron morphology:
MeshManager: Handles 3D mesh surfaces (OBJ format)SkeletonGraph: Graph-based skeleton representation (loads from polylines format)MorphologyGraph: SWC-like morphology model produced byfit_morphology, can be saved to SWC format with cycle annotations
The main purpose of this package is to take a closed triangle mesh and the output of a mean curvature flow calculation (MCF) and convert it into an SWC model. The algorithm is as follows:
- Load the triangle mesh using
MeshManagerand the MCF polylines usingSkeletonGraph. - Optionally optimize the skeleton using
SkeletonOptimizerto improve medial axis alignment. - Use
fit_morphologyto determine junction locations along the skeleton and estimate radii. - The result is a
MorphologyGraphcontaining the skeleton with radii information. - Export the graph as an SWC file.
Since SWC format does not support cycles, a graph with cycles is broken by locating a duplicate node and rewiring one incident cycle edge to the duplicate.
The mean curvature flow (MCF) is a geometric evolution of a surface in 3D space. It is a process where the surface evolves over time, with the rate of change of the surface at each point determined by the mean curvature at that point. The mean curvature flow is a way to smooth out the surface and make it more regular, while preserving its overall shape and topology.
You can easily compute the MCF data using the Computational Geometry Algorithms Library (CGAL) package. CGAL provides a standalone GUI program called CGALLab that includes the MCF skeletonization operation. You can download the Windows demo version of it here: CGALLab Windows Demo.
Simply open the program, load your mesh, go to Operations -> Triangulated Surface Mesh Skeletonization -> Mean Curvature Skeleton (Advanced), apply the operation, then right-click on the new object labelled "fixed points" and save it to a polylines.txt file.
MCF skeletonization reproduces general topology very well, but the resulting skeleton can deviate from the true medial axis and sometimes even clip through the mesh surface, especially in regions with holes or high curvature. The SkeletonOptimizer module provides optimization to refine MCF-generated skeleton polylines by gently pushing points toward the center of the mesh volume.
- Surface crossing detection: Automatically detects skeleton points that are outside the mesh surface
- Medial axis optimization: Pushes skeleton points toward the mesh center while preserving topology
- Smoothing regularization: Maintains polyline smoothness during optimization
- Endpoint preservation: Optional preservation of polyline endpoints
- Configurable parameters: Control optimization behavior with flexible options
from mcf2swc import MeshManager, SkeletonGraph, SkeletonOptimizer, SkeletonOptimizerOptions
# Load mesh and skeleton
mesh_mgr = MeshManager(mesh_path="mesh.obj")
skeleton = SkeletonGraph.from_txt("skeleton.polylines.txt")
# Configure optimization
opts = SkeletonOptimizerOptions(
max_iterations=100,
step_size=0.1,
convergence_threshold=1e-4,
preserve_terminal_nodes=True, # Preserve endpoints
preserve_branch_nodes=False, # Allow branch nodes to move
smoothing_weight=0.5,
n_rays=6, # Number of rays for medial axis estimation
verbose=True
)
# Optimize skeleton
optimizer = SkeletonOptimizer(skeleton, mesh_mgr.mesh, opts)
optimized_skeleton = optimizer.optimize()
# Save optimized skeleton (GraphML format)
optimized_skeleton.to_txt("skeleton_optimized.graphml")The optimizer uses an iterative approach:
- For points outside the mesh: Move toward the closest surface point
- For points inside the mesh: Cast rays in multiple directions to estimate distance to surface, then move toward the medial axis based on distance imbalance
- Apply smoothing: Use Laplacian smoothing to maintain skeleton continuity
- Iterate until convergence: Repeat until point movement falls below threshold
The optimization balances two objectives:
- Centering: Push points toward the medial axis using multi-directional ray sampling
- Smoothing: Maintain smooth skeleton structure
The relative weight of these objectives is controlled by the smoothing_weight parameter (0 = no smoothing, 1 = strong smoothing). The n_rays parameter controls how many directions are sampled for medial axis estimation (default: 6 for axis-aligned rays).
from mcf2swc import MeshManager, SkeletonGraph, SkeletonOptimizer, SkeletonOptimizerOptions, fit_morphology, FitOptions
# 1. Load inputs
mesh_mgr = MeshManager(mesh_path="neuron.obj")
skeleton = SkeletonGraph.from_txt("neuron_mcf.polylines.txt")
# 2. (Optional) Optimize skeleton for better medial axis alignment
opt_options = SkeletonOptimizerOptions(
max_iterations=50,
step_size=0.1,
preserve_terminal_nodes=True,
smoothing_weight=0.5
)
optimizer = SkeletonOptimizer(skeleton, mesh_mgr.mesh, opt_options)
optimized_skeleton = optimizer.optimize()
# 3. Fit morphology (estimate radii and build SWC-like graph)
fit_options = FitOptions(
spacing=1.0, # Sample spacing along skeleton
radius_strategy="equivalent_area" # How to estimate radii
)
morphology = fit_morphology(mesh_mgr.mesh, optimized_skeleton, fit_options)
# 4. Convert to SWC and save
swc_model = morphology.to_swc()
swc_model.to_swc("neuron.swc")The resulting SWC file will have cycle annotations if any cycles were detected in the skeleton topology.