Maria
Maria

Reputation: 11

Comparing and Exporting Initial SVG Paths with Skeletonized Paths Using Trimesh

I am working on a project where I need to compare an initial SVG with its skeleton and decided to work with Trimesh. The goal is to create an output image that combines the initial SVG with skeletonized paths: for the parts where the skeleton is close to the initial on a certain threshold - keep only skeletonized edges, while for the parts where the distance is over the threshold - keep only the initials.

Initial steps were easy

import trimesh

initial_edges = trimesh.load('vectorized_shapes.svg')
skeletonized_edges = initial_edges.medial_axis() 

However, I encountered challenges when attempting to compare individual vertices between the initial and skeletonized paths. The approach I tried didn't work well, as vertices are not entities, and I couldn't find a suitable way to compare initial entities with the skeletonized ones.

for i, initial_vertex in enumerate(initial_edges.vertices):
    min_distance = float("inf")
    # Find the closest skeleton vertex for this initial vertex
    for j, skeleton_vertex in enumerate(skeletonized_edges.vertices):
        distance_current = np.linalg.norm(initial_vertex - skeleton_vertex)
        # If the vertex is closer to inital than any one before - 
        if distance_current < min_distance:
            min_distance = distance_current
            closest_skeleton = skeleton_vertex
            skeleton_index = j
    # If skeleton is closer to initial than threshold - keep skeleton
    if min_distance <= threshold:
        vertex = closest_skeleton
    # If skeleton is not closer to initial than threshold - keep initial
    else:
        vertex = initial_vertex
        
    final_vertices.append(vertex)

figure 1

It didn't work very well, since vertices are not entities and I couldn't also find a way to compare initial entities with the skeletonized ones.

Additionally, when I attempted to export the skeletonized edges using the following code:

initial_edges.export(file_obj="svg.svg"))

The output in Jupyter and the exported file differed significantly.

figure 2

My questions are:

  1. Is it possible to effectively compare initial paths with skeletonized paths in Trimesh?
  2. If not, are there alternative libraries or methods that can be recommended for this task?
  3. What parameters should be passed to the export function to ensure the output image matches the display in Jupyter using the .show() method?

Any insights or suggestions would be greatly appreciated!

Upvotes: 1

Views: 89

Answers (1)

Maria
Maria

Reputation: 11

Managed to save it as svg from matplotlib the following way:

def save_skeleton_as_svg(skeletonized_edges,
                         min_line_skeleton_threshold: int = 10, 
                         skeleton_smooth_tolerance: int = 5,
                         skeletonized_svg_name = "skeletonized.svg") -> None: # TODO: skeletonized_svg_name should be dynamic
    """
        It's a pretty ugly approach since trimesh has out-of-the-box export and save functions,
        but it doesn't play with parameters and representation thus it's much more precise

        Parameters:
        - skeletonized_edges: trimesh Path2D from skeletonize_svg function
        - min_line_skeleton_threshold: Lines smaller than this will be eliminated at the final svg
        - skeleton_smooth_tolerance: How smooth the final image should be.
        The lower is the number - the smoother is the result
        - skeletonized_svg_name: Save skeleton to the file named skeletonized_svg_name

    """
    # Assuming skeletonized_edges is an instance of Path2D
    fig, ax = plt.subplots()

    # Turn off axis ticks and labels from matplotlib
    ax.set_xticks([])
    ax.set_yticks([])
    ax.set_xticklabels([])
    ax.set_yticklabels([])

    # Remove the box around the plot
    ax.spines['top'].set_visible(False)
    ax.spines['right'].set_visible(False)
    ax.spines['bottom'].set_visible(False)
    ax.spines['left'].set_visible(False)

    # Entity is a trimesh entity, can be line, curve, circle etc
    for entity in skeletonized_edges.entities:
        # Get the discrete representation of the entity
        discrete = entity.discrete(skeletonized_edges.vertices)

        # Check if the entity length is longer than the threshold
        if len(discrete) > min_line_skeleton_threshold:
            # Smooth the curve using the Ramer-Douglas-Peucker algorithm
            line = LineString(discrete)
            # Setting preserve_topology=False simplifies the geometry without
            # enforcing strict topological preservation, which can result in
            # a smoother and more straightforward simplification of the path.
            simplified_line = line.simplify(tolerance=skeleton_smooth_tolerance,
                                            preserve_topology=False)

            # Convert the simplified LineString back to discrete representation
            simplified_discrete = list(simplified_line.coords)

            # if the entity has its own plot method use it
            if hasattr(entity, 'plot'):
                entity.plot(skeletonized_edges.vertices, ax=ax)
                continue

            # otherwise plot the simplified discrete curve
            ax.plot(*zip(*simplified_discrete))

    # Save the plot in SVG format
    plt.savefig(skeletonized_svg_name, format='svg')

Will continue comparing skeleton with initial image without trimesh

Upvotes: 0

Related Questions