navis-org / skeletor

Extraction of 3D skeletons from meshes.
https://navis-org.github.io/skeletor/
GNU General Public License v3.0
205 stars 26 forks source link

Vascular structure with different radii #45

Open koenterheegde0507 opened 5 months ago

koenterheegde0507 commented 5 months ago

Hey,

I'm testing out your algorithms on a vascular structure with an aneurysm. All methods I have tried out right now seem to work quite well on narrow vascular structures, but when the radius becomes bigger it seems to go wrong. See this: image

Might this be due to a not correct mesh? I do use fix_mesh and contract. When using a smaller value than 0.1 for epsilon in the contract function, most of the times the contraction stops prematurely.

Complete code:

import numpy as np
import pyvista as pv
import tkinter as tk 
from tkinter import filedialog
import os
import skeletor as sk
import trimesh as tm
import open3d as o3d
import networkx as nx

root = tk.Tk()
root.withdraw()
root.title('Pick patient data')
file_path = filedialog.askdirectory()

aorta_mesh = tm.load(f"{file_path}\Segment_1.stl")
aorta_mesh_simplified = aorta_mesh.simplify_quadric_decimation(len(aorta_mesh.faces)/10)
fixed2 = sk.pre.fix_mesh(aorta_mesh_simplified, fix_normals=True, remove_disconnected=5, inplace=True)
contracted_mesh = sk.pre.contract(fixed2,epsilon=0.1)

skel = sk.skeletonize.by_wavefront(contracted_mesh, waves=50, step_size=1)
#contracted_mesh = sk.pre.contract(aorta_mesh,epsilon=0.2)
skel2 = sk.skeletonize.by_teasar(contracted_mesh,inv_dist=10)

skel3 = sk.skeletonize.by_tangent_ball(contracted_mesh)
skel4 = sk.skeletonize.by_vertex_clusters(contracted_mesh,sampling_dist=1,cluster_pos='median')

big_plotter = pv.Plotter(shape=(2,2))
big_plotter.subplot(0,0)
big_plotter.add_points(np.array(skel.vertices),color='black')
big_plotter.add_mesh(aorta_mesh_simplified,opacity=0.5)
big_plotter.add_text('Wavefront',position='upper_right',font_size=10)

big_plotter.subplot(0,1)
big_plotter.add_points(np.array(skel2.vertices),color='black')
big_plotter.add_mesh(aorta_mesh_simplified,opacity=0.5)
big_plotter.add_text('TEASAR',position='upper_right',font_size=10)

big_plotter.subplot(1,0)
big_plotter.add_points(np.array(skel3.vertices),color='black')
big_plotter.add_mesh(aorta_mesh_simplified,opacity=0.5)
big_plotter.add_text('Tangent Ball',position='upper_right',font_size=10)

big_plotter.subplot(1,1)
big_plotter.add_points(np.array(skel4.vertices),color='black')
big_plotter.add_mesh(aorta_mesh_simplified,opacity=0.5)
big_plotter.add_text('Vertex Cluster',position='upper_right',font_size=10)

big_plotter.show()
schlegelp commented 5 months ago

Can you share the mesh from your examples please?

koenterheegde0507 commented 5 months ago

Sharing a .STL is not supported :(

schlegelp commented 5 months ago

Try zipping before upload.

koenterheegde0507 commented 5 months ago

Could I send it to you personally? Its personal data so would rather not share it in here

schlegelp commented 5 months ago

Yes, my email is in the setup.py.

koenterheegde0507 commented 5 months ago

I've sent the email with the STL in it

schlegelp commented 5 months ago

I'd use the wavefront method without prior contraction here:

import trimesh as tm 
import skeletor as sk 

m = tm.load('Segment_1.stl')
s = sk.skeletonize.by_wavefront(m)
Screenshot 2024-03-30 at 14 41 24

This screenshot shows the actual skeleton which doesn't look half bad. You might have noticed though that the bulbous region the skeleton seems to have a decent midline but also various spiky "offshoots". On closer inspection, these consist of single nodes - which in my experience are often due to imperfections in the mesh.

I used Blender to quickly re-mesh the mesh (via the modifier of the same name) and then also ran a function to remove these bristles:

m2 = tm.load('Segment_1_remesh.stl')
s2 = sk.skeletonize.by_wavefront(m2)
s2_clean = sk.post.postprocessing.remove_hairs(s2, los_only=False)
Screenshot 2024-03-30 at 16 19 52

As you can see it already looks much better. In general I find it's often better to think about how to clean up the initial skeleton than trying to optimise the skeletonisation.

As a final hint: the wavefront method is somewhat sensitive to where the wave starts. Ideally it would start at the tip of one of the vasculatures but by default a semi-random vertex on the mesh is chosen. You can define the start point using the using the origins parameter:

# Here, I randomly picked the 50,000th vertex as starting point but that already made the skeleton better off the bat
s2 = sk.skeletonize.by_wavefront(m2, waves=1, origins=[50000])
schlegelp commented 5 months ago

Oh forgot to say: I had to make a minor tweak to the remove_hairs function. To run the code above, you will have to re-install skeletor from Github.

koenterheegde0507 commented 5 months ago

When will the new function be available using pip install instead of cloning the repository?

schlegelp commented 5 months ago

I released skeletor 1.3.0 this morning. Note though that the remove_hairs function was renamed to remove_bristles.

koenterheegde0507 commented 5 months ago

I've used pip uninstall skeletor and the reinstalled it, but the functionality doesn't seem to be there yet?

koenterheegde0507 commented 5 months ago

This only seems to work on the wavefront method, I still get strange skeletons for the other methods when using the remove_bristles function. However, I do not remesh it as I don't want to use external software.

schlegelp commented 5 months ago

Can you share examples (code + image) for these "strange" skeletons?

schlegelp commented 5 months ago

For what it's worth: I had a bit of a play with your mesh myself and I'd say the wavefront method is your best bet.

TEASAR is problematic when the object has both small and large radii which is the case for your mesh: too small inv_dist and you get extraneous branches on the bulbous parts, too large inv_dist and you loose details in the fine branches. Plus the skeleton is on the mesh surface - not sure if you need midline traces?

Vertex cluster and edge collapse require a contracted mesh which for some reason doesn't seem to work well with your mesh.

Tangent ball doesn't do well on the bulbous part - looks like it may be thrown off by the small vessel attached to the surface.

schlegelp commented 5 months ago

I've used pip uninstall skeletor and the reinstalled it, but the functionality doesn't seem to be there yet?

Sorry, I just realised that the Github Actions workflow that was supposed to push version 1.3.0 to PyPI had failed. The new version is now definitely on PyPI.