import numpy as np
import pyvista as pv
import vtk
import sys,os
from .implicit.implicit import surface
from .branch_addition.check import *
from .branch_addition.close import *
from .branch_addition.basis import *
from .branch_addition.calculate_length import *
from .branch_addition.calculate_radii import *
from .branch_addition.set_root import *
from .branch_addition.add_edge import *
from .branch_addition.add_bifurcation import *
from .branch_addition.add_branches_v3 import *
from .forest_utils.connect import *
from .forest_utils.smooth import *
from .forest_utils.compete_add import *
from .sv_interface.get_sv_data import *
from .sv_interface.options import *
from .sv_interface.build_files import *
from .sv_interface.waveform import generate_physiologic_wave, wave
from .sv_interface.build_results import make_results
from .sv_interface.view_0d_result_plots import view_plots
from .sv_interface.build_0d_run_script import run_0d_script
from .sv_interface.locate import locate_0d_solver, locate_1d_solver
from .sv_interface.export_3d_only import export_3d_only
from .collision.collision import *
from .utils.gcode.gcode import *
from copy import deepcopy
from itertools import combinations
from tqdm import tqdm
import pickle
import tetgen
import json
from scipy.interpolate import splev, splprep
import matplotlib.pyplot as plt
import platform
import pymeshfix
from .sv_interface import ROM
from .utils.remeshing.remesh import remesh_surface
#from wrapt_timeout_decorator import *
import tkinter as tk
from tkinter.filedialog import askopenfilename
[docs]def get():
root = tk.Tk()
root.withdraw()
filename = askopenfilename()
root.update()
return filename
[docs]class tree:
"""
Data structure:\n
index: 0:2 -> proximal node coordinates \n
index: 3:5 -> distal node coordinates \n
index: 6:8 -> unit basis U (all) \n
index: 9:11 -> unit basis V (all) \n
index: 12:14 -> unit basis W (axial direction) (all) \n
index: 15,16 -> children (-1 means no child) \n
index: 17 -> parent (NA) \n
index: 18 -> proximal node index (only real edges) \n
index: 19 -> distal node index (only real edges) \n
index: 20 -> length (path length) \n
index: 21 -> radius (what are the units?) \n
index: 22 -> flow (NA) \n
index: 23 -> left bifurcation (NA) \n
index: 24 -> right bifurcation (NA) \n
index: 25 -> reduced resistance (NA) \n
index: 26 -> depth (NA) \n
index: 27 -> reduced downstream length (NA) \n
index: 28 -> root radius scaling factor (same as edge for intermediate) \n
index: 29 -> edge that subedge belongs to (if actual edge; self pointer) \n
index: 30 -> self identifying index (-1 if intermediate) \n
"""
def __init__(self):
#self.parameters = {'gamma' : 3.0,
# 'lambda' : 2.0,
# 'mu' : 1.0,
# 'nu' : 3.6/100,
# 'Pperm' : 100*1333.22,
# 'Pterm' : 60*1333.22,
# 'Qterm' : (0.125)/60,
# 'edge_num': 0}
self.set_parameters()
self.data = np.zeros((1,31))
self.radius_buffer = 0.01
self.fraction = None
self.set_assumptions()
self.rng_points = []
self.time = {'search':[],
'constraints':[],
'local_optimize':[],
'collision':[],
'close_time':[],
'search_1':[],
'search_2':[],
'search_3':[],
'search_4':[],
'add_time':[],
'add_1':[],
'add_2':[],
'add_3':[],
'add_4':[],
'total':[],
'brute_optimize': [],
'method_optimize': [],
'depth':[],
'method_time':[],
'brute_time':[],
'brute_x_value':[],
'brute_value':[],
'method_x_value':[],
'method_value':[],
'truth_x_value':[],
'truth_value':[]}
[docs] def set_parameters(self,**kwargs):
self.parameters = {}
self.parameters['gamma'] = kwargs.get('gamma',3.0)
self.parameters['lambda'] = kwargs.get('lambda',2.0)
self.parameters['mu'] = kwargs.get('mu',1.0)
self.parameters['nu'] = kwargs.get('nu',3.6)/100
self.parameters['Pperm'] = kwargs.get('Pperm',100)*1333.22
self.parameters['Pterm'] = kwargs.get('Pterm',60)*1333.22
self.parameters['Qterm'] = kwargs.get('Qterm',0.125)/60
self.parameters['edge_num'] = kwargs.get('edge_num',0)
self.parameters['rho'] = kwargs.get('rho',1.06)
[docs] def set_assumptions(self,**kwargs):
self.homogeneous = kwargs.get('homogeneous',True)
self.directed = kwargs.get('directed',False)
self.convex = kwargs.get('convex',False)
self.hollow = kwargs.get('hollow',False)
self.dimension = kwargs.get('dimension',3)
self.clamped_root= kwargs.get('clamped_root',False)
self.nonconvex_counter = 0
[docs] def show_assumptions(self):
print('homogeneous : {}'.format(self.homogeneous))
print('directed : {}'.format(self.directed))
print('convex : {}'.format(self.convex))
print('hollow : {}'.format(self.hollow))
print('dimension : {}'.format(self.dimension))
print('clamped root: {}'.format(self.clamped_root))
[docs] def set_boundary(self,boundary):
self.boundary = boundary
self.fraction = (self.boundary.volume**(1/3))/20
[docs] def set_root(self,low=-1,high=0,start=None,direction=None,
limit_high=None,limit_low=None,niter=200):
Qterm = self.parameters['Qterm']
gamma = self.parameters['gamma']
nu = self.parameters['nu']
Pperm = self.parameters['Pperm']
Pterm = self.parameters['Pterm']
result = set_root(self.data,self.boundary,Qterm,
gamma,nu,Pperm,Pterm,self.fraction,
self.homogeneous,self.convex,self.directed,
start,direction,limit_high,limit_low,self.boundary.volume,
low=-1,high=0,niter=niter)
self.data = result[0]
self.sub_division_map = result[1]
self.sub_division_index = result[2]
self.parameters['edge_num'] = 1
#self.sub_division_map = [-1]
#self.sub_division_index = np.array([0])
[docs] def add(self,low,high,isforest=False,radius_buffer=0.01,threshold=None,method='L-BFGS-B'):
vessel,data,sub_division_map,sub_division_index,threshold = add_branch(self,low,high,threshold_exponent=0.5,
threshold_adjuster=0.75,all_max_attempts=40,
max_attemps=3,sampling=20,max_skip=8,
flow_ratio=None,radius_buffer=radius_buffer,
isforest=isforest,threshold=threshold,method=method)
if isforest:
return vessel,data,sub_division_map,sub_division_index,threshold
else:
self.data = data
self.parameters['edge_num'] += 2
self.sub_division_map = sub_division_map
self.sub_division_index = sub_division_index
[docs] def n_add(self,n,method='L-BFGS-B'):
self.rng_points,_ = self.boundary.pick(size=40*n,homogeneous=True)
self.rng_points = self.rng_points.tolist()
for i in tqdm(range(n),desc='Adding vessels'):
#for i in range(n):
#self.rng_points = self.rng_points.tolist()
self.add(-1,0,method=method)
#plt.plot(list(range(len(self.time['search'][1:]))),self.time['search'][1:],label='search time')
#plt.plot(list(range(len(self.time['search'][1:]))),self.time['constraints'][1:],label='constraint time')
#plt.plot(list(range(len(self.time['search'][1:]))),self.time['local_optimize'][1:],label='local time')
#plt.plot(list(range(len(self.time['search'][1:]))),self.time['collision'][1:],label='collision time')
#plt.plot(list(range(len(self.time['search'][1:]))),self.time['search_1'][1:],label='search 1 time')
#plt.plot(list(range(len(self.time['search'][1:]))),self.time['search_2'][1:],label='search 2 time')
#plt.plot(list(range(len(self.time['search'][1:]))),self.time['search_3'][1:],label='search 3 time')
#plt.plot(list(range(len(self.time['search'][1:]))),self.time['search_4'][1:],label='search 4 time')
#plt.plot(list(range(len(self.time['search'][1:]))),self.time['close_time'][1:],label='close_time time')
#plt.plot(list(range(len(self.time['search'][1:]))),self.time['add_time'][1:],label='add_time')
#plt.plot(list(range(len(self.time['search'][1:]))),self.time['add_1'][1:],label='add_1')
#plt.plot(list(range(len(self.time['search'][1:]))),self.time['add_2'][1:],label='add_2')
#plt.plot(list(range(len(self.time['search'][1:]))),self.time['add_3'][1:],label='add_3')
#plt.plot(list(range(len(self.time['search'][1:]))),self.time['add_4'][1:],label='add_4')
#plt.plot(list(range(len(self.time['search'][1:]))),self.time['total'][1:],label='total')
#plt.ylim([0,0.1])
#plt.legend()
#plt.show()
[docs] def show(self,surface=False,vessel_colors='red',background_color='white',
resolution=100,show_segments=True,save=False,name=None,show=True,
surface_color='red',other_surface_color='blue'):
models = []
actors = []
if not isinstance(vessel_colors,str):
colors = vtk.vtkColor3d()
colors.Set(vessel_colors[0],vessel_colors[1],vessel_colors[2])
else:
colors = vtk.vtkNamedColors()
colors = colors.GetColor3d(vessel_colors)
if not isinstance(background_color,str):
backcolors = vtk.vtkColor3d()
backcolors.Set(background_color[0],background_color[1],background_color[2])
else:
backcolors = vtk.vtkNamedColors()
backcolors = backcolors.GetColor3d(background_color)
if not isinstance(surface_color,str):
surf_color = vtk.vtkColor3d()
surf_color.Set(surface_color[0],surface_color[1],surface_color[2])
else:
surf_color = vtk.vtkNamedColors()
surf_color = surf_color.GetColor3d(surface_color)
if not isinstance(other_surface_color,str):
other_surf_color = vtk.vtkColor3d()
other_surf_color.Set(other_surface_color[0],other_surface_color[1],other_surface_color[2])
else:
other_surf_color = vtk.vtkNamedColors()
other_surf_color = other_surf_color.GetColor3d(other_surface_color)
if show_segments:
if self.homogeneous:
data_subset = self.data[self.data[:,-1] > -1]
else:
data_subset = self.data[self.data[:,29] > -1]
for edge in range(data_subset.shape[0]):
center = tuple((data_subset[edge,0:3] + data_subset[edge,3:6])/2)
radius = data_subset[edge,21]
direction = tuple(data_subset[edge,12:15])
vessel_length = data_subset[edge,20]
cyl = vtk.vtkTubeFilter()
line = vtk.vtkLineSource()
line.SetPoint1(data_subset[edge,0],data_subset[edge,1],data_subset[edge,2])
line.SetPoint2(data_subset[edge,3],data_subset[edge,4],data_subset[edge,5])
cyl.SetInputConnection(line.GetOutputPort())
cyl.SetRadius(radius)
cyl.SetNumberOfSides(resolution)
models.append(cyl)
mapper = vtk.vtkPolyDataMapper()
actor = vtk.vtkActor()
mapper.SetInputConnection(cyl.GetOutputPort())
actor.SetMapper(mapper)
actor.GetProperty().SetColor(colors)
actors.append(actor)
if surface:
mapper = vtk.vtkPolyDataMapper()
actor = vtk.vtkActor()
mapper.SetInputData(self.boundary.polydata)
actor.SetMapper(mapper)
actor.GetProperty().SetColor(surf_color)
actor.GetProperty().SetOpacity(0.25)
actors.append(actor)
for other_surface in self.boundary.subtracted_volumes:
mapper = vtk.vtkPolyDataMapper()
actor = vtk.vtkActor()
mapper.SetInputData(other_surface.polydata)
actor.SetMapper(mapper)
actor.GetProperty().SetColor(other_surf_color)
actor.GetProperty().SetOpacity(0.5)
actors.append(actor)
renderer = vtk.vtkRenderer()
renderer.SetBackground(backcolors)
render_window = vtk.vtkRenderWindow()
render_window.AddRenderer(renderer)
render_window.SetWindowName('SimVascular Vessel Network')
interactor = vtk.vtkRenderWindowInteractor()
interactor.SetRenderWindow(render_window)
for actor in actors:
renderer.AddActor(actor)
if save:
render_window.Render()
w2if = vtk.vtkWindowToImageFilter()
w2if.SetInput(render_window)
w2if.Update()
writer = vtk.vtkPNGWriter()
if name is None:
tag = time.gmtime()
name = str(tag.tm_year)+str(tag.tm_mon)+str(tag.tm_mday)+str(tag.tm_hour)+str(tag.tm_min)+str(tag.tm_sec)
writer.SetFileName(os.getcwd()+os.sep+'{}.png'.format(name))
writer.SetInputData(w2if.GetOutput())
writer.Write()
elif show==True:
render_window.Render()
interactor.Start()
elif show==False:
for m in models:
m.Update()
return models
[docs] def save(self,filename=None):
if filename is None:
tag = time.gmtime()
filename = 'network_{}'.format(str(tag.tm_year)+
str(tag.tm_mon) +
str(tag.tm_mday)+
str(tag.tm_hour)+
str(tag.tm_min) +
str(tag.tm_sec))
os.mkdir(filename)
file = open(filename+"/vessels.ccob",'wb')
pickle.dump(self.data,file)
file.close()
file = open(filename+"/parameters.ccob",'wb')
parameters = (self.parameters,self.fraction,self.homogeneous,self.directed,self.convex)
pickle.dump(parameters,file)
file.close()
file = open(filename+"/boundary.ccob",'wb')
pickle.dumps(self.boundary,file)
file.close()
[docs] def load(self,filename):
file = open(filename+"/vessels.ccob",'rb')
self.data = pickle.load(file)
file.close()
file = open(filename+"/parameters.ccob",'rb')
self.parameters,self.fraction,self.homogeneous,self.directed,self.convex = pickle.load(file)
file.close()
file = open(filename+"/boundary.ccob",'rb')
self.boundary = pickle.loads(file)
file.close()
[docs] def export(self,steady=True,apply_distal_resistance=True,gui=True,cylinders=False,make=True,global_edge_size=None,splines=False,splines_file_path='',spline_sample_points=100):
if cylinders:
pv_data = []
models = self.show(show=False)
for m in models:
pv_data.append(pv.PolyData(var_inp=m.GetOutput()))
merge = pv_data[0].merge(pv_data[1:])
return merge
interp_xyz,interp_r,interp_n,frames,branches,interp_xyzr = get_interpolated_sv_data(self.data)
#points,radii,normals = sv_data(interp_xyzr,interp_r,radius_buffer=self.radius_buffer)
if steady:
time = [0, 1]
flow = [self.data[0,22], self.data[0,22]]
else:
time,flow = wave(self.data[0,22],self.data[0,21]*2) #changed wave function
time = time.tolist()
flow = flow.tolist()
flow[-1] = flow[0]
if apply_distal_resistance:
R = self.parameters['Pterm']/self.data[0,22]
else:
R = 0
if make:
num_caps = 1+2+int((self.parameters['edge_num']-1)/2)
options = file_options(num_caps,time=time,flow=flow,gui=gui,distal_resistance=R)
if global_edge_size is None:
options.set_mesh_options(global_edge_size=global_edge_size)
points,radii,normals = sv_data(interp_xyzr,interp_r,radius_buffer=self.radius_buffer)
build(points,radii,normals,options)
if splines:
spline_file = open(splines_file_path+'b_splines.txt','w+')
for ind,spline in enumerate(interp_xyzr):
spline_file.write('Vessel: {}, Number of Points: {}\n\n'.format(ind,spline_sample_points))
n = np.linspace(0,1,spline_sample_points)
spline_data = splev(n,spline[0])
for j in range(spline_sample_points):
spline_file.write('{}, {}, {}\n'.format(spline_data[0][j],spline_data[1][j],spline_data[2][j]))
spline_file.write('\n')
spline_file.close()
return interp_xyz,interp_r,interp_xyzr
[docs] def export_truncated(self,steady=True,radius=None,indicies=None,apply_distal_resistance=True,gui=True,cylinders=False,make=True):
if cylinders:
pv_data = []
models = self.show(show=False)
for m in models:
pv_data.append(pv.PolyData(var_inp=m.GetOutput()))
merge = pv_data[0].merge(pv_data[1:])
return merge
interp_xyz,interp_r,interp_n,frames,branches,interp_xyzr = get_truncated_interpolated_sv_data(self.data,radius=radius,indicies=indicies)
points,radii,normals = sv_data(interp_xyzr,interp_r,radius_buffer=self.radius_buffer)
if steady:
time = [0, 1]
flow = [self.data[0,22], self.data[0,22]]
else:
time,flow = wave(self.data[0,22],self.data[0,21]*2)
time = time.tolist()
flow = flow.tolist()
flow[-1] = flow[0]
if apply_distal_resistance:
R = self.parameters['Pterm']/self.data[0,22]
else:
R = 0
if make:
num_caps = 1+2+int((self.parameters['edge_num']-1)/2)
options = file_options(num_caps,time=time,flow=flow,gui=gui,distal_resistance=R)
build(points,radii,normals,options)
return interp_xyz,interp_r
[docs] def show_truncated(self,radius=None,indicies=None):
large,small = truncate(self.data,radius=radius,indicies=indicies)
combined = []
plotter = pv.Plotter()
models = []
for i in large:
combined += i
for i in range(self.data.shape[0]):
center = (self.data[i,0:3] + self.data[i,3:6])/2
m = pv.Cylinder(center=center,direction=self.data[i,12:15],radius=self.data[i,21],height=self.data[i,20])
if i in combined:
plotter.add_mesh(m,color='red')
else:
plotter.add_mesh(m,color='blue',opacity=0.4)
plotter.set_background('white')
return plotter
[docs] def export_3d_solid(self,outdir=None,folder="3d_tmp"):
if outdir is None:
outdir = os.getcwd()+os.sep+folder
else:
outdir = outdir+os.sep+folder
if not os.path.isdir(outdir):
os.mkdir(folder)
def polyline_from_points(pts,r):
poly = pv.PolyData()
poly.points = pts
cell = np.arange(0,len(pts),dtype=np.int_)
cell = np.insert(cell,0,len(pts))
poly.lines = cell
poly.scalars = r/r[0]
return poly
interp_xyz,interp_r,interp_xyzr = self.export(make=False)
vessels = []
tets = []
surfs = []
t_list = np.linspace(0,1,num=1000)
for vessel_id in range(len(interp_xyz)):
x,y,z,r = splev(t_list,interp_xyzr[vessel_id][0])
#_,r = splev(t_list,interp_r[vessel_id][0])
points = np.zeros((len(t_list),3))
points[:,0] = x
points[:,1] = y
points[:,2] = z
polyline = polyline_from_points(points,r)
vessel = polyline.tube(radius=r[0])
vessels.append(vessel)
vessel_fix = pymeshfix.MeshFix(remesh_surface(vessel.triangulate(),hausd=0.001,verbosity=0))
vessel_fix.repair(verbose=False)
surfs.append(vessel_fix.mesh)
#tets.append(tetgen.TetGen(vessel_fix.mesh))
#for i in tqdm(range(len(tets)),desc="Tetrahedralizing"):
# tets[i].tetrahedralize(minratio=1.2)
#surfs = []
#for i in tqdm(range(len(tets)),desc="Extracting Surfaces"):
# surfs.append(tets[i].grid.extract_surface().triangulate())
# surf_fix = pymeshfix.MeshFix(surfs[-1])
# surf_fix.repair(verbose=False)
# surfs[-1] = surf_fix.mesh
unioned = surfs[0].boolean_union(surfs[1])
unioned = unioned.clean()
for i in tqdm(range(2,len(surfs)),desc="Unioning"):
unioned = unioned.boolean_union(surfs[i])
unioned = unioned.clean()
unioned.save(outdir+os.sep+"unioned_solid.vtp")
with open(outdir+os.sep+"simvascular_python_script.py","w") as file:
file.write(export_3d_only.format(outdir+os.sep+"unioned_solid.vtp"))
return vessels,unioned
[docs] def export_0d_simulation(self,steady=True,outdir=None,folder="0d_tmp",number_cardiac_cycles=1,
number_time_pts_per_cycle=5,density=1.06,viscosity=0.04,material="olufsen",
olufsen={'k1':0.0,'k2':-22.5267,'k3':1.0e7,'material exponent':1.0,'material pressure':0.0},
linear={'material ehr':1e7,'material pressure':0.0},path_to_0d_solver=None,
viscosity_model='constant',vivo=True):
"""
This script builds the 0D input file for running 0D simulation.
Parameters:
-----------
steady: bool, optional, [default=True]
outdir: str, optional, [default=None]
folder: str, optional, [default="tmp"]
number_cardiac_cycles: int, optional, [default=1]
"""
if outdir is None:
outdir = os.getcwd()+os.sep+folder
else:
outdir = outdir+os.sep+folder
if not os.path.isdir(outdir):
os.mkdir(folder)
if path_to_0d_solver is None:
path_to_0d_solver = locate_0d_solver()
else:
path_to_0d_solver = locate_0d_solver(windows_drive=path_to_0d_solver,linux_drive=path_to_0d_solver)
input_file = {'description':{'description of case':None,
'analytical results':None},
'boundary_conditions':[],
'junctions':[],
'simulation_parameters':{},
'vessels':[]}
simulation_parameters = {}
simulation_parameters["number_of_cardiac_cycles"] = number_cardiac_cycles
simulation_parameters["number_of_time_pts_per_cardiac_cycle"] = number_time_pts_per_cycle
input_file['simulation_parameters'] = simulation_parameters
for vessel in range(self.data.shape[0]):
tmp = {}
tmp['vessel_id'] = vessel
tmp['vessel_length'] = self.data[vessel,20]
tmp['vessel_name'] = 'branch'+str(vessel)+"_seg0"
tmp['zero_d_element_type'] = "BloodVessel"
if material == "olufsen":
material_stiffness = olufsen['k1']*np.exp(olufsen['k2']*self.data[vessel,21])+olufsen['k3']
else:
material_stiffness = linear['material ehr']
zero_d_element_values = {}
if viscosity_model == 'constant':
nu = self.parameters['nu']
elif viscosity_model == 'modified viscosity law':
W = 1.1
lam = 0.5
D = self.data[vessel,21]*2*(10000)
nu_ref = self.parameters['nu']
Hd = 0.45 #discharge hematocrit (dimension-less)
C = lambda d: (0.8+np.exp(-0.075))*(-1+(1/(1+10**(-11)*d**12)))+(1/(1+10**(-11)*d**12))
nu_vitro_45 = lambda d: 220*np.exp(-1.3*d) + 3.2-2.44*np.exp(-0.06*d**0.645)
nu_vivo_45 = lambda d: 6*np.exp(-0.085*d)+3.2-2.44*np.exp(-0.06*d**0.645)
if vivo == True:
nu_45 = nu_vivo_45
else:
nu_45 = nu_vitro_45
nu_mod = lambda d: (1+(nu_45 - 1)*(((1-Hd)**C-1)/((1-0.45)**C-1))*(d/(d-W))**(4*lam))*(d/(d-W))**(4*(1-lam))
ref = nu_ref - nu_mod(10000) # 1cm (units given in microns)
nu = nu_mod(D) + ref
zero_d_element_values["R_poiseuille"] = ((8*nu/np.pi)*self.data[vessel,20])/self.data[vessel,21]**4
zero_d_element_values["C"] = (3*self.data[vessel,20]*np.pi*self.data[vessel,21]**2)/(2*material_stiffness)
zero_d_element_values["L"] = (self.data[vessel,20]*density)/(np.pi*self.data[vessel,21]**2)
zero_d_element_values["stenosis_coefficient"] = 0.0
tmp['zero_d_element_values'] = zero_d_element_values
if vessel == 0:
bc = {}
bc['bc_name'] = "INFLOW"
bc['bc_type'] = "FLOW"
bc_values = {}
if steady:
bc_values["Q"] = [self.data[vessel,22], self.data[vessel,22]]
bc_values["t"] = [0, 1]
with open(outdir+os.sep+"inflow.flow","w") as file:
for i in range(len(bc_values["t"])):
file.write("{} {}\n".format(bc_values["t"][i],bc_values["Q"][i]))
file.close()
else:
time,flow = wave(self.data[vessel,22],self.data[vessel,21]*2) # changed wave function
bc_values["Q"] = flow.tolist()
bc_values["t"] = time.tolist()
bc_values["Q"][-1] = bc_values["Q"][0]
simulation_parameters["number_of_time_pts_per_cardiac_cycle"] = len(bc_values["Q"])
with open(outdir+os.sep+"inflow.flow","w") as file:
for i in range(len(bc_values["t"])):
file.write("{} {}\n".format(bc_values["t"][i],bc_values["Q"][i]))
file.close()
bc['bc_values'] = bc_values
input_file['boundary_conditions'].append(bc)
tmp['boundary_conditions'] = {'inlet':"INFLOW"}
if self.data[vessel,15] > 0 and self.data[vessel,16] > 0:
junction = {}
junction['inlet_vessels'] = [vessel]
junction['junction_name'] = "J"+str(vessel)
junction['junction_type'] = "NORMAL_JUNCTION"
junction['outlet_vessels'] = [int(self.data[vessel,15]), int(self.data[vessel,16])]
input_file['junctions'].append(junction)
elif self.data[vessel,15] < 0 and self.data[vessel,16] < 0:
bc = {}
bc['bc_name'] = "OUT"+str(vessel)
bc['bc_type'] = "RESISTANCE"
bc_values = {}
bc_values["Pd"] = self.parameters["Pterm"]
bc_values["R"] = 0
bc['bc_values'] = bc_values
input_file['boundary_conditions'].append(bc)
tmp['boundary_conditions'] = {'outlet':'OUT'+str(vessel)}
else:
junction = {}
junction['inlet_vessels'] = [vessel]
junction['junction_name'] = "J"+str(vessel)
junction['junction_type'] = "NORMAL_JUNCTION"
junction['outlet_vessels'] = [int(self.data[vessel,15]), int(self.data[vessel,16])]
input_file['junctions'].append(junction)
input_file['vessels'].append(tmp)
obj = json.dumps(input_file,indent=4)
with open(outdir+os.sep+"solver_0d.in","w") as file:
file.write(obj)
file.close()
with open(outdir+os.sep+"plot_0d_results_to_3d.py","w") as file:
file.write(make_results)
file.close()
with open(outdir+os.sep+"plot_0d_results_at_slices.py","w") as file:
file.write(view_plots)
file.close()
with open(outdir+os.sep+"run.py","w") as file:
if platform.system() == "Windows":
if path_to_0d_solver is not None:
solver_path = path_to_0d_solver.replace(os.sep,os.sep+os.sep)
else:
solver_path = path_to_0d_solver
print("WARNING: Solver location will have to be given manually")
print("Current solver path is: {}".format(solver_path))
solver_file = (outdir+os.sep+"solver_0d.in").replace(os.sep,os.sep+os.sep)
else:
if path_to_0d_solver is not None:
solver_path = path_to_0d_solver
else:
solver_path = path_to_0d_solver
print("WARNING: Solver location will have to be given manually")
print("Current solver path is: {}".format(solver_path))
solver_file = outdir+os.sep+"solver_0d.in"
file.write(run_0d_script.format(solver_path,solver_file))
file.close()
geom = np.zeros((self.data.shape[0],8))
geom[:,0:3] = self.data[:,0:3]
geom[:,3:6] = self.data[:,3:6]
geom[:,6] = self.data[:,20]
geom[:,7] = self.data[:,21]
np.savetxt(outdir+os.sep+"geom.csv",geom,delimiter=",")
[docs] def export_1d_simulation(self,steady=True,outdir=None,folder="1d_tmp",number_cariac_cycles=1,num_points=100):
_,_,interp_xyzr = self.export(make=False)
# Make Centerline Approximation Polydata
#branches = get_branches(self.data)
#centerline_ids = []
#for id,branch in enumerate(branches):
# branch_ids = []
# for seg in branch
if outdir is None:
outdir = os.getcwd()
outdir = outdir + os.sep + folder
if not os.path.exists(outdir):
os.mkdir(outdir)
def make_points(x,y,z):
"""Helper to make XYZ points"""
return np.column_stack((x, y, z))
def lines_from_points(points):
"""Given an array of points, make a line set"""
poly = pv.PolyData()
poly.points = points
cells = np.full((len(points) - 1, 3), 2, dtype=np.int_)
cells[:, 1] = np.arange(0, len(points) - 1, dtype=np.int_)
cells[:, 2] = np.arange(1, len(points), dtype=np.int_)
poly.lines = cells
return poly
polys = []
for ind,spline in enumerate(interp_xyzr):
n = np.linspace(0,1,num_points)
spline_data = splev(n,spline[0])
spline_data_normal = splev(n,spline[0],der=1)
points = make_points(spline_data[0],spline_data[1],spline_data[2])
normal = make_points(spline_data_normal[0],spline_data_normal[1],spline_data_normal[2])
normal = normal/np.linalg.norm(normal,axis=1).reshape(-1,1)
poly_line = lines_from_points(points)
poly_line['VesselId'] = np.ones(num_points,dtype=int)*ind
poly_line['MaximumInscribedSphereRadius'] = spline_data[3]
poly_line['CenterlineSectionArea'] = np.pi*spline_data[3]**2
poly_line.point_data.set_array(normal,'CenterlineSectionNormal')
polys.append(poly_line)
for ind in range(len(polys)):
cent_ids = np.zeros((polys[ind].n_points,len(polys)),dtype=int)
polys[ind].point_data.set_array(cent_ids,'CenterlineId')
polys[ind].point_data['CenterlineId'][:,ind] = 1
bifurcation_point_ids = [] # polys ind index, polys jnd index, polys jnd point index
for ind in range(1,len(polys)):
current = ind
count = 0
while current != 0:
#if jnd == ind:
# continue
jnd = None
closest_dist = np.inf
for i in range(len(polys)):
if i == current:
continue
closest_pt_id = polys[i].find_closest_point(polys[current].points[0])
closest_point = polys[i].points[closest_pt_id]
closest_dist_tmp = np.linalg.norm(polys[current].points[0] - closest_point)
if closest_dist_tmp < closest_dist:
jnd = i
closest_dist = closest_dist_tmp
closest_point_id = polys[jnd].find_closest_point(polys[current].points[0])
closest_point = polys[jnd].points[closest_point_id]
dist = np.linalg.norm(polys[current].points[0] - closest_point)
if count == 0:
bifurcation_point_ids.append([ind,jnd,closest_point_id,closest_point])
#if dist < polys[current].point_data['MaximumInscribedSphereRadius'][0]:
polys[jnd].point_data['CenterlineId'][0:closest_point_id+1,ind] = 1
#bifurcation_point_ids.append([ind,jnd,closest_point_id,closest_point])
current = jnd
count += 1
# Determine Branch Temp Ids
branch_tmp_count = 0
for ind in range(len(polys)):
tmp_split = []
for bif in bifurcation_point_ids:
if bif[1] == ind:
tmp_split.append(bif[2])
tmp_split.sort()
tmp_split.insert(0,0)
tmp_split.append(None)
branch_tmp_ids = np.zeros(polys[ind].points.shape[0],dtype=int)
for i in range(1,len(tmp_split)):
branch_tmp_ids[tmp_split[i-1]:tmp_split[i]] = branch_tmp_count
branch_tmp_count += 1
polys[ind].point_data['BranchIdTmp'] = branch_tmp_ids
for ind in range(len(polys)):
tmp_bif = []
tmp_bif_pt = []
for bif in bifurcation_point_ids:
if ind > 0:
rad = polys[ind].point_data['MaximumInscribedSphereRadius'][0]
pt = polys[ind].points[0,:]
b = np.argwhere(np.linalg.norm(polys[ind].points - pt,axis=1)<rad).flatten().tolist()
b_pt = b.pop(b.index(0))
tmp_bif.extend(b)
tmp_bif_pt.append(0)
if bif[1] == ind:
rad = polys[ind].point_data['MaximumInscribedSphereRadius'][bif[2]]
pt = polys[ind].points[bif[2]]
b = np.argwhere(np.linalg.norm(polys[ind].points - pt,axis=1)<rad).flatten().tolist()
b_pt = b.pop(b.index(bif[2]))
tmp_bif.extend(b)
tmp_bif_pt.append(b_pt)
tmp_bif = np.array(tmp_bif,dtype=int)
tmp_bif_pt = np.array(tmp_bif_pt,dtype=int)
bifurcation_id_tmp = np.ones(polys[ind].points.shape[0],dtype=int)*-1
bifurcation_id_tmp[tmp_bif] = 2
bifurcation_id_tmp[tmp_bif_pt] = 1
polys[ind].point_data['BifurcationIdTmp'] = bifurcation_id_tmp
bif_id_count = 0
for ind in range(len(polys)):
bif_id = []
new = True
for jnd in range(polys[ind].n_points):
if polys[ind].point_data['BifurcationIdTmp'][jnd] > 0 and new:
bif_id.append(bif_id_count)
#bif_id_count_next = 1 + bif_id_count
new = False
elif polys[ind].point_data['BifurcationIdTmp'][jnd] > 0 and not new:
bif_id.append(bif_id_count)
#bif_id_count += 1
#new = True
elif polys[ind].point_data['BifurcationIdTmp'][jnd] < 0 and not new:
bif_id.append(-1)
bif_id_count += 1
new = True
else:
bif_id.append(-1)
bif_id = np.array(bif_id)
polys[ind].point_data['BifurcationId'] = bif_id
branch_id_count = 0
for ind in range(len(polys)):
branch_id = []
for jnd in range(polys[ind].n_points):
new = False
if polys[ind].point_data['BifurcationId'][jnd] < 0:
branch_id.append(branch_id_count)
new = False
elif polys[ind].point_data['BifurcationId'][jnd] > 0 and not new:
branch_id.append(-1)
branch_id_count += 1
new = True
else:
branch_id.append(-1)
branch_id = np.array(branch_id)
polys[ind].point_data['BranchId'] = branch_id
# Set Path Values for Branches and Bifurcations also obtain outlet branches
outlets = []
for ind in range(len(polys)):
branch_ids = list(set(polys[ind].point_data['BranchId'].tolist()))
outlets.append(max(branch_ids))
branch_ids.sort()
path_init = np.zeros(polys[ind].points.shape[0])
polys[ind].point_data['Path'] = path_init
if branch_ids[0] == -1:
branch_ids = branch_ids[1:]
for b_idx in branch_ids:
poly_pt_ids = np.argwhere(polys[ind].point_data['BranchId']==b_idx).flatten()
poly_pt_path = np.cumsum(np.insert(np.linalg.norm(np.diff(polys[ind].points[poly_pt_ids],axis=0),axis=1),0,0))
polys[ind].point_data['Path'][poly_pt_ids] = poly_pt_path
bif_ids = list(set(polys[ind].point_data['BifurcationId'].tolist()))
bif_ids.sort()
if bif_ids[0] == -1:
bif_ids = bif_ids[1:]
for bif_idx in bif_ids:
poly_pt_ids = np.argwhere(polys[ind].point_data['BifurcationId']==bif_idx).flatten()
poly_pt_path = np.cumsum(np.insert(np.linalg.norm(np.diff(polys[ind].points[poly_pt_ids],axis=0),axis=1),0,0))
polys[ind].point_data['Path'][poly_pt_ids] = poly_pt_path
# Set Point Ids
Global_node_count = 0
branch_starts = []
for ind in range(len(polys)):
GlobalNodeId = list(range(Global_node_count,Global_node_count+polys[ind].n_points))
Global_node_count = GlobalNodeId[-1]+1
if ind < len(polys):
branch_starts.append(Global_node_count)
GlobalNodeId = np.array(GlobalNodeId)
polys[ind].point_data['GlobalNodeId'] = GlobalNodeId
# Merge and Connect Lines
centerlines_all = polys[0]
for ind in range(1,len(polys)):
closest_pt_id = centerlines_all.find_closest_point(polys[ind].points[0])
closest_next_id = centerlines_all.points.shape[0]
centerlines_all = centerlines_all.merge(polys[ind])
new_line = [2,closest_pt_id,closest_next_id]
centerlines_all.lines = np.hstack((centerlines_all.lines,np.array(new_line)))
# Connect lines among spline branches
#for branch_global_node_id in branch_starts:
# pt_idx = np.argwhere(centerline_all.point_data['GlobalNodeId']==branch_global_node_id)
# remaining_points =
# Generate 1D Solver Files
centerlines_all.save(outdir+os.sep+'centerlines.vtp')
#param = ROM.parameters.Parameters()
#param.output_directory = outdir
#param
return centerlines_all,polys
[docs] def export_centerlines(self,outdir=None,folder="centerlines_tmp",num_points=100):
_,_,interp_xyzr = self.export(make=False)
if outdir is None:
outdir = os.getcwd()
outdir = outdir + os.sep + folder
if not os.path.exists(outdir):
os.mkdir(outdir)
def make_points(x,y,z):
"""Helper to make XYZ points"""
return np.column_stack((x, y, z))
def lines_from_points(points):
"""Given an array of points, make a line set"""
poly = pv.PolyData()
poly.points = points
cells = np.full((len(points) - 1, 3), 2, dtype=np.int_)
cells[:, 1] = np.arange(0, len(points) - 1, dtype=np.int_)
cells[:, 2] = np.arange(1, len(points), dtype=np.int_)
poly.lines = cells
return poly
polys = []
for ind,spline in enumerate(interp_xyzr):
n = np.linspace(0,1,num_points)
spline_data = splev(n,spline[0])
spline_data_normal = splev(n,spline[0],der=1)
points = make_points(spline_data[0],spline_data[1],spline_data[2])
normal = make_points(spline_data_normal[0],spline_data_normal[1],spline_data_normal[2])
normal = normal/np.linalg.norm(normal,axis=1).reshape(-1,1)
poly_line = lines_from_points(points)
#poly_line['VesselId'] = np.ones(num_points,dtype=int)*ind
poly_line['MaximumInscribedSphereRadius'] = spline_data[3]
#poly_line['CenterlineSectionArea'] = np.pi*spline_data[3]**2
#poly_line.point_data.set_array(normal,'CenterlineSectionNormal')
polys.append(poly_line)
for ind,poly in enumerate(polys):
poly.save(outdir+os.sep+"centerline_id_{}.vtp".format(ind))
return polys
[docs] def collision_free(self,outside_vessels,radius_buffer=0.01):
return no_outside_collision(self,outside_vessels,radius_buffer=radius_buffer)
[docs] def export_gcode(self):
interp_xyz,interp_r,interp_n,frames,branches,interp_xyzr = get_interpolated_sv_data(self.data)
points,radii,normals = sv_data(interp_xyzr,interp_r,radius_buffer=self.radius_buffer)
generate_gcode(points)
[docs]class forest:
def __init__(self,boundary=None,number_of_networks=1,
trees_per_network=[2],scale=None,start_points=None,
directions=None,root_lengths_high=None,
root_lengths_low=None,convex=False,compete=False):
"""
The forest class defines some number of vascular networks which can grow
within one given perfusion domain.
Parameters
boundary: svcco.surface object
perfusion domain object defined by svcco.surface
which will serve as a boundary for all networks
built for this forest object
For more information see:
>>>help(svcco.surface)
number_of_networks: int
integer specifying the number of networks to be
built for the given forest object (default=1)
trees_per_network: list of ints
list of integers specifying the number of
interpenetrating trees per network. By definition
len(trees_per_network) = number_of_networks
scale:
start_points:
directions:
root_lengths_high:
root_lengths_low:
convex:
compete:
Methods:
set_roots
add
"""
self.networks = []
self.connections = []
self.assignments = []
self.backup = []
self.boundary = boundary
self.convex = convex
self.compete = compete
self.number_of_networks = number_of_networks
self.trees_per_network = trees_per_network
if isinstance(start_points,type(None)):
start_points = [[None for j in range(trees_per_network[i])] for i in range(number_of_networks)]
if isinstance(directions,type(None)):
directions = [[None for j in range(trees_per_network[i])] for i in range(number_of_networks)]
if isinstance(root_lengths_high,type(None)):
root_lengths_high = [[None for j in range(trees_per_network[i])] for i in range(number_of_networks)]
if isinstance(root_lengths_low,type(None)):
root_lengths_low = [[None for j in range(trees_per_network[i])] for i in range(number_of_networks)]
self.starting_points = start_points
self.directions = directions
self.root_lengths_high = root_lengths_high
self.root_lengths_low = root_lengths_low
networks = []
for idx in range(self.number_of_networks):
network = []
for jdx in range(self.trees_per_network[idx]):
tmp = tree()
tmp.set_assumptions(convex=self.convex)
tmp.set_boundary(self.boundary)
if self.directions[idx][jdx] is not None:
tmp.clamped_root = True
network.append(tmp)
networks.append(network)
self.networks = networks
[docs] def set_roots(self,scale=None,bounds=None):
if self.compete:
volume = deepcopy(self.boundary.volume)
self.boundary.volume = self.boundary.volume/self.number_of_networks
if self.boundary is None:
print("Need to assign boundary!")
return
networks = []
connections = []
backup = []
for idx in range(self.number_of_networks):
network = []
if not isinstance(self.trees_per_network,list):
print("trees_per_network must be list of ints"+
" with length equal to number_of_networks")
for jdx in range(self.trees_per_network[idx]):
#tmp = tree()
tmp = self.networks[idx][jdx]
#tmp.set_assumptions(convex=self.convex)
#if self.directions[idx][jdx] is not None:
# tmp.clamped_root = True
#tmp.set_boundary(self.boundary)
if scale is not None:
tmp.parameters['Qterm'] *= scale
if idx == 0 and jdx == 0:
collisions = [True]
else:
collisions = [True]
while any(collisions):
if bounds is None:
tmp.set_root(start=self.starting_points[idx][jdx],
direction=self.directions[idx][jdx],
limit_high=self.root_lengths_high[idx][jdx],
limit_low=self.root_lengths_low[idx][jdx])
else:
print("Not implemented yet!")
collisions = []
repeat = False
for net in networks:
for t in net:
collisions.append(not t.collision_free(tmp.data[0,:].reshape(1,-1)))
if collisions[-1]:
repeat = True
break
if repeat:
break
for t in network:
collisions.append(not t.collision_free(tmp.data[0,:].reshape(1,-1)))
if collisions[-1]:
repeat = True
break
#print(collisions)
network.append(tmp)
networks.append(network)
connections.append(None)
backup.append(None)
self.networks = networks
self.connections = connections
self.backup = backup
if self.compete:
self.boundary.volume = volume
[docs] def add(self,number_of_branches,network_id=0,radius_buffer=0.01,exact=True):
if self.compete:
network_id = -1
if network_id == -1:
exit_number = []
active_networks = list(range(len(self.networks)))
for network in self.networks:
exit_number.append(network[0].parameters['edge_num'] + number_of_branches)
else:
exit_number = []
active_networks = [network_id]
for nid in range(self.number_of_networks):
if nid in active_networks:
exit_number.append(self.networks[nid][0].parameters['edge_num'] + number_of_branches)
else:
exit_number.append(self.networks[nid][0].parameters['edge_num'])
for AN in active_networks:
for ATr in range(self.trees_per_network[AN]):
self.networks[AN][ATr].rng_points,_ = self.boundary.pick(size=40*number_of_branches,homogeneous=True)
self.networks[AN][ATr].rng_points = self.networks[AN][ATr].rng_points.tolist()
if self.compete:
# new
#self.networks[AN][ATr].rng_points,_ = self.boundary.pick(size=len(self.boundary.tet_verts),homogeneous=True,replacement=False)
#self.networks[AN][ATr].rng_points = self.networks[AN][ATr].rng_points.tolist()
for n in tqdm(range(len(self.networks[AN][ATr].rng_points))):
pt = np.array(self.networks[AN][ATr].rng_points.pop(0))
if exact:
other_vessels,distances = close_exact(self.networks[AN][ATr].data,pt)
else:
other_vessels,distances = close(self.networks[AN][ATr].data,pt)
minimum_distance = min(distances)
#if minimum_distance < 4*forest.networks[nid][njd].data[other_vessels[0],21]:
# continue
retry = False
for idx in active_networks:
for jdx in list(range(self.trees_per_network[idx])):
if idx == AN:
continue
if exact:
other_vessels,distances = close_exact(self.networks[idx][jdx].data,pt)
else:
other_vessels,distances = close(self.networks[idx][jdx].data,pt)
if min(distances) < minimum_distance:
retry = True
break
if retry:
break
if retry:
continue
else:
self.networks[AN][ATr].rng_points.insert(-1,pt.tolist())
#new
if not self.compete:
while len(active_networks) > 0:
for nid in active_networks:
number_of_trees = len(self.networks[nid])
for njd in range(number_of_trees):
success = False
threshold = None
while not success:
vessel,data,sub_division_map,sub_division_index,threshold = self.networks[nid][njd].add(-1,0,isforest=True,radius_buffer=radius_buffer,threshold=threshold)
new_vessels = np.vstack((data[vessel,:],data[-2,:],data[-1,:]))
repeat = False
for nikd in range(len(self.networks)):
if nikd == nid:
check_trees = list(range(len(self.networks[nikd])))
check_trees.remove(njd)
else:
check_trees = list(range(len(self.networks[nikd])))
for njkd in check_trees:
if not self.networks[nikd][njkd].collision_free(new_vessels,radius_buffer=radius_buffer):
repeat = True
break
if repeat:
break
if repeat:
#p = self.show()
#p.show()
#print("\n\ncollision\n\n")
continue
else:
success = True
self.networks[nid][njd].data = data
self.networks[nid][njd].parameters['edge_num'] += 2
self.networks[nid][njd].sub_division_map = sub_division_map
self.networks[nid][njd].sub_division_index = sub_division_index
#print(self.networks[nid][0].parameters['edge_num'])
if self.networks[nid][0].parameters['edge_num'] >= exit_number[nid]:
active_networks.remove(nid)
else:
for i in tqdm(range(number_of_branches),desc="Adding branches"):
compete_add(self,network_ids=network_id,radius_buffer=radius_buffer)
[docs] def show(self,show=True,resolution=100,final=False,merged_trees=False,background='white',off_screen=False):
if merged_trees:
colors = ['r','b','g','y']
plotter = pv.Plotter()
merged_list = []
for i in range(self.number_of_networks):
for j in range(self.trees_per_network[i]):
merged_list.append(self.networks[i][j].export(cylinders=True))
plotter.add_mesh(merged_list[-1],colors[i])
return plotter,merged_list
model_networks = []
for net_id,network in enumerate(self.networks):
model_trees = []
for tree_id,network_tree in enumerate(network):
model = []
for edge in range(network_tree.parameters['edge_num']):
#if network_tree.data[edge,15]==-1 and network_tree.data[edge,16]==-1 and edge != 0:
# dis_point = (network_tree.data[edge,0:3] + network_tree.data[edge,3:6])/2
# vessel_length = network_tree.data[edge,20]/2
#else:
dis_point = network_tree.data[edge,3:6]
vessel_length = network_tree.data[edge,20]
center = tuple((network_tree.data[edge,0:3] + dis_point)/2)
radius = network_tree.data[edge,21]
direction = tuple(network_tree.data[edge,12:15])
#vessel_length = network_tree.data[edge,20]
model.append(pv.Cylinder(radius=radius,height=vessel_length,
center=center,direction=direction,
resolution=resolution))
if not np.any(self.connections[net_id] is None):
for edge in range(self.connections[net_id][tree_id].shape[0]):
term = self.connections[net_id][tree_id][edge,:]
center = tuple((term[0:3]+term[3:6])/2)
radius = term[21]
direction = term[12:15]
vessel_length = term[20]
model.append(pv.Cylinder(radius=radius,height=vessel_length,
center=center,direction=direction,
resolution=resolution))
#for edge in range(len(self.connections[net_id][0])):
# term = network_tree.data[self.assignments[net_id][tree_id][edge],:]
# conn = self.connections[net_id][0][edge]
# center = tuple((term[3:6] + conn)/2)
# radius = term[21]
# direction = tuple(conn-term[3:6])
# vessel_length = np.linalg.norm(conn-term[3:6])
# model.append(pv.Cylinder(radius=radius,height=vessel_length,
# center=center,direction=direction,
# resolution=resolution))
model_trees.append(model)
model_networks.append(model_trees)
"""
model_connections = []
for connections in conn:
if connections is None:
continue
else:
for c_idx in range(connections.shape[0]):
center = tuple((connections[c_idx,0:3] + connections[c_idx,3:6])/2)
radius = connections[c_idx,21]
direction = tuple((connections[c_idx,3:6] - connections[c_idx,0:3])/connections[c_idx,20])
vessel_length = connections[c_idx,20]
model_conn.append(pv.Cylinder(radius=radius,height=vessel_length,
center=center,direction=direction,
resolution=resolution))
"""
if show:
plot = pv.Plotter(off_screen=off_screen)
colors = ['r','b','g','y']
plot.set_background(background)
for model_idx,model_network in enumerate(model_networks):
for color_idx, model_tree in enumerate(model_network):
for model in model_tree:
plot.add_mesh(model,colors[color_idx])
#for c_model in model_conn:
# plot.add_mesh(c_model,'r')
#if i == 0:
# plot.add_mesh(model[i],'r')
#else:
# plot.add_mesh(model[i],'b')
#path = plot.generate_orbital_path(n_points=100,viewup=(1,1,1))
#plot.show(auto_close=False)
#plot.open_gif('cco_gamma.gif')
#plot.orbit_on_path(path,write_frames=True)
#plot.close()
return plot
[docs] def connect(self,network_id=-1,buffer=None,curve_sample_size_min=5,curve_sample_size_max=11,curve_degree=3):
self.forest_copy = self.copy()
self.forest_copy.connections,self.forest_copy.assignments = connect(self.forest_copy,network_id=network_id,buffer=buffer)
#self.forest_copy.connections,self.forest_copy.connected_forest,self.splines = smooth(self.forest_copy,curve_sample_size_min=curve_sample_size_min,curve_sample_size_max=curve_sample_size_max,curve_degree=curve_degree)
self.forest_copy.connections,_,_ = link(self.forest_copy)
[docs] def assign(self):
self.connections,self.assignments = connect(self)
[docs] def rotate(self):
forest_copy = self.copy()
forest_copy.assign()
comb,pts = rotate_terminals(forest_copy)
self.forest_copy = forest_copy
return comb,pts
[docs] def copy(self):
forest_copy = forest(boundary=self.boundary,number_of_networks=self.number_of_networks,
trees_per_network=self.trees_per_network,scale=None,convex=self.convex,
compete=self.compete)
for ndx in range(self.number_of_networks):
for tdx in range(self.trees_per_network[ndx]):
forest_copy.networks[ndx][tdx].data = deepcopy(self.networks[ndx][tdx].data)
forest_copy.networks[ndx][tdx].parameters = deepcopy(self.networks[ndx][tdx].parameters)
forest_copy.networks[ndx][tdx].sub_division_map = deepcopy(self.networks[ndx][tdx].sub_division_map)
forest_copy.networks[ndx][tdx].sub_division_index = deepcopy(self.networks[ndx][tdx].sub_division_index)
forest_copy.connections = deepcopy(self.connections)
forest_copy.assignments = deepcopy(self.assignments)
return forest_copy
[docs] def export_solid(self,outdir=None,folder="3d_tmp",shell=False,variable_thickness=False,shell_thickness=0.01):
if outdir is None:
outdir = os.getcwd()+os.sep+folder
else:
outdir = outdir+os.sep+folder
if not os.path.isdir(outdir):
os.mkdir(folder)
def polyline_from_points(pts,r):
poly = pv.PolyData()
poly.points = pts
cell = np.arange(0,len(pts),dtype=np.int_)
cell = np.insert(cell,0,len(pts))
poly.lines = cell
poly["radius"] = r #r/min(r)
return poly
final_points,final_radii,final_normals,CONNECTED_COPY,ALL_INTERP_XYZ,ALL_INTERP_RADII = self.export()
ALL_vessels = []
ALL_tets = []
ALL_surfs = []
ALL_vessel_shells = []
ALL_tet_shells = []
ALL_surf_shells = []
t_list = np.linspace(0,1,num=1000)
P = self.show()
for net_id in range(len(final_points)):
for tree in range(self.trees_per_network[net_id]-1):
interp_xyz = ALL_INTERP_XYZ[net_id][tree]
interp_r = ALL_INTERP_RADII[net_id][tree]
vessels = []
tets = []
surfs = []
if shell:
vessel_shells = []
tet_shells = []
surf_shells = []
for vessel_id in range(len(self.ALL_POINTS[net_id][tree])):
#x,y,z = splev(t_list,interp_xyz[vessel_id][0])
#_,r = splev(t_list,interp_r[vessel_id][0])
#points = np.zeros((len(t_list),3))
#points[:,0] = x
#points[:,1] = y
#points[:,2] = z
points = np.array(self.ALL_POINTS[net_id][tree][vessel_id])
r = np.array(self.ALL_RADII[net_id][tree][vessel_id])
#print("Points: {}".format(points))
#print("Radii: {}".format(r))
if shell:
polyline_shell = polyline_from_points(points,r+shell_thickness)
vessel_shell = polyline_shell.tube(radius=min(r+shell_thickness),scalars='radius',radius_factor=max(r+shell_thickness)/min(r+shell_thickness)).triangulate()
#vessel_shell = remesh_surface(vessel_shell,auto=False,hausd=min(r+shell_thickness)/10,verbosity=0)
vessel_shells.append(vessel_shell)
tet_shells.append(tetgen.TetGen(vessel_shell))
polyline = polyline_from_points(points,r)
vessel = polyline.tube(radius=min(r),scalars='radius',radius_factor=max(r)/min(r)).triangulate()
#vessel = remesh_surface(vessel,auto=False,hausd=min(r)/10,verbosity=0)
vessels.append(vessel)
P.add_mesh(vessel,show_edges=True,opacity=0.4)
P.add_points(points,render_points_as_spheres=True,point_size=20)
tets.append(tetgen.TetGen(vessel))
desc1 = 'Tetrahedralizing'
desc1 = desc1+' '*(40-len(desc1))
# Disable
def blockPrint():
sys.stdout = open(os.devnull, 'w')
# Restore
def enablePrint():
sys.stdout = sys.__stdout__
#@timeout(60)
def tetrahedralize(tet):
tet.tetrahedralize(minratio=1.2)
return tet
tet_success = True
tet_fail_list = []
tet_fail_shell_list = []
for i in tqdm(range(len(tets)),position=0,leave=False,bar_format='{l_bar}{bar:50}{r_bar}{bar:-10b}',desc=desc1):
blockPrint()
try:
tets[i] = tetrahedralize(tets[i])
except:
try:
fixed_mesh = pymeshfix.MeshFix(vessels[i])
fixed_mesh.repair(verbose=False)
tets[i] = tetgen.TetGen(fixed_mesh.mesh)
tets[i] = tetrahedralize(tets[i])
except:
tet_success = False
tet_fail_list.append(i)
enablePrint()
if shell:
blockPrint()
try:
tet_shells[i] = tetrahedralize(tet_shells[i])
except:
try:
fixed_mesh = pymeshfix.MeshFix(vessel_shells[i])
fixed_mesh.repair(verbose=False)
tet_shells[i] = tetgen.TetGen(fixed_mesh.mesh)
tet_shells[i] = tetrahedralize(tet_shells[i])
except:
tet_success = False
tet_fail_shell_list.append(i)
enablePrint()
desc2 = 'Extracting Surfaces'
desc2 = desc2+' '*(40-len(desc2))
surf_success = True
surf_fail_list = []
surf_fail_shell_list = []
for i in tqdm(range(len(tets)),position=0,leave=False,bar_format='{l_bar}{bar:50}{r_bar}{bar:-10b}',desc=desc2):
try:
surf_fix = pymeshfix.MeshFix(tets[i].grid.extract_surface().triangulate())
surf_fix.repair(verbose=False)
surfs.append(surf_fix.mesh)
except:
surf_success = False
surf_fail_list.append(i)
if shell:
try:
surf_shell_fix = pymeshfix.MeshFix(tet_shells[i].grid.extract_surface().triangulate())
surf_shell_fix.repair(verbose=False)
surf_shells.append(surf_shell_fix.mesh)
except:
surf_success = False
surf_fail_shell_list.append(i)
ALL_vessels.append(vessels)
ALL_tets.append(tets)
ALL_surfs.append(surfs)
if shell:
ALL_vessel_shells.append(vessel_shells)
ALL_tet_shells.append(tet_shells)
ALL_surf_shells.append(surf_shells)
unioned = surfs[0].boolean_union(surfs[1])
unioned = unioned.clean()
if shell:
unioned_shell = surf_shells[0].boolean_union(surf_shells[1])
unioned_shell = unioned_shell.clean()
last_unioned_shell = deepcopy(unioned_shell)
desc3 = 'Unioning'
desc3 = desc3+' '*(40-len(desc3))
union_success = True
union_fail_list = []
union_fail_shell_list = []
last_unioned = deepcopy(unioned)
for i in tqdm(range(2,len(surfs)),position=0,leave=False,bar_format='{l_bar}{bar:50}{r_bar}{bar:-10b}',desc=desc3):
try:
unioned = unioned.boolean_union(surfs[i])
unioned = unioned.clean()
last_unioned = deepcopy(unioned)
except:
union_success = False
union_fail_list.append(i)
unioned = last_unioned
if shell:
try:
unioned_shell = unioned_shell.boolean_union(surf_shells[i])
unioned_shell = unioned_shell.clean()
last_unioned_shell = deepcopy(unioned_shell)
except:
union_success = False
union_fail_shell_list.append(i)
unioned_shell = last_unioned_shell
if union_success:
unioned.save(outdir+os.sep+"unioned_solid_network_{}_tree_{}.vtp".format(net_id,tree))
if shell:
unioned_shell.save(outdir+os.sep+"unioned_solid_network_shell_{}_tree_{}.vtp".format(net_id,tree))
else:
unioned.save(outdir+os.sep+"partial_unioned_solid_network_{}_tree_{}.vtp".format(net_id,tree))
if shell:
unioned_shell.save(outdir+os.sep+"unioned_solid_network_shell_{}_tree_{}.vtp".format(net_id,tree))
if tet_success:
print('Tetrahedralize : PASS')
else:
print('Tetrahedralize : Fail ---> {}'.format(tet_fail_list))
if surf_success:
print('Surface Extraction: PASS')
else:
print('Surface Extraction: Fail ---> {}'.format(surf_fail_list))
if tet_success:
print('Union : PASS')
else:
print('Union : Fail ---> {}'.format(union_fail_list))
if shell:
unioned_fix = pymeshfix.MeshFix(unioned)
unioned_fix.repair(verbose=True)
unioned = unioned_fix.mesh
unioned.save(outdir+os.sep+"partial_unioned_solid_network_{}_tree_{}.stl".format(net_id,tree))
unioned_shell_fix = pymeshfix.MeshFix(unioned_shell)
unioned_shell_fix.repair(verbose=True)
unioned_shell = unioned_shell_fix.mesh
unioned_shell.save(outdir+os.sep+"unioned_solid_network_shell_{}_tree_{}.stl".format(net_id,tree))
return ALL_vessels,ALL_tets,ALL_surfs,P,unioned,ALL_vessel_shells,ALL_tet_shells,ALL_surf_shells,unioned_shell
else:
return ALL_vessels,ALL_tets,ALL_surfs,P,unioned
[docs] def export(self,make=False,spline=False,write_splines=False,spline_sample_points=100,steady=True,apply_distal_resistance=False,gui=False):
ALL_POINTS = []
ALL_RADII = []
ALL_NORMALS = []
ALL_INTERP_XYZ = []
ALL_INTERP_RADII = []
ALL_SPLINES = []
CONNECTED_COPY = []
desc1 = 'Extracting Data from Networks'
desc1 = desc1+' '*(40-len(desc1))
for network in tqdm(range(self.number_of_networks),bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}',position=0,leave=False,desc=desc1):
network_points = []
network_radii = []
network_normals = []
network_endpoints = []
network_copy = []
for tr in range(self.trees_per_network[network]):
tree_copy = deepcopy(self.networks[network][tr].data)
tree_copy_terminals = tree_copy[np.argwhere(tree_copy[:,15]==-1).flatten(),:]
tree_copy_terminals = tree_copy_terminals[np.argwhere(tree_copy_terminals[:,16]==-1).flatten(),-1].astype(int)
tree_copy[tree_copy_terminals,3:6] = (tree_copy[tree_copy_terminals,0:3] + tree_copy[tree_copy_terminals,3:6])/2
tree_copy[tree_copy_terminals,20] = tree_copy[tree_copy_terminals,20]/2
max_vessel_id = max(tree_copy[:,-1])
conn_parents = np.argwhere(self.connections[network][tr][:,17] <= max_vessel_id).flatten()
tree_copy[self.connections[network][tr][conn_parents,17].astype(int),15] = self.connections[network][tr][conn_parents,-1]
tree_copy = np.vstack((tree_copy,self.connections[network][tr]))
network_copy.append(tree_copy)
#tmp_tree = tree()
#tmp_tree.data = tree_copy
#tmp_tree.show()
for tr,net in enumerate(network_copy):
update(net,self.networks[network][tr].parameters['gamma'],self.networks[network][tr].parameters['nu'])
update_radii(net,self.networks[network][tr].parameters['Pperm'],self.networks[network][tr].parameters['Pterm'])
#tmp_tree = tree()
#tmp_tree.data = net
#tmp_tree.show()
#P = self.show()
total = self.trees_per_network[network]
desc2 = 'Extracting Data from Network {} Trees'.format(network)
desc2 = desc2+' '*(40-len(desc2))
with tqdm(total=total,bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}',position=1,leave=False,desc=desc2) as pbar_tree:
for tree_copy in network_copy:
interp_xyz,interp_r,interp_n,frames,branches,interp_xyzr = get_interpolated_sv_data(tree_copy)
points,radii,normals = sv_data(interp_xyzr,interp_r,radius_buffer=0.01)
#for p_list in points:
# P.add_points(np.array(p_list),render_points_as_spheres=True,color='g',point_size=30)
network_points.append(points)
network_radii.append(radii)
network_normals.append(normals)
pts = [p[-1] for p in points]
network_endpoints.append(pts)
#P.show()
pbar_tree.update(1)
CONNECTED_COPY.append(network_copy)
# Calculate Vessel Reordering relative to the first vessel of the network
order = [list(range(len(network_endpoints[0])))]
for tree in range(1,self.trees_per_network[network]):
tmp = []
for idx in order[0]:
#tmp.append(np.argwhere(np.all(np.isclose(e[0][0],network_endpoints[]),axis=1)).flatten()[0])
tmp.append(network_endpoints[tree].index(network_endpoints[0][idx]))
order.append(tmp)
# Reorder Vessels
reordered_points = []
reordered_radii = []
reordered_normals = []
for tree in range(self.trees_per_network[network]):
tmp_points = []
tmp_radii = []
tmp_normals = []
for idx in order[tree]:
tmp_points.append(network_points[tree][idx])
tmp_radii.append(network_radii[tree][idx])
tmp_normals.append(network_normals[tree][idx])
reordered_points.append(tmp_points)
reordered_radii.append(tmp_radii)
reordered_normals.append(tmp_normals)
#print('reorder: {}'.format(len(reordered_points[-1])))
# Reverse and Combine
final_points = [reordered_points[0]]
final_radii = [reordered_radii[0]]
final_normals = [reordered_normals[0]]
final_interp_xyz = []
final_interp_radii = []
for tree in range(1,self.trees_per_network[network]):
if tree == 1:
for vessel in range(len(reordered_points[tree])):
vessel_normals_flip = np.array(reordered_normals[tree][vessel])*-1
reordered_normals[tree][vessel] = vessel_normals_flip.tolist()
final_points[0][vessel].extend(list(reversed(reordered_points[tree][vessel]))[2:]) #changed
final_radii[0][vessel].extend(list(reversed(reordered_radii[tree][vessel]))[2:]) #changed
final_normals[0][vessel].extend(list(reversed(reordered_normals[tree][vessel]))[2:]) #changed 9-8-22
#make the connecting contours have equal average radii
else:
tmp_points = []
tmp_radii = []
tmp_normals = []
for vessel in range(len(reordered_points[tree])):
tmp_points.append(list(reversed(reordered_points[tree][vessel])))
tmp_radii.append(list(reversed(reordered_radii[tree][vessel])))
tmp_normals.append(list(reversed(reordered_normals[tree][vessel])))
# make sure the radii is less than the average radii of the first vessel
final_points.append(tmp_points)
final_radii.append(tmp_radii)
final_normals.append(tmp_normals)
interp_xyz = []
interp_r = []
for vessel in range(len(final_points[-1])):
pass
#P.add_points(np.array(final_points[-1][vessel]),render_points_as_spheres=True,color='g',point_size=30)
#interp_xyz.append(splprep(np.array(final_points[-1][vessel]).T,s=0))
#r = np.vstack((interp_xyz[-1][1],np.array(final_radii[-1][vessel]).T))
#interp_r.append(splprep(r,s=0))
final_interp_xyz.append(interp_xyz)
final_interp_radii.append(interp_r)
#P.show()
ALL_POINTS.append(final_points)
ALL_RADII.append(final_radii)
ALL_NORMALS.append(final_normals)
ALL_INTERP_XYZ.append(final_interp_xyz)
ALL_INTERP_RADII.append(final_interp_radii)
self.ALL_POINTS = ALL_POINTS
self.ALL_RADII = ALL_RADII
# Export Components of Network to SV Builder for Code Generation
if make:
for component in range(len(final_points)):
if steady:
time = [0, 1]
flow = [self.networks[network][0].data[0,22], self.networks[network][0].data[0,22]]
else:
time,flow = wave(self.networks[network][0].data[0,22],self.networks[network][0].data[0,21]*2) # changed wave function
time = time.tolist()
flow = flow.tolist()
flow[-1] = flow[0]
if apply_distal_resistance:
R = self.networks[network][0].parameters['Pterm']/self.networks[network][0].data[0,22]
else:
R = 0
num_caps = self.trees_per_network[network]
options = file_options(num_caps,time=time,flow=flow,gui=gui,distal_resistance=R)
build(final_points[component],final_radii[component],final_normals[component],options)
if spline:
for network in range(len(ALL_POINTS)):
network_splines = []
for tree in range(len(ALL_POINTS[network])):
for vessel in range(len(ALL_POINTS[network][tree])):
pt_array = np.array(ALL_POINTS[network][tree][vessel])
r_array = np.array(ALL_RADII[network][tree][vessel]).reshape(-1,1)
pt_r_combined = deepcopy(np.hstack((pt_array,r_array)).T)
print(pt_r_combined.shape)
vessel_ctr = splprep(pt_r_combined,s=0)
vessel_spline = lambda t: splev(t,vessel_ctr[0])
network_splines.append(deepcopy(vessel_spline))
if write_splines:
spline_file = open(os.getcwd()+os.sep+"network_{}_b_splines.txt".format(network),"w+")
for j,vessel_spline in enumerate(network_splines):
spline_file.write('Vessel: {}, Number of Points: {}\n\n'.format(j,spline_sample_points))
t = np.linspace(0,1,num=spline_sample_points)
data = deepcopy(vessel_spline(t))
print(data[0][0])
for k in range(spline_sample_points):
spline_file.write('{}, {}, {}, {}\n'.format(data[0][k],data[1][k],data[2][k],data[3][k]))
spline_file.write('\n')
spline_file.close()
ALL_SPLINES.append(network_splines)
return final_points,final_radii,final_normals,CONNECTED_COPY,ALL_INTERP_XYZ,ALL_INTERP_RADII,ALL_SPLINES
[docs]def perfusion_territory(tree,subdivisions=0,mesh_file=None,tree_file=None):
terminals = tree.data[tree.data[:,15]<0,:]
terminals = terminals[terminals[:,16]<0,:]
territory_id = []
territory_volumes = np.zeros(len(terminals))
vol = tree.boundary.tet.grid.compute_cell_sizes().cell_data['Volume']
#for idx, cell_center in tqdm(enumerate(tree.boundary.tet.grid.cell_centers().points),desc='Calculating Perfusion Territories'):
for idx, cell_center in enumerate(tree.boundary.tet.grid.cell_centers().points):
closest_terminal = np.argmin(np.linalg.norm(cell_center.reshape(1,-1)-terminals[:,3:6],axis=1))
territory_id.append(closest_terminal)
territory_volumes[closest_terminal] += vol[idx]
territory_id = np.array(territory_id)
tree.boundary.tet.grid['perfusion_territory_id'] = territory_id
if mesh_file is not None:
tree.boundary.tet.grid.save(mesh_file)
else:
tree.boundary.tet.grid.save('example.vtu')
models = tree.show(show=False)
append = vtk.vtkAppendPolyData()
#append.UserManagedInputsOn()
#append.SetInputDataObject(models[0])
#append.SetNumberOfInputs(len(models))
for i in range(len(models)):
append.AddInputData(models[i].GetOutput())
append.Update()
total_tree = append.GetOutput()
writer = vtk.vtkXMLPolyDataWriter()
if tree_file is not None:
writer.SetFileName(tree_file)
else:
writer.SetFileName(os.getcwd()+os.sep+'example_tree.vtp')
writer.SetInputDataObject(total_tree)
writer.Update()
return territory_id,territory_volumes/np.sum(territory_volumes)