Mesh failing using python API.

Provides a system for patient-specific cardiovascular modeling and simulation.
POST REPLY
User avatar
David Molony
Posts: 2
Joined: Sun Apr 11, 2021 10:47 am

Mesh failing using python API.

Post by David Molony » Thu Jan 19, 2023 3:38 pm

I am trying to reconstruct vessels using the python API from ctgr files I automatically generate. I encounter 2 issues that I would appreciate any help in identifying what I am doing wrong or missing. I am attaching the ctgr and pth files for reference also. My python code to generate the model and mesh is also below.

1. With the python API while I can generate the model I cannot successfully mesh the model. I get an error about self-intersecting facets (see full print out below). However, I can import the same ctgr files into the simvascular GUI and generate a model there that I can successfully mesh. I'm wondering what my code is missing to replicate the GUI process?

2. Secondly, when I generate the model (Create Model button in GUI) in simvascular it recognizes each individual ctgr group as a wall and gives unique outlet ids. In my python code when I create the model there is only one wall and all the outlets share the same id. How can I replicate the GUI behaviour using the python API?

PYTHON CODE

Code: Select all

import vtk
import os
import sv

def get_profile_contour(contours, cid, npts):
  cont = contours[cid]
  cont_pd = cont.get_polydata()
  cont_ipd = sv.geometry.interpolate_closed_curve(polydata=cont_pd, number_of_points=npts)
  return cont_ipd

def loft_contours_polydata(contours):
  """Lofts contours for use in polydata reconstruction"""
  num_samples = 50
  use_distance = True
  aligned_contours = []
  for index in range(0, len(contours)):
    cont = get_profile_contour(contours, index, num_samples)

    if index == 0:
      cont_align = cont
    else:
      cont_align =  sv.geometry.align_profile(last_cont_align, cont, use_distance)

    last_cont_align = cont_align
    aligned_contours.append(cont_align)

  options = sv.geometry.LoftOptions()
  options.interpolate_spline_points = True
  options.num_spline_points = 400 # contorls number of elements along vessel length
  options.num_long_points = 200 # controls how well matches initial geometry (more curved vessels requires greater values)

  lofted_surface = sv.geometry.loft(polydata_list=aligned_contours, loft_options=options)  

  # Create a new solid from the lofted solid.
  lofted_model = sv.modeling.PolyData()
  lofted_model.set_surface(surface=lofted_surface)
  return lofted_model


def merge(main_contours, branch_contours, recon_method):
  """Merges branches to main vessel"""
  if recon_method == 'vtp':
    lofted_contours = loft_contours_polydata(main_contours)
    lofted_capped = sv.vmtk.cap(surface=lofted_contours.get_polydata(),
                          use_center=False)
    lofted_model = sv.modeling.PolyData()
    lofted_model.set_surface(lofted_capped) 

  for branch_contour in branch_contours:

    # merge vessels polydata
    if recon_method == 'vtp':
      modeler = sv.modeling.Modeler(sv.modeling.Kernel.POLYDATA)
      lofted_contours = loft_contours_polydata(branch_contour)
      lofted_capped = sv.vmtk.cap(surface=lofted_contours.get_polydata(),
                          use_center=False)

      lofted_model1 = sv.modeling.PolyData()
      lofted_model1.set_surface(lofted_capped)

      lofted_model = modeler.union(model1=lofted_model, model2=lofted_model1)
       
  return lofted_model

def read_contours(segmentation_path):
  '''Read an SV contour group file.
  '''
  contour_group = sv.segmentation.Series(segmentation_path)
  num_conts = contour_group.get_num_segmentations()
  contours = []

  for i in range(num_conts):
    cont = contour_group.get_segmentation(i)
    contours.append(cont)
  return contours

def create_mesh(model):
  mesher = sv.meshing.TetGen()

  caps = model.identify_caps()
  wall_id = [i + 1 for i in range(len(caps)) if caps[i]]

  print("Wall ids are {}".format(wall_id))

  # Set the model for the mesher.
  mesher.set_model(model)
  mesher.set_walls(wall_id)
  face_ids = mesher.get_model_face_ids()
  print(face_ids)


  edge_size = 1.5
  print("Set meshing options ... ")
  options = sv.meshing.TetGenOptions(global_edge_size=edge_size, surface_mesh_flag=True, volume_mesh_flag=True)
  options.radius_meshing_on = False
  options.boundary_layer_inside = True
  options.radius_meshing_scale = 0.4 


  # Generate the mesh. 
  mesher.generate_mesh(options)

  # Get the mesh as a vtkUnstructuredGrid. 
  mesh = mesher.get_mesh()
  print("Mesh:")
  print("  Number of nodes: {0:d}".format(mesh.GetNumberOfPoints()))
  print("  Number of elements: {0:d}".format(mesh.GetNumberOfCells()))

  # Write the mesh.
  mesh_series = sv.meshing.Series()
  mesh_series.write('mesh.msh')
  mesh_path = 'mesh.vtu'
  mesher.write_mesh(mesh_path)

if __name__ == "__main__":
    os.chdir('Segmentations')
    main_contours = read_contours('CX.ctgr')
    branch_contours = []
    branch_contours.append(read_contours('OM1_scaled.ctgr'))
    branch_contours.append(read_contours('OM2_scaled.ctgr'))
    recon_method= 'vtp'
    model = merge(main_contours, branch_contours, recon_method)
    create_mesh(model)
    
TEGTEN ERROR

Checking surface mesh
Regions: 1
Number of Free Edges on Surface: 209
Number of Non-Manifold Edges on Surface: 0
Iteration 1/10
Iteration 2/10
Iteration 3/10
Iteration 4/10
Iteration 5/10
Iteration 6/10
Iteration 7/10
Iteration 8/10
Iteration 9/10
Iteration 10/10
Final mesh improvement
Converting to TetGen...
Adding Facet Markers...
TetGen Meshing Started...
Delaunizing vertices...
Delaunay seconds: 0.027234
Creating surface mesh ...
Found two nearly self-intersecting facets.
1st: [41, 39, 40] #2
2nd: [41, 39, 6052] #2
The dihedral angle between them is 0.0150374 degree.
Hint: You may use -p/# to decrease the dihedral angle tolerance 0.1 (degree).
ERROR: TetGen quit and returned error code 3
Attachments
test.zip
(326.8 KiB) Downloaded 4 times

User avatar
David Parker
Posts: 1629
Joined: Tue Aug 23, 2005 2:43 pm

Re: Mesh failing using python API.

Post by David Parker » Mon Jan 23, 2023 10:05 pm

Hello,

I've run your Python script and am now trying to figure out what the SV GUI version is doing. One thing I found out is that the GUI version is lofting using sv.geometry.loft_nurbs. However, when I use this function to loft the union of two vessels is instead returning the intersection of the two vessels, seems like the loft has surface normals flipped or something.

Stay tuned!

Cheers,
Dave

User avatar
David Parker
Posts: 1629
Joined: Tue Aug 23, 2005 2:43 pm

Re: Mesh failing using python API.

Post by David Parker » Wed Jan 25, 2023 9:25 pm

Hello,

I added a sv.modeling.PolyData().create_vessel_model method to the Python API that creates a solid model from a list of segmentations. I created a model from your data and was able to generate a mesh from it.

Note that I was able to create a solid model from the Demo data on SimTK and mesh it using a script similar to the one you use. I am thinking that the problem you are having with meshing is caused by the segmentations you have created. You could try creating smoother segmentations. You should also loft with the segmentation points and not control points (see https://github.com/SimVascular/SimVascu ... vessels.py).

It will be a week or so until I can integrate the new method into SV (see https://github.com/SimVascular/SimVascular/issues/1003).

Cheers,
Dave

User avatar
David Molony
Posts: 2
Joined: Sun Apr 11, 2021 10:47 am

Re: Mesh failing using python API.

Post by David Molony » Fri Jan 27, 2023 7:54 am

Thank you Dave for your help and exposing the new python method. I will use this going forward though I think I figured out the problem with my code also. I should have had a "not" when identifying the wall_id.

Code: Select all

  caps = model.identify_caps()
  wall_id = [i + 1 for i in range(len(caps)) if not caps[i]]
Thanks for sharing the example for using segmentation points also. I had noticed that the model did not seem to fully represent the contours.

User avatar
Florian Gartner
Posts: 12
Joined: Tue Dec 12, 2023 7:13 am

Re: Mesh failing using python API.

Post by Florian Gartner » Fri Jun 07, 2024 5:50 am

Hello,
I have the same problem as described here.

I'm trying to create a model from a contour group using the Python API. I tried with the 'sv.modeling.PolyData().create_vessel_model' function as well as with David's script above. In both cases, I can create and add a model to the GUI that looks nice and pretty.

However, when I try to mesh the model either in the GUI or by using a Python script, I get the error "failed in generating mesh". To test things out, I ran the create-vessel-model.py test script on GitHub ( https://github.com/SimVascular/SimVasc ... -model.py ) in SimVascular's Python console.

I get the same behavior and error when trying to create the mesh. Below is the exact script that I used (I just provide a direct path for script_path to be able to run it in the Simvascular console and I added a line "sv.dmg.add_model(name = 'test_model', model = model)" to add the model also to the GUI). I use SimVascular 23.03.27 on Windows 11.
Can you tell me what needs to be done in order to be able to mesh a model created with the Python API? Thanks a lot in advance!

Code adapted from 'create-vessel-model.py' on GitHub:

Code: Select all

'''Test creating a solid model of the contours from two vessels. 
'''
import os
from pathlib import Path
import sv
import sys
import vtk

## Set some directory paths. 
script_path = Path('C:/Users/flori/OneDrive/Desktop/Simvascular_Simulations/new-api-tests/modeling')
parent_path = script_path.parent
data_path = parent_path / 'data'

try:
    sys.path.insert(1, str(parent_path / 'graphics'))
    import graphics as gr
except:
    print("Can't find the new-api-tests/graphics package.")

def create_mesh(model):
  print("========== create mesh ==========")
  mesher = sv.meshing.TetGen()

  # Set the model for the mesher.
  mesher.set_model(model)
  face_ids = mesher.get_model_face_ids()
  print("Face IDs: {}".format(face_ids))

  # Set the wall IDs.
  caps = model.identify_caps()
  print("Cap ids: {}".format(caps))
  wall_ids = [face_ids[i] for i in range(len(caps)) if not caps[i]]
  print("Wall ids are {}".format(wall_ids))
  mesher.set_walls(wall_ids)

  edge_size = 0.9259
  print("Set meshing options ... ")
  options = sv.meshing.TetGenOptions(global_edge_size=edge_size, surface_mesh_flag=True, volume_mesh_flag=True)
  #options.radius_meshing_on = False
  #options.boundary_layer_inside = True
  #options.radius_meshing_scale = 0.4 

  # Generate the mesh. 
  mesher.generate_mesh(options)

  # Get the mesh as a vtkUnstructuredGrid. 
  mesh = mesher.get_mesh()
  print("Mesh:")
  print("  Number of nodes: {0:d}".format(mesh.GetNumberOfPoints()))
  print("  Number of elements: {0:d}".format(mesh.GetNumberOfCells()))
  return mesher.get_surface()

def read_contours(name):
    '''Read an SV contour group file.
    '''
    file_name = str(data_path / 'DemoProject' / 'Segmentations' / str(name+'.ctgr'))
    contour_group = sv.segmentation.Series(file_name)
    num_conts = contour_group.get_num_segmentations()
    contours = []

    for i in range(num_conts):
        cont = contour_group.get_segmentation(i)
        contours.append(cont)

    return contours

def get_contours_points(contours):

    points_list = []

    for cont in contours:
        cont_pts = cont.get_points()
        num_pts = len(cont_pts)

        points = vtk.vtkPoints()
        points.SetNumberOfPoints(num_pts)
        lines = vtk.vtkCellArray()

        for i in range(num_pts):
            pt = cont_pts[i]
            points.SetPoint(i, pt[0], pt[1], pt[2])
            if i > 0 and i < num_pts:
                cell = [i-1,i]
                lines.InsertNextCell(2,cell)

        cell = [num_pts-1,0]
        lines.InsertNextCell(2,cell)
        lines.InsertCellPoint(0)

        cont_pd = vtk.vtkPolyData()
        cont_pd.SetPoints(points)
        cont_pd.SetLines(lines)

        points_list.append(cont_pd)

    return points_list 

# Create a graphics window.
win_width = 1000
win_height = 1000
renderer, renderer_window = gr.init_graphics(win_width, win_height)

aorta_contours = read_contours('aorta')
aorta_points_list = get_contours_points(aorta_contours)

iliac_contours = read_contours('right_iliac')
iliac_points_list = get_contours_points(iliac_contours)
points_list = [aorta_points_list, iliac_points_list]

loft_opts = sv.LoftOptions()
loft_opts.linear_multiplier = 10
loft_opts.method = "nurbs"
loft_opts.num_out_pts_in_segs = 60
loft_opts.sample_per_segment = 12
loft_opts.u_knot_span_type = "average"
loft_opts.v_knot_span_type = "average"
loft_opts.u_parametric_span_type = "chord"
loft_opts.v_parametric_span_type = "chord"


model = sv.modeling.PolyData()
model.create_vessel_model(contour_list=points_list, num_sampling_pts=60, loft_options=loft_opts)
vessel_model_pd = model.get_polydata()
gr.add_geometry(renderer, vessel_model_pd, color=[1,0,0], edges=True)

model.write(file_name="vessel-model", format="vtp")

sv.dmg.add_model(name = 'test_model', model = model)

# Mesh the model.
#mesh = create_mesh(model)
#gr.add_geometry(renderer, mesh, color=[1,0,0], edges=True)

gr.display(renderer_window)
Attachments
SimVas_aorta_model.png.png
model generated by the test script in the GUI
SimVas_aorta_model.png.png (197.31 KiB) Viewed 188 times
meshing_error.png
mesh error when running the mesher in GUI
meshing_error.png (227.02 KiB) Viewed 188 times

User avatar
David Parker
Posts: 1629
Joined: Tue Aug 23, 2005 2:43 pm

Re: Mesh failing using python API.

Post by David Parker » Tue Jun 18, 2024 1:11 pm

Hello,

You need to identify each model face Type as cap or wall. The flat inlet and outlet faces are caps, the rest are walls.

Cheers,
Dave

User avatar
Florian Gartner
Posts: 12
Joined: Tue Dec 12, 2023 7:13 am

Re: Mesh failing using python API.

Post by Florian Gartner » Tue Jun 18, 2024 5:20 pm

Hi Dave,

how exactly do I need to do this? Do the following lines in the function 'create_mesh()' not do this?:

Code: Select all

  caps = model.identify_caps()
  print("Cap ids: {}".format(caps))
  wall_ids = [face_ids[i] for i in range(len(caps)) if not caps[i]]
  print("Wall ids are {}".format(wall_ids))
  mesher.set_walls(wall_ids)

I don't find any method within 'model' that could set the type of the faces, only the 'set_walls()' routine in 'mesher'.
Also I was expecting, since the GUI automatically identifies and sets the type of the faces when creating a model, that 'create_vessel_model()' would do it alike and imitate the GUI behavior. Is there a reason why this is not done automatically by 'create_vessel_model()'.

Thanks and cheers,
Flo

User avatar
David Parker
Posts: 1629
Joined: Tue Aug 23, 2005 2:43 pm

Re: Mesh failing using python API.

Post by David Parker » Tue Jun 18, 2024 6:07 pm

Hi Flo,

Looking at the picture you attached of the SV Modeling Tool I don't see that the model faces have been set as cap/wall.

Note that the Python API is not integrated with the SV GUI so if you add a model to SV the GUI does not update any parameters like cap/wall types. If these are not set then I think that SV will not create the ModelFaceID data needed by the mesh generator.

Most users don't execute Python scripts in the SV GUI because it has limited use.

Cheers,
Dave

POST REPLY