Skip to content

API Reference

create_sn_graph(image, max_num_vertices=-1, edge_threshold=1.0, max_edge_length=-1, minimal_sphere_radius=5.0, edge_sphere_threshold=1.0, return_sdf=False)

Create a graph from an image/volume using the Sphere-Node (SN) graph skeletonisation algorithm.

This function converts a grayscale (or binary) image/volume into a graph representation by first computing its signed distance field (assuming boundary contour has value 0), then placing sphere centers as vertices and creating edges between neighboring spheres based on specified criteria.

Parameters:

Name Type Description Default
image ndarray

Input image/volume where the foreground is positive and the background is 0. Can be a numpy array of arbitrary dimension.

required
max_num_vertices int

Maximum number of vertices (sphere centers) to generate. If -1, no limit is applied. Defaults to -1.

-1
edge_threshold float

Threshold value for determining the minimal portion of an edge that must lie within the object. Higher value is more restrictive, with 1 requiring edge to be fully contained in the object. Defaults to 1.0.

1.0
max_edge_length int

Maximum allowed length for edges between vertices. If -1, no limit is applied. Defaults to -1.

-1
minimal_sphere_radius float

Minimum radius allowed for spheres when placing vertices. Defaults to 5.

5.0
edge_sphere_threshold float

Threshold value for determining the minimum allowable distance between an edge and non-endpoint spheres. Higher value is more restrictive, with 1 allowing no overlap whatsoever. Defaults to 1.0.

1.0
return_sdf bool

If True, the signed distance field array is returned as well. Defaults to False.

False

Returns:

Name Type Description
tuple Union[Tuple[List[Tuple[int, ...]], List[Tuple[Tuple[int, ...], ...]], ndarray], Tuple[List[Tuple[int, ...]], List[Tuple[Tuple[int, ...], ...]]]]

A tuple containing a list of sphere centers as coordinate tuples, a list of edges as pairs of vertex coordinates, and a signed distance field array if return_sdf is True.

Source code in src/sn_graph/core.py
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
def create_sn_graph(
    image: np.ndarray,
    max_num_vertices: int = -1,
    edge_threshold: float = 1.0,
    max_edge_length: int = -1,
    minimal_sphere_radius: float = 5.0,
    edge_sphere_threshold: float = 1.0,
    return_sdf: bool = False,
) -> Union[
    Tuple[List[Tuple[int, ...]], List[Tuple[Tuple[int, ...], ...]], np.ndarray],
    Tuple[List[Tuple[int, ...]], List[Tuple[Tuple[int, ...], ...]]],
]:
    """Create a graph from an image/volume using the Sphere-Node (SN) graph skeletonisation algorithm.

    This function converts a grayscale (or binary) image/volume into a graph representation by first computing its signed distance field (assuming boundary contour has value 0), then placing sphere
    centers as vertices and creating edges between neighboring spheres based on specified criteria.

    Args:
        image (np.ndarray): Input image/volume where the foreground is positive and the background is 0.
            Can be a numpy array of arbitrary dimension.
        max_num_vertices (int, optional): Maximum number of vertices (sphere centers) to generate.
            If -1, no limit is applied. Defaults to -1.
        edge_threshold (float, optional): Threshold value for determining the minimal portion of an edge that must lie within the object. Higher value is more restrictive, with 1 requiring edge to be fully contained in the object. Defaults to 1.0.
        max_edge_length (int, optional): Maximum allowed length for edges between vertices.
            If -1, no limit is applied. Defaults to -1.
        minimal_sphere_radius (float, optional): Minimum radius allowed for spheres when placing vertices.
            Defaults to 5.
        edge_sphere_threshold (float, optional): Threshold value for determining the minimum allowable distance between an edge and non-endpoint spheres. Higher value is more restrictive, with 1 allowing no overlap whatsoever. Defaults to 1.0.
        return_sdf (bool, optional): If True, the signed distance field array is returned as well.
            Defaults to False.

    Returns:
        tuple: A tuple containing a list of sphere centers as coordinate tuples, a list of edges as pairs of vertex coordinates, and a signed distance field array if return_sdf is True.
    """
    (
        image,
        max_num_vertices,
        edge_threshold,
        max_edge_length,
        minimal_sphere_radius,
        edge_sphere_threshold,
        return_sdf,
    ) = _validate_args(
        image,
        max_num_vertices,
        edge_threshold,
        max_edge_length,
        minimal_sphere_radius,
        edge_sphere_threshold,
        return_sdf,
    )

    # Pad the image with 0's to avoid edge effects in the signed distance field computation
    padded_image = np.pad(image, 1)
    padded_sdf_array = skfmm.distance(padded_image, dx=1, periodic=False)
    # Remove padding
    slice_tuple = tuple(slice(1, -1) for _ in range(image.ndim))
    sdf_array = padded_sdf_array[slice_tuple]

    spheres_centres = choose_sphere_centres(
        sdf_array, max_num_vertices, minimal_sphere_radius
    )

    edges = determine_edges(
        spheres_centres,
        sdf_array,
        max_edge_length,
        edge_threshold,
        edge_sphere_threshold,
    )

    spheres_centres, edges = _standardize_output(spheres_centres, edges)

    if return_sdf:
        return spheres_centres, edges, sdf_array
    return spheres_centres, edges

draw_sn_graph(spheres_centres, edges, sdf_array=None, background_image=None)

Draw a graph of spheres and edges on an image/volume (or on a blank background). The function creates a monochromatic volume of dimension equal to the dimension of the graph. Value 0 is given to the background, value 1 to the background image/volume, value 2 to the edges, and value 4 to the spheres.

Parameters:

Name Type Description Default
spheres_centres list

list of tuples, each tuple contains coordinates of a sphere's centre.

required
edges list

list of tuples of tuples, each tuple contains coordinates of the two endpoints of an edge.

required
sdf_array optional(ndarray)

the signed distance field array, if not provided no spheres will be drawn.

None
background_image optional(ndarray)

the image/volume on which to draw the graph. If not provided and if sdf_array is not provided either, a blank background will be created with the size inferred from the coordinates of the graph vertices.

None

Returns:

Type Description
ndarray

the image/volume (or a blank background) with the graph drawn on it.

Raises:

Type Description
ValueError

if the shape of sdf_array is not equal to the shape of background_image.

Source code in src/sn_graph/visualisation.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
def draw_sn_graph(
    spheres_centres: list,
    edges: list,
    sdf_array: Optional[np.ndarray] = None,
    background_image: Optional[np.ndarray] = None,
) -> np.ndarray:
    """
    Draw a graph of spheres and edges on an image/volume (or on a blank background). The function creates a monochromatic volume of dimension equal to the dimension of the graph.
    Value 0 is given to the background, value 1 to the background image/volume, value 2 to the edges, and value 4 to the spheres.

    Args:
        spheres_centres (list): list of tuples, each tuple contains coordinates of a sphere's centre.
        edges (list): list of tuples of tuples, each tuple contains coordinates of the two endpoints of an edge.
        sdf_array (optional(np.ndarray) ): the signed distance field array, if not provided no spheres will be drawn.
        background_image (optional(np.ndarray) ): the image/volume on which to draw the graph. If not provided and if sdf_array is not provided either, a blank background will be created with the size inferred from the coordinates of the graph vertices.

    Returns:
        (np.ndarray): the image/volume (or a blank background) with the graph drawn on it.

    Raises:
        ValueError: if the shape of `sdf_array` is not equal to the shape of `background_image`.
    """

    # Check dimensions consistency
    if sdf_array is not None and background_image is not None:
        if sdf_array.shape != background_image.shape:
            raise ValueError(
                f"Dimension mismatch: sdf_array shape {sdf_array.shape} doesn't match background_image shape {background_image.shape}"
            )

    if background_image is not None:
        img = background_image.copy()
    elif sdf_array is not None:
        img = np.zeros(sdf_array.shape)
    else:
        if not spheres_centres:
            # If no spheres and no background image, return an empty array
            return np.array([])
        else:
            # Create a blank image with shape based on the maximum coordinates of spheres, with an offset of 10 to give some room to breathe
            shape = np.max(np.array(spheres_centres) + 10, axis=0)
            img = np.zeros(shape)

    # Draw edges
    for edge in edges:
        start = np.array(edge[0])
        end = np.array(edge[1])
        pixels = line_nd(start, end)

        img[pixels] = 2

    # If no sdf_array is provided, return the image with edges only
    if sdf_array is None:
        return img

    # Draw spheres
    for center in spheres_centres:
        radius = sdf_array[center]
        center_array = np.array(center)
        sphere_coords = generate_sphere_surface(center_array, radius, sdf_array.shape)

        img[sphere_coords] = 4

    return img

visualize_3d_graph(spheres_centres, edges, sdf_array=None)

Visualize a 3D graph with vertices, edges, and spheres by creating a trimesh scene object.

Parameters:

Name Type Description Default
spheres_centres list

list of coordinate tuples [(x1,y1,z1), (x2,y2,z2), ...]

required
edges list

list of tuples of coordinates for start and end of edges [((x1,y1,z1), (x2,y2,z2)), ...]

required
sdf_array optional(ndarray)

array that can be queried at vertex coordinates to get radius, if not provided, no spheres will be drawn

None

Returns:

Name Type Description
scene Scene

A 3D scene containing the graph visualization.

Source code in src/sn_graph/visualisation.py
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
def visualize_3d_graph(
    spheres_centres: list, edges: list, sdf_array: Optional[np.ndarray] = None
) -> trimesh.scene.scene.Scene:
    """
    Visualize a 3D graph with vertices, edges, and spheres by creating a trimesh scene object.

    Args:
        spheres_centres (list): list of coordinate tuples [(x1,y1,z1), (x2,y2,z2), ...]
        edges (list ): list of tuples of coordinates for start and end of edges [((x1,y1,z1), (x2,y2,z2)), ...]
        sdf_array (optional(np.ndarray) ): array that can be queried at vertex coordinates to get radius, if not provided, no spheres will be drawn

    Returns:
        scene (trimesh.Scene): A 3D scene containing the graph visualization.
    """

    if spheres_centres and (len(spheres_centres[0]) != 3):
        raise ValueError("Expected 3D coordinates (x,y,z) for vertices.")

    if edges and (len(edges[0][0]) != 3 or len(edges[0][1]) != 3):
        raise ValueError("Expected 3D coordinates (x,y,z) for edge endpoints.")

    if sdf_array is not None and sdf_array.ndim != 3:
        raise ValueError(
            f"SDF array must be 3-dimensional. Found {sdf_array.ndim} dimensions."
        )

    # Create a scene
    scene = trimesh.Scene()

    if sdf_array is not None:
        # Add spheres and vertex points for each vertex
        for v in spheres_centres:
            # Get radius from SDF array
            radius = sdf_array[tuple(v)]

            # Create a smooth sphere based on SDF value
            sphere = trimesh.creation.icosphere(radius=radius, subdivisions=2)
            sphere.visual.vertex_colors = [255, 0, 0, 150]  # Red
            sphere.apply_translation(v)
            scene.add_geometry(sphere)

            # Add small vertex points
            point = trimesh.creation.icosphere(radius=0.1)
            point.visual.face_colors = [0, 0, 255, 255]
            point.apply_translation(v)
            scene.add_geometry(point)

    # Add edges directly as line segments - blue and thick
    for start_coord, end_coord in edges:
        # Create a line segment between start and end
        line = trimesh.creation.cylinder(
            radius=0.05,  # Thick lines
            segment=[start_coord, end_coord],
        )
        line.visual.vertex_colors = [0, 0, 255, 255]  # Blue
        scene.add_geometry(line)

    return scene