Skeletonize densely labeled image volumes using TEASAR-derived algorithms for neuroscience and connectomics research.
—
Functions for creating direct connections between specific points and converting synapse locations to skeleton targets. These capabilities are useful for precise path tracing and integrating external annotations into skeletonization workflows.
Creates a skeleton path between two preselected points on a binary image, finding the optimal route through the object geometry.
def connect_points(
labels,
start,
end,
anisotropy=(1,1,1),
fill_holes=False,
in_place=False,
pdrf_scale=100000,
pdrf_exponent=4
):
"""
Draw a centerline between preselected points on a binary image.
Extract a single centerline skeleton between two preselected points
from a binary image using a penalty distance field approach.
Parameters:
- labels (numpy.ndarray): Binary image (2D or 3D)
- start (tuple): Starting point coordinates (x, y, z) in voxel space
- end (tuple): Ending point coordinates (x, y, z) in voxel space
- anisotropy (tuple): Physical voxel dimensions (default: (1,1,1))
- fill_holes (bool): Fill holes in shapes before tracing (default: False)
- in_place (bool): Modify input array in place (default: False)
- pdrf_scale (float): Penalty field scale factor (default: 100000)
- pdrf_exponent (int): Penalty field exponent (default: 4)
Returns:
Skeleton: Single skeleton tracing the path between start and end points
Raises:
ValueError: If start and end points are in disconnected components
Notes:
- Input must be binary (True/False values)
- Coordinates are in voxel space (not physical units)
- Uses penalty distance field with Dijkstra's algorithm
- Start and end points must be in the same connected component
"""import kimimaro
import numpy as np
# Extract single neuron as binary image
neuron_binary = (labels == target_neuron_id)
# Connect soma to axon terminal
soma_center = (125, 200, 45) # Coordinates in voxels
axon_terminal = (890, 1240, 156) # Coordinates in voxels
# Trace direct connection
direct_path = kimimaro.connect_points(
neuron_binary,
start=soma_center,
end=axon_terminal,
anisotropy=(16, 16, 40) # nm per voxel
)
print(f"Direct path length: {direct_path.cable_length():.1f} nm")
print(f"Path vertices: {len(direct_path.vertices)}")
# Compare with full skeletonization
full_skeleton = kimimaro.skeletonize(
neuron_binary.astype(np.uint32),
anisotropy=(16, 16, 40)
)[1]
print(f"Full skeleton length: {full_skeleton.cable_length():.1f} nm")
print(f"Direct path is {direct_path.cable_length()/full_skeleton.cable_length()*100:.1f}% of full skeleton")# Connect multiple points in sequence
connection_points = [
(100, 150, 30), # Start point
(200, 250, 45), # Intermediate point 1
(350, 400, 60), # Intermediate point 2
(500, 550, 75) # End point
]
# Create sequential connections
path_segments = []
for i in range(len(connection_points) - 1):
segment = kimimaro.connect_points(
neuron_binary,
start=connection_points[i],
end=connection_points[i + 1],
anisotropy=(16, 16, 40)
)
path_segments.append(segment)
# Combine segments (you would need to implement skeleton merging)
print(f"Created {len(path_segments)} path segments")Converts synapse centroid locations to skeleton target points, enabling enhanced skeletonization around synaptic sites.
def synapses_to_targets(labels, synapses, progress=False):
"""
Convert synapse centroids to skeleton targets.
Given synapse centroids (in voxels) and desired SWC integer labels,
finds the nearest voxel to each centroid for the corresponding label.
This enables targeted skeletonization that ensures skeleton paths
pass through or near synaptic sites.
Parameters:
- labels (numpy.ndarray): Labeled volume with neuron segments
- synapses (dict): Mapping of {label_id: [((x,y,z), swc_label), ...]}
- label_id: Which neuron label the synapse belongs to
- (x,y,z): Synapse centroid coordinates in voxel space
- swc_label: SWC node type for this synapse (e.g., 3=dendrite, 4=axon)
- progress (bool): Show progress bar (default: False)
Returns:
dict: Mapping of {(x,y,z): swc_label, ...} for use as extra_targets
Notes:
- Coordinates are in voxel space, not physical units
- SWC labels follow standard: 1=soma, 2=axon, 3=dendrite, 4=apical, etc.
- Targets can be used with extra_targets_before or extra_targets_after
"""import kimimaro
import numpy as np
# Define synapse locations for multiple neurons
synapses = {
# Neuron 1 has 3 synapses
1: [
((120, 200, 30), 3), # Dendrite synapse
((150, 220, 35), 3), # Another dendrite synapse
((180, 240, 40), 4), # Apical dendrite synapse
],
# Neuron 5 has 2 synapses
5: [
((80, 180, 25), 2), # Axon synapse
((90, 190, 28), 2), # Another axon synapse
],
# Neuron 12 has 1 soma synapse
12: [
((300, 400, 60), 1), # Soma synapse
]
}
# Convert synapses to target format
targets = kimimaro.synapses_to_targets(
labels,
synapses,
progress=True
)
print(f"Generated {len(targets)} target points")
print("Target locations:", list(targets.keys())[:5])
# Use targets in skeletonization to ensure paths hit synapses
skeletons = kimimaro.skeletonize(
labels,
anisotropy=(16, 16, 40),
extra_targets_after=list(targets.keys()), # Add as extra endpoints
teasar_params={
"scale": 1.5,
"const": 300,
"soma_detection_threshold": 750,
},
progress=True
)
# The resulting skeletons will have paths that pass through synapse locations
for label_id, skeleton in skeletons.items():
if label_id in synapses:
n_synapses = len(synapses[label_id])
print(f"Neuron {label_id}: {n_synapses} synapses, {len(skeleton.vertices)} skeleton vertices")import kimimaro
import pandas as pd
import numpy as np
# Load connectomics annotations
def load_synapse_annotations(annotation_file):
"""Load synapse annotations from CSV or database."""
# Example format: neuron_id, x, y, z, synapse_type, confidence
df = pd.read_csv(annotation_file)
synapses = {}
for _, row in df.iterrows():
neuron_id = int(row['neuron_id'])
coords = (int(row['x']), int(row['y']), int(row['z']))
# Map synapse type to SWC label
swc_type_map = {
'presynaptic': 2, # Axon terminal
'postsynaptic': 3, # Dendrite
'soma_synapse': 1, # Soma
'apical': 4 # Apical dendrite
}
swc_label = swc_type_map.get(row['synapse_type'], 3)
if neuron_id not in synapses:
synapses[neuron_id] = []
synapses[neuron_id].append((coords, swc_label))
return synapses
# Load and process annotations
synapses = load_synapse_annotations("connectome_synapses.csv")
targets = kimimaro.synapses_to_targets(labels, synapses, progress=True)
# Skeletonize with synapse targets
skeletons = kimimaro.skeletonize(
labels,
anisotropy=(8, 8, 30), # High-resolution EM data
extra_targets_after=list(targets.keys()),
teasar_params={
"scale": 1.2, # Tighter invalidation for precise targeting
"const": 200, # Smaller minimum radius
"pdrf_scale": 150000, # Higher penalty field influence
},
parallel=8,
progress=True
)
print(f"Processed {len(skeletons)} neurons with {len(targets)} synapse targets")def validate_synapse_targeting(skeletons, synapses, targets, max_distance=500):
"""Validate that skeletons actually reach synapse targets."""
validation_results = {}
for label_id, skeleton in skeletons.items():
if label_id not in synapses:
continue
results = {
'synapses_targeted': len(synapses[label_id]),
'distances': [],
'missed_synapses': []
}
# Check each synapse
for (synapse_coords, swc_label) in synapses[label_id]:
# Find closest skeleton vertex to synapse
skeleton_coords = skeleton.vertices
distances = np.sqrt(np.sum((skeleton_coords - np.array(synapse_coords))**2, axis=1))
min_distance = np.min(distances)
results['distances'].append(min_distance)
if min_distance > max_distance:
results['missed_synapses'].append({
'coords': synapse_coords,
'distance': min_distance,
'swc_label': swc_label
})
results['mean_distance'] = np.mean(results['distances'])
results['max_distance'] = np.max(results['distances'])
results['success_rate'] = 1 - len(results['missed_synapses']) / len(synapses[label_id])
validation_results[label_id] = results
return validation_results
# Run validation
validation = validate_synapse_targeting(skeletons, synapses, targets)
for label_id, results in validation.items():
print(f"Neuron {label_id}:")
print(f" Success rate: {results['success_rate']*100:.1f}%")
print(f" Mean distance to synapses: {results['mean_distance']:.1f} nm")
if results['missed_synapses']:
print(f" Missed {len(results['missed_synapses'])} synapses")Install with Tessl CLI
npx tessl i tessl/pypi-kimimaro