CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-kimimaro

Skeletonize densely labeled image volumes using TEASAR-derived algorithms for neuroscience and connectomics research.

Pending
Overview
Eval results
Files

post-processing.mddocs/

Post-processing and Quality Enhancement

Post-processing functions improve skeleton quality by removing artifacts, joining disconnected components, eliminating loops, and trimming small branches. These functions are essential for preparing skeletons for visualization and analysis.

Imports

from typing import Sequence
from osteoid import Skeleton

Capabilities

Comprehensive Post-processing

Applies a complete post-processing pipeline to clean and improve skeleton quality.

def postprocess(
    skeleton: Skeleton,
    dust_threshold: float = 1500.0,
    tick_threshold: float = 3000.0
) -> Skeleton:
    """
    Apply comprehensive post-processing to improve skeleton quality.

    The following steps are applied in order:
    1. Remove disconnected components smaller than dust_threshold
    2. Remove loops (skeletons should be trees)
    3. Join close components within radius threshold
    4. Remove small branches (ticks) smaller than tick_threshold

    Parameters:
    - skeleton (Skeleton): Input skeleton to process
    - dust_threshold (float): Remove components smaller than this (physical units)
    - tick_threshold (float): Remove branches smaller than this (physical units)

    Returns:
    Skeleton: Cleaned and improved skeleton
    """

Usage Example

import kimimaro

# Generate initial skeletons
skeletons = kimimaro.skeletonize(labels, anisotropy=(16, 16, 40))

# Post-process each skeleton
cleaned_skeletons = {}
for label_id, skeleton in skeletons.items():
    cleaned_skeletons[label_id] = kimimaro.postprocess(
        skeleton,
        dust_threshold=2000,  # Remove components < 2000 nm
        tick_threshold=5000   # Remove branches < 5000 nm
    )

# The cleaned skeletons have:
# - No small disconnected pieces
# - No loops (proper tree structure)
# - Connected nearby components
# - No tiny branches/artifacts

Component Joining

Connects nearby skeleton components by finding the closest vertices and linking them, useful for joining skeletons from adjacent image chunks or reconnecting artificially separated components.

def join_close_components(
    skeletons: Sequence[Skeleton],
    radius: float = float('inf'),
    restrict_by_radius: bool = False
) -> Skeleton:
    """
    Join nearby skeleton components within specified distance.

    Given a set of skeletons which may contain multiple connected components,
    attempts to connect each component to the nearest other component via
    the nearest two vertices. Repeats until no components remain or no
    points closer than radius are available.

    Parameters:
    - skeletons (list or Skeleton): Input skeleton(s) to process
    - radius (float): Maximum distance for joining components (default: infinity)
    - restrict_by_radius (bool): If True, only join within boundary distance radius

    Returns:
    Skeleton: Single connected skeleton with components joined
    """

Usage Example

import kimimaro
from osteoid import Skeleton

# Join multiple skeletons from the same neuron
skeleton_chunks = [skel1, skel2, skel3]  # From adjacent image volumes

# Join all components without distance restriction
merged_skeleton = kimimaro.join_close_components(skeleton_chunks)

# Join only components within 1500 nm of each other
selective_merge = kimimaro.join_close_components(
    skeleton_chunks,
    radius=1500,
    restrict_by_radius=True
)

# Join components of a single fragmented skeleton
fragmented_skeleton = skeletons[neuron_id]
connected_skeleton = kimimaro.join_close_components([fragmented_skeleton])

Advanced Post-processing Workflow

For complex skeletonization tasks, you may want to apply post-processing steps individually:

import kimimaro

# Get initial skeleton
skeleton = skeletons[target_neuron_id]

# Step 1: Remove small artifacts first
clean_skeleton = kimimaro.postprocess(
    skeleton,
    dust_threshold=1000,   # Conservative dust removal
    tick_threshold=0       # Skip tick removal for now
)

# Step 2: Join components from different image chunks
if len(skeleton_chunks) > 1:
    joined_skeleton = kimimaro.join_close_components(
        skeleton_chunks,
        radius=2000,  # 2 micron joining threshold
        restrict_by_radius=True
    )
else:
    joined_skeleton = clean_skeleton

# Step 3: Final cleanup with aggressive tick removal
final_skeleton = kimimaro.postprocess(
    joined_skeleton,
    dust_threshold=2000,   # More aggressive dust removal
    tick_threshold=3000    # Remove small branches
)

# Step 4: Validate result
print(f"Original vertices: {len(skeleton.vertices)}")
print(f"Final vertices: {len(final_skeleton.vertices)}")
print(f"Connected components: {final_skeleton.n_components}")

Quality Assessment

After post-processing, you can assess skeleton quality:

# Check connectivity
n_components = skeleton.n_components
if n_components == 1:
    print("Skeleton is fully connected")
else:
    print(f"Skeleton has {n_components} disconnected components")

# Check for loops (should be 0 for proper tree structure)
n_cycles = len(skeleton.cycles())
if n_cycles == 0:
    print("Skeleton is a proper tree")
else:
    print(f"Warning: Skeleton has {n_cycles} loops")

# Measure total cable length
total_length = skeleton.cable_length()
print(f"Total cable length: {total_length:.1f} physical units")

# Check branch complexity
branch_points = len([v for v in skeleton.vertices if len(skeleton.edges[v]) > 2])
endpoints = len([v for v in skeleton.vertices if len(skeleton.edges[v]) == 1])
print(f"Branch points: {branch_points}, Endpoints: {endpoints}")

Integration with Analysis Pipeline

Post-processing typically fits into a larger analysis workflow:

import kimimaro
import numpy as np

# 1. Skeletonization
labels = np.load("segmentation.npy")
skeletons = kimimaro.skeletonize(
    labels,
    anisotropy=(16, 16, 40),
    parallel=4,
    progress=True
)

# 2. Post-processing
cleaned_skeletons = {}
for label_id, skeleton in skeletons.items():
    cleaned_skeletons[label_id] = kimimaro.postprocess(
        skeleton,
        dust_threshold=1500,
        tick_threshold=3000
    )

# 3. Cross-sectional analysis
analyzed_skeletons = kimimaro.cross_sectional_area(
    labels,
    cleaned_skeletons,
    anisotropy=(16, 16, 40),
    smoothing_window=5
)

# 4. Export results
for label_id, skeleton in analyzed_skeletons.items():
    with open(f"neuron_{label_id}.swc", "w") as f:
        f.write(skeleton.to_swc())

Common Post-processing Issues

Over-aggressive Dust Removal

  • Problem: Important small structures removed
  • Solution: Use lower dust_threshold or inspect removed components

Incomplete Component Joining

  • Problem: Components remain disconnected despite proximity
  • Solution: Check radius parameter, verify component spacing

Loop Preservation

  • Problem: Biological loops incorrectly removed
  • Solution: Use custom post-processing instead of full postprocess()

Branch Over-trimming

  • Problem: Important dendrites removed as "ticks"
  • Solution: Lower tick_threshold or use length-based filtering

Install with Tessl CLI

npx tessl i tessl/pypi-kimimaro

docs

analysis-utilities.md

cli-interface.md

core-skeletonization.md

index.md

point-connection.md

post-processing.md

tile.json