Skip to content

Getting started

The notebook presents basic usage of the algorithm.

import sn_graph as sn
from skimage import data
import numpy as np
import matplotlib.pyplot as plt

Scikit-image horse example

img = data.horse()
img = img * -1 + 1  # Switch background and foreground
spheres_centers, edges, sdf_array = sn.create_sn_graph(
    image=img,
    max_num_vertices=300,
    edge_threshold=0.95,
    max_edge_length=-1,
    minimal_sphere_radius=1.5,
    return_sdf=True,
)

graph_img = sn.draw_sn_graph(spheres_centers, edges, sdf_array, background_image=img)
# Create figure with two subplots side by side
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 7))

ax1.imshow(img, cmap="binary")
ax1.set_title("Binary Image")

ax2.imshow(graph_img, cmap="gray")
ax2.set_title("SN-Graph")

print(f"""
Graph Statistics:
Number of nodes: {len(spheres_centers)}
Number of edges: {len(edges)}
""")

plt.tight_layout()
plt.show()

Graph Statistics:
Number of nodes: 207
Number of edges: 385


No description has been provided for this image

Synthetic numpy array example

img = np.zeros((512, 512))
img[0:450, 50:] = 1  # Create a square
img[100:250, 100:250] = 0

spheres_centers, edges, sdf_array = sn.create_sn_graph(
    img, max_num_vertices=20, minimal_sphere_radius=6, return_sdf=True
)

graph_img = sn.draw_sn_graph(spheres_centers, edges, sdf_array, background_image=img)

# Create figure with two subplots side by side
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 7))

ax1.imshow(img, cmap="binary")
ax1.set_title("Binary Image")

ax2.imshow(graph_img, cmap="gray")
ax2.set_title("SN-Graph")

print(f"""
Graph Statistics:
Number of nodes: {len(spheres_centers)}
Number of edges: {len(edges)}
""")

plt.tight_layout()
plt.show()

Graph Statistics:
Number of nodes: 20
Number of edges: 31


No description has been provided for this image

Compare with other skeletonisation approaches

from skimage.morphology import skeletonize, thin, medial_axis
import time
def compare_skeletonization(image):
    timings = {}

    # Basic skeletonization
    start = time.time()
    skeleton_basic = skeletonize(image)
    timings["basic"] = time.time() - start

    # Thinning
    start = time.time()
    skeleton_thin = thin(image)
    timings["thin"] = time.time() - start

    # Medial axis
    start = time.time()
    skeleton_medial = medial_axis(image)
    timings["medial"] = time.time() - start

    # Sphere-node graph without circles
    start = time.time()
    spheres_centers, edges, sdf_array = sn.create_sn_graph(
        image=image,
        max_num_vertices=50,
        edge_threshold=0.95,
        max_edge_length=-1,
        minimal_sphere_radius=3,
        return_sdf=True,
    )
    sn_graph_img = sn.draw_sn_graph(spheres_centers, edges)
    timings["sn_graph"] = time.time() - start

    # Sphere-node graph with circles
    sn_graph_img_circles = sn.draw_sn_graph(spheres_centers, edges, sdf_array)

    # Create visualization
    fig, axes = plt.subplots(2, 3, figsize=(15, 10))

    # Original image
    axes[0, 0].imshow(image, cmap="binary")
    axes[0, 0].set_title("Original")
    axes[0, 0].axis("off")

    # Basic skeletonization
    axes[0, 1].imshow(skeleton_basic, cmap="binary")
    axes[0, 1].set_title(f'Basic Skeletonization\n{timings["basic"]:.3f}s')
    axes[0, 1].axis("off")

    # Medial axis
    axes[0, 2].imshow(skeleton_medial, cmap="binary")
    axes[0, 2].set_title(f'Medial Axis\n{timings["medial"]:.3f}s')
    axes[0, 2].axis("off")

    # Thinning
    axes[1, 0].imshow(skeleton_thin, cmap="binary")
    axes[1, 0].set_title(f'Thinning\n{timings["thin"]:.3f}s')
    axes[1, 0].axis("off")

    # SN Graph without circles
    axes[1, 1].imshow(sn_graph_img * (-1) + 1, cmap="gray")
    axes[1, 1].set_title(f'SN-Graph\n{timings["sn_graph"]:.3f}s')
    axes[1, 1].axis("off")
    # SN Graph with circles
    axes[1, 2].imshow(sn_graph_img_circles * (-1) + 1, cmap="gray")
    axes[1, 2].set_title("SN-Graph with Circles")
    axes[1, 2].axis("off")

    plt.tight_layout()
    plt.show()

    # Print timing statistics
    print("\nExecution Times:")
    for method, timing in timings.items():
        print(f"{method}: {timing:.3f} seconds")

    # Print skeleton statistics
    print("\nSkeleton Statistics:")
    print(f"Basic Skeletonization pixels: {np.sum(skeleton_basic)}")
    print(f"Thinning pixels: {np.sum(skeleton_thin)}")
    print(f"Medial Axis pixels: {np.sum(skeleton_medial)}")
    print(f"SN Graph nodes: {len(spheres_centers)}")
    print(f"SN Graph edges: {len(edges)}")

    return {
        "basic": skeleton_basic,
        "thin": skeleton_thin,
        "medial": skeleton_medial,
        "sn_graph": sn_graph_img,
        "sn_graph_circles": sn_graph_img_circles,
        "timings": timings,
    }
# Load the image and invert it
img = data.horse()
img = img * -1 + 1
# Run the comparison using the image
results = compare_skeletonization(img)
No description has been provided for this image

Execution Times:
basic: 0.009 seconds
thin: 0.150 seconds
medial: 0.029 seconds
sn_graph: 0.230 seconds

Skeleton Statistics:
Basic Skeletonization pixels: 1197
Thinning pixels: 1179
Medial Axis pixels: 1202
SN Graph nodes: 50
SN Graph edges: 78