def get_stent_likely_positions(data, th): """ Get a pointset of positions that are likely to be on the stent. If the given data has a "sampling" attribute, the positions are scaled accordingly. Detection goes according to three criteria: * intensity above given threshold * local maximum * at least one neighbour with intensity above threshold """ # Get mask mask = get_mask_with_stent_likely_positions(data, th) # Convert mask to points indices = np.where(mask == 2) # Tuple of 1D arrays pp = PointSet(np.column_stack(reversed(indices)), dtype=np.float32) # Correct for anisotropy and offset if hasattr(data, 'sampling'): pp *= PointSet(list(reversed(data.sampling))) if hasattr(data, 'origin'): pp += PointSet(list(reversed(data.origin))) return pp
def pp_to_graph(pp, type='oneEdge'): """ PointSet to graph with points connected with edges or as one edge. Returns graph. Can be used for centerline output. """ from stentseg.stentdirect import stentgraph graph = stentgraph.StentGraph() if type == 'oneEdge': # add single nodes for p in pp: p_as_tuple = tuple(p.flat) graph.add_node(p_as_tuple) # add one path of pp pstart = p_as_tuple pend = tuple(pp[-1].flat) graph.add_edge(pstart, pend, path=pp) else: for i, p in enumerate(pp[:-1]): n1 = tuple(p.flat) n2 = tuple(pp[i + 1].flat) # create path between nodes as PointSet path = PointSet(3, dtype=np.float32) for p in [n1, n2]: path.append(p) graph.add_edge(n1, n2, path=path) return graph
def plot_points(pp, mc='g', ms='o', mw=8, alpha=0.5, ls='', ax=None, **kwargs): """ Plot a point or set of points in current axis and restore current view alpha 0.9 = solid; 0.1 transparant """ if ax is None: ax = vv.gca() # check if pp is 1 point and not a PointSet if not isinstance(pp, PointSet): pp = np.asarray(pp) if pp.ndim == 1: p = PointSet(3) p.append(pp) pp = p # get view and plot view = ax.GetView() point = vv.plot(PointSet(pp), mc=mc, ms=ms, mw=mw, ls=ls, alpha=alpha, axes=ax, **kwargs) ax.SetView(view) return point
def get_stent_orientation(R1, R2): R1, R2 = np.asarray(R1), np.asarray(R2) R1, R2 = PointSet(R1), PointSet(R2) # turn array ndim2 into PointSet R1_ant, R1_post, R1_left, R1_right = R1[0], R1[1], R1[2], R1[3] R2_ant, R2_post, R2_left, R2_right = R2[0], R2[1], R2[2], R2[3] refvector = [0, 0, 10] # z-axis angle = (R1_ant - R2_ant).angle(refvector) # order does not matter
def convert_paths_to_PointSet(g): """ for centerline graphs that were created with paths as list """ from stentseg.utils import PointSet import numpy as np for n1, n2 in g.edges(): path2 = PointSet(3, dtype=np.float32) # Obtain path of edge path = g.edge[n1][n2]['path'] for p in path: path2.append(tuple(p.flat)) g.edge[n1][n2]['path'] = path2.copy()
def point_in_pointcloud_closest_to_p(pp, point): """ Find point in PointSet which is closest to a point Returns a PointSet with point found in PointSet and point """ vecs = pp - point dists_to_pp = ((vecs[:, 0]**2 + vecs[:, 1]**2 + vecs[:, 2]**2)**0.5).reshape(-1, 1) pp_index = list(dists_to_pp).index(dists_to_pp.min()) # index on path pp_point = pp[pp_index] p_in_pp_and_point = PointSet(3, dtype='float32') [p_in_pp_and_point.append(*p) for p in (pp_point, point)] return p_in_pp_and_point
def _find_better_pos3(pp, pos, vec, step, n): """ In 3D we search recursively left/right, forward/backward. Maybe this implementation could instead do a circle fit. """ stub1, stub2 = PointSet([1, 1, 1]), PointSet([1, 0, 1]) v1 = vec.cross(stub1) v1 = v1 if v1.norm() > 0 else vec.cross(stub2) v2 = vec.cross(v1) v1, v2 = v1.normalize() * step, v2.normalize() * step measure = _distance_measure(pp, pos, n) # for loops are like while loops with a failsafe for i in range(5): curpos = pos for i in range(30): new_pos = pos + v1 new_measure = _distance_measure(pp, new_pos, n) if new_measure <= measure: break pos, measure = new_pos, new_measure for i in range(30): new_pos = pos - v1 new_measure = _distance_measure(pp, new_pos, n) if new_measure <= measure: break pos, measure = new_pos, new_measure for i in range(30): new_pos = pos + v2 new_measure = _distance_measure(pp, new_pos, n) if new_measure <= measure: break pos, measure = new_pos, new_measure for i in range(30): new_pos = pos - v2 new_measure = _distance_measure(pp, new_pos, n) if new_measure <= measure: break pos, measure = new_pos, new_measure if (pos == curpos).all(): break return pos
def get_picked_seed(data, label): """ Picked point (SHIFT+r-click) is converted to world coordinates as in Step1 Input: volume data, label """ from stentseg.utils import PointSet coord = label2volindices(label) # [x,y,z] p = PointSet(coord, dtype=np.float32) # Correct for anisotropy and offset if hasattr(data, 'sampling'): p *= PointSet(list(reversed(data.sampling))) if hasattr(data, 'origin'): p += PointSet(list(reversed(data.origin))) return list(p.flat)
def points_from_nodes_in_graph(graph): """ """ # Create set of tuples to remove duplicates pp = set(tuple(p) for p in graph.nodes()) # Turn into a pointset return PointSet(np.array(list(pp)))
def measure_centerline_strain(): """ Measure the centerline strain. """ i1 = slider_ref.value i2 = slider_ves.value # Get the centerline section of interest index1 = int(np.ceil(i1)) index2 = int(np.floor(i2)) section = centerline[index1:index2 + 1] # get this section of the centerline for each phase sections = [] for phase in range(len(deforms)): deform = deforms[phase] dx = deform.get_field_in_points(section, 0) dy = deform.get_field_in_points(section, 1) dz = deform.get_field_in_points(section, 2) deform_vectors = PointSet(np.stack([dx, dy, dz], 1)) sections.append(section + deform_vectors) # Measure the strain of the full section, by measuring the total length in each phase. lengths = [] for phase in range(len(deforms)): section = sections[phase] length = sum( float(section[i].distance(section[i + 1])) for i in range(len(section) - 1)) lengths.append(length) if min(lengths) == 0: return 0 else: # Strain as delta-length divided by initial length return (max(lengths) - min(lengths)) / min(lengths)
def get_midpoints_peaksvalleys(model): """ Get midpoints near the peaks and valleys """ # remove hooks, pop nodes model_hooks1, model_noHooks1 = _get_model_hooks(model) stentgraph.pop_nodes(model_noHooks1) midpoints_peaks_valleys = PointSet(3) for n1, n2 in sorted(model_noHooks1.edges()): if stentgraph._edge_length(model_noHooks1, n1, n2) < 10: # in mm # get midpoint for edges near struts mid = (n1[0] + n2[0]) / 2, (n1[1] + n2[1]) / 2, (n1[2] + n2[2]) / 2 path = model_noHooks1.edge[n1][n2]['path'] mid_and_pathpoint = point_in_pointcloud_closest_to_p(path, mid) midpoints_peaks_valleys.append( mid_and_pathpoint[0]) # append pp_point return midpoints_peaks_valleys
def points_from_mesh(mesh, invertZ=True): """ Create a point cloud (represented as a PointSet) from a visvis mesh object, or from a filename pointing to a .stl or .obj file. """ if isinstance(mesh, str): mesh = vv.meshRead(mesh) if invertZ == True: for vertice in mesh._vertices: vertice[-1] = vertice[-1] * -1 # Create set of tuples to remove duplicates pp = set(tuple(p) for p in mesh._vertices) # Turn into a pointset return PointSet(np.array(list(pp)))
def points_from_edges_in_graph(graph, type='order'): """ Get all points on the paths in the graph, return as list with PointSets per path. If type='order' return in correct order of path points; there may be duplicates between edges, not within a selfloop edge. If type = 'noduplicates' return in random order but without duplicate points. """ paths_as_pp = [] for n1, n2 in graph.edges(): path = graph.edge[n1][n2]['path'] # points on path include n1 n2 if type == 'noduplicates': # Create set of tuples to remove duplicates; but order is lost! path = np.asarray(path) pp = set(tuple(p) for p in path) # turn into a pointset path_as_pp = PointSet(np.array(list(pp))) elif type == 'order': if path[0].all() == path[-1].all(): path = path[1:] # remove the duplicate (in selfloop) path_as_pp = PointSet(path) paths_as_pp.append(path_as_pp) return paths_as_pp
def get_isosurface3d(vol, threshold=300, showsurface=False): """ Generate isosurface to return the vertices (surface_points), based on HU threshold Vertices are scaled using vol.sampling and vol.origin return: verts, faces, pp_verts, mesh """ from skimage import measure, morphology from mpl_toolkits.mplot3d.art3d import Poly3DCollection verts, faces = measure.marching_cubes(vol, threshold) # verts in z,y,x #verts, faces, normals, values = measure.marching_cubes_lewiner(vol,level=threshold) #version after 0.12 returns verts, faces, normals, values vertpoints = copy.deepcopy(verts) # set scale and origin vertpoints[:, 0] *= vol.sampling[0] # sampling z,y,x vertpoints[:, 1] *= vol.sampling[1] vertpoints[:, 2] *= vol.sampling[2] vertpoints[:, 0] += vol.origin[0] vertpoints[:, 1] += vol.origin[1] vertpoints[:, 2] += vol.origin[2] pp = vertpoints.copy() # pp are the surfacepoints pp[:, 0] = vertpoints[:, -1] # to x y x pp[:, -1] = vertpoints[:, 0] # to x y z verts = copy.deepcopy( pp) # vertice points xyz scaled and set to vol origin pp_verts = PointSet(pp) if showsurface: # Fancy indexing: `verts[faces]` to generate a collection of triangles mesh = Poly3DCollection(verts[faces], alpha=0.1) face_color = [0, 0, 1] mesh.set_facecolor(face_color) fig = plt.figure(figsize=(15, 15)) ax = fig.add_subplot(111, projection='3d') ax.add_collection3d(mesh) ax.set_xlim(0, vol.shape[0]) ax.set_ylim(0, vol.shape[1]) ax.set_zlim(0, vol.shape[2]) plt.show() else: mesh = None return verts, faces, pp_verts, mesh
def get_vessel_points_from_plane_points(pp): """ Select points from the vessel points that are very close to the plane defined by the given plane points. Returns a 2D and a 3D point set. """ abcd = fitting.fit_plane(pp) # Get 2d and 3d coordinates of points that lie (almost) on the plane # pp2 = fitting.project_to_plane(ppvessel, abcd) # pp3 = fitting.project_from_plane(pp2, abcd) signed_distances = fitting.signed_distance_to_plane(ppvessel, abcd) distances = np.abs(signed_distances) # Select points to consider. This is just to reduce the search space somewhat. selection = np.where(distances < 5)[0] # We assume that each tree points in ppvessel makes up a triangle (face) # We make sure of that when we load the mesh. # Select first index of each face (every 3 vertices is 1 face), and remove duplicates selection_faces = set(3 * (selection // 3)) # Now iterate over the faces (triangles), and check each edge. If the two # points are on different sides of the plane, then we interpolate on the # edge to get the exact spot where the edge intersects the plane. sampled_pp3 = PointSet(3) visited_edges = set() for fi in selection_faces: # for each face index for edge in [(fi + 0, fi + 1), (fi + 0, fi + 2), (fi + 1, fi + 2)]: if signed_distances[edge[0]] * signed_distances[edge[1]] < 0: if edge not in visited_edges: visited_edges.add(edge) d1, d2 = distances[edge[0]], distances[edge[1]] w1, w2 = d2 / (d1 + d2), d1 / (d1 + d2) p = w1 * ppvessel[edge[0]] + w2 * ppvessel[edge[1]] sampled_pp3.append(p) return fitting.project_to_plane(sampled_pp3, abcd), sampled_pp3
def get_plane_points_from_centerline_index(i): """ Get a set of points that lie on the plane orthogonal to the centerline at the given index. The points are such that they can be drawn as a line for visualization purposes. The plane equation can be obtained via a plane-fit. """ if True: # Cubic fit of the centerline i = max(1.1, min(i, centerline.shape[0] - 2.11)) # Sample center point and two points right below/above, using # "cardinal" interpolating (C1-continuous), or "basic" approximating (C2-continious). pp = [] for j in [i - 0.1, i, i + 0.1]: index = int(j) t = j - index coefs = pirt.interp.get_cubic_spline_coefs(t, "basic") samples = centerline[index - 1], centerline[index], centerline[ index + 1], centerline[index + 2] pp.append(samples[0] * coefs[0] + samples[1] * coefs[1] + samples[2] * coefs[2] + samples[3] * coefs[3]) # Get center point and vector pointing down the centerline p = pp[1] vec1 = (pp[2] - pp[1]).normalize() else: # Linear fit of the centerline i = max(0, min(i, centerline.shape[0] - 2)) index = int(i) t = i - index # Sample two points of interest pa, pb = centerline[index], centerline[index + 1] # Get center point and vector pointing down the centerline p = t * pb + (1 - t) * pa vec1 = (pb - pa).normalize() # Get two orthogonal vectors that define the plane that is orthogonal # to the above vector. We can use an arbitrary vector to get the first, # but there is a tiiiiiny chance that it is equal to vec1 so that the # normal collapese. vec2 = vec1.cross([0, 1, 0]) if vec2.norm() == 0: vec2 = vec1.cross((1, 0, 0)) vec3 = vec1.cross(vec2) # Sample some point on the plane and get the plane's equation pp = PointSet(3) radius = 6 pp.append(p) for t in np.linspace(0, 2 * np.pi, 12): pp.append(p + np.sin(t) * radius * vec2 + np.cos(t) * radius * vec3) return pp
def deform_points_2d(pp2, plane): """ Given a 2D pointset (and the plane that they are on), return a list with the deformed versions of that pointset. """ pp3 = fitting.project_from_plane( pp2, plane) #todo: shouldn't origin be subtracted?! see dynamic.py deformed = [] for phase in range(len(deforms)): deform = deforms[phase] dx = deform.get_field_in_points( pp3, 0 ) #todo: shouldn't this be z=0 y=1 x=2! see dynamic.py; adapt in all functions?! dy = deform.get_field_in_points(pp3, 1) dz = deform.get_field_in_points(pp3, 2) deform_vectors = PointSet(np.stack([dx, dy, dz], 1)) pp3_deformed = pp3 + deform_vectors deformed.append(fitting.project_to_plane(pp3_deformed, plane)) return deformed
def on_key(event): if event.key == vv.KEY_CONTROL: global nr coordinates = np.asarray(label2worldcoordinates(label), dtype=np.float32) # x,y,z n2 = tuple(coordinates.flat) sd2._nodes1.add_node(n2, number=nr) print(nr) if nr > 0: for n in list(sd2._nodes1.nodes()): if sd2._nodes1.node[n]['number'] == nr - 1: path = [n2, n] sd2._nodes1.add_edge(n2, n, path=PointSet(np.row_stack(path))) sd2._nodes1.Draw(mc='r', mw=10, lc='y') nr += 1 if event.key == vv.KEY_ENTER: sd2._graphrefined = sd2._RefinePositions(sd2._nodes1) sd2._graphrefined.Draw(mc='b', mw=10, lc='g') if event.key == vv.KEY_ESCAPE: # Store to EXCEL pp1 = [] try: pp1 = sd2._graphrefined.nodes() pp1.sort(key=lambda x: sd2._graphrefined.node[x]['number'] ) # sort nodes by click number # pp1 = sd2._graphrefined.nodes() print('*** refined manual picked were stored ***') except AttributeError: pp1 = sd2._nodes1.nodes() pp1.sort(key=lambda x: sd2._nodes1.node[x]['number'] ) # sort nodes by click number print('*** manual picked were stored ***') pp1 = np.asarray(pp1) storeCoordinatesToExcel(pp1, exceldir) print('---stored to excel {} ---'.format(exceldir)) print('---model can be stored as ssdf in do_segmentation---')
regsteps=params.regsteps, verbose=False) # do not use first point, as they are influenced by user selected points centerline1 = centerline1[1:-1] # smooth centerline centerline2 = smooth_centerline(centerline1, 20) centerline_nodes = pp_to_graph(centerline2) f = vv.figure(1) vv.clf() f.position = 709.00, 30.00, 1203.00, 1008.00 a1 = vv.subplot(121) vv.plot(pp, ms='.', ls='', alpha=0.2, mw=7) # stent seed points vv.plot(PointSet(list(start1)), ms='.', ls='', mc='g', mw=18) # start1 vv.plot(PointSet(list(end1)), ms='.', ls='', mc='m', mw=16) vv.plot(centerline1, ms='.', ls='', mw=8, mc='c') vv.plot(centerline2, ms='.', ls='', mw=8, mc='y') # smoothed a1.axis.visible = showAxis a1.daspect = 1, 1, -1 a2 = vv.subplot(122) show_ctvolume(s.vol, None, showVol=showVol, clim=clim0, isoTh=isoTh, removeStent=False) label = pick3d(vv.gca(), s.vol) vv.plot(PointSet(list(start1)), ms='.', ls='', mc='g', mw=18) # start1
showAxis = True # True or False showVol = 'ISO' # MIP or ISO or 2D or None ringpart = True # True; False clim0 = (0, 2500) # clim0 = -550,500 isoTh = 250 meshradius = 0.7 # create class object for excel analysis foo = ExcelAnalysis() # excel locations initialized in class ## Renal origin coordinates: input by user/read excel # coordinates, left and right most caudal renal # ctcode1 xrenal1, yrenal1, zrenal1 = 132.7, 89.2, 85.5 renal1 = PointSet(list((xrenal1, yrenal1, zrenal1))) # ctcode2 if ctcode2: xrenal2, yrenal2, zrenal2 = 171, 165.1, 39.5 renal2 = PointSet(list((xrenal2, yrenal2, zrenal2))) # renal_left, renal_right = foo.readRenalsExcel(sheet_renals_obs, ptcode, ctcode1) # renal1 = renal_left ## Load (dynamic) stent models, vessel, ct # Load static CT image to add as reference s = loadvol(basedir, ptcode, ctcode1, cropname, 'avgreg') vol1 = s.vol if ctcode2: s = loadvol(basedir, ptcode, ctcode2, cropname, 'avgreg') vol2 = s.vol
def find_centerline(pp, start, ends, step, *, substep=None, ndist=20, regfactor=0.2, regsteps=10, verbose=False): """ Find path from a start point to an end-point or set of end-points. Starting from start, will try to find a route to the closest of the given end-points, while maximizing the distance to the points in pp. Works on 2D and 3D points. Arguments: pp (Pointset): points to stay away from. Typically points on the structure that we are trying to find a centerline for. start (tuple, PointSet): Point or tuple indicating the start position. ends (tuple, list, Pointset): Point or points indicating end positions. step (float): the stepsize of points along the centerline. substep (float, optional): the stepsize for the candiate positions. Default is 0.2 of step. ndist (int): the number of closest points to take into account in the distance measurements. Default 20. regfactor (float): regularization factor between 0 and 1. Default 0.2. regsteps (float): Distance in number of steps from start/end at which there is no additional "endpoint" regularization. Default 10. verbose (bool): if True, print info. Returns a Pointset with points that are each step distance from each-other (except for the last step). The first point matches the start position and the last point matches one of the end positions. """ substep = substep or step * 0.2 # Check pp and ndim if not isinstance(pp, PointSet): raise ValueError('pp must be a pointset') dims = pp.shape[1] if dims not in (2, 3): raise ValueError('find_centerline() only works on 2D or 3D data') # Prepare start start = np.array(start) start.shape = -1, dims start = PointSet(start) assert start.shape[0] == 1 assert start.shape[1] == dims # Prepare ends ends = np.array(ends) ends.shape = -1, dims ends = PointSet(ends) assert ends.shape[1] == dims # Init centerline = PointSet(dims) pos = start # Prepare for first iter centerline.append(pos) end = _closest_point(ends, pos) vec = (end - pos).normalize() * step n_estimate = (end - pos).norm() / step i = 0 while pos.distance(end) > step: i += 1 measure = _distance_measure(pp, pos, ndist) #if i > n_estimate * 2 or measure > 250: # * 4: if i > n_estimate * 2 or measure > 60: # return sooner print('i={} and n_estimate={}; measure={}'.format( i, n_estimate, measure)) print('We seem to be lost. Centerline is returned') return centerline # raise RuntimeError('We seem to be lost') if verbose: print('iter %i, distance %1.1f of %1.1f' % (i, pos.distance(end), start.distance(end))) # Estimated position and refine using distance measure # Step towards end-pos, but refine by looking orthogonal to real direction pos1 = pos + vec pos2 = _find_better_pos(pp, pos1, vec, substep, ndist) # Calculate damp factor to direct regularization towards start-end direction reg_damper = 1.0 - float(min(pos.distance(end), pos1.distance(start))) / (regsteps * step) reg_damper = min(1.0, max(reg_damper, 0)) # Combine original estimate with distance-based position -> regularization reg = min(1, regfactor + reg_damper) refpos = reg * pos1 + (1 - reg) * pos2 # Rescale so we take equally sized steps vec_real = (refpos - pos).normalize() pos = pos + vec_real * step # Store and prepare for next step centerline.append(pos) end = _closest_point(ends, pos) vec = ((1 - reg_damper) * vec_real + (end - pos).normalize()).normalize() * step # Wrap up centerline.append(end) return centerline
ppmodelvessel = points_from_nodes_in_graph(s_modelvessel.model) s_model = loadmodel(basedir, ptcode, ctcode, 'prox', modelname='modelavgreg') ppmodel = points_from_nodes_in_graph(s_model.model) # Visualize AC.a.MakeCurrent() vv.cla() # clear vol otherwise plots not visible somehow # seedpoints segmentations vv.plot(ppmodelvessel, ms='.', ls='', alpha=0.6, mw=2) vv.plot(ppmodel, ms='.', ls='', alpha=0.6, mw=2) # stents for j in range(len(stentsStartPoints)): vv.plot(PointSet(list(stentsStartPoints[j])), ms='.', ls='', mc='g', mw=20) # startpoint green vv.plot(PointSet(list(stentsEndPoints[j])), ms='.', ls='', mc='r', mw=20) # endpoint red for j in range(len(allcenterlines)): vv.plot(allcenterlines[j], ms='.', ls='', mw=10, mc='y') # vessels for j in range(len(vesselStartPoints)): vv.plot(PointSet(list(vesselStartPoints[j])), ms='.', ls='', mc='g', mw=20) # startpoint green vv.plot(PointSet(list(vesselEndPoints[j])), ms='.', ls='', mc='r', mw=20) # endpoint red for j in range(len(allcenterlinesv)): vv.plot(allcenterlinesv[j], ms='.', ls='', mw=10, mc='y') vv.title('Centerlines and seed points')
import os from stentseg.utils import PointSet from stentseg.utils.centerline import find_centerline, points_from_mesh, smooth_centerline, pp_to_graph from stentseg.utils.datahandling import select_dir, loadvol, loadmodel from stentseg.utils.visualization import show_ctvolume from stentseg.stentdirect import stentgraph TEST = 14 if TEST == 1: import imageio im = imageio.imread('~/Desktop/test_centerline.png')[:, :, 0] y, x = np.where(im < 200) pp = PointSet(np.column_stack([x, y])) start = (260, 60) ends = [(230, 510), (260, 510), (360, 510)] centerline = find_centerline(pp, start, ends, 8, ndist=20, regfactor=0.2, regsteps=10) vv.figure(1) vv.clf() a1 = vv.subplot(111) vv.plot(pp, ms='.', ls='')
def take_measurements(measure_volume_change): """ This gets called when the slider is releases. We take measurements and update the corresponding texts and visualizations. """ # Get points that form the contour of the vessel in 2D pp = get_plane_points_from_centerline_index(slider_ves.value) pp2, pp3 = get_vessel_points_from_plane_points(pp) plane = pp2.plane # Collect measurements in a dict. That way we can process it in one step at the end measurements = {} # Store slider positions, so we can reproduce this measurement later measurements["centerline indices"] = slider_ref.value, slider_ves.value # Early exit? if len(pp2) == 0: line_2d.SetPoints(pp2) line_ellipse1.SetPoints(pp2) line_ellipse2.SetPoints(pp2) vesselVisMesh2.SetFaces(np.zeros((3, 3), np.int32)) vesselVisMesh2.SetNormals(None) process_measurements(measurements) return # Measure length of selected part of the centerline and the strain in that section measurements["centerline distance"] = get_distance_along_centerline() measurements["centerline strain"] = measure_centerline_strain() # Measure centerline curvature curvature_mean, curvature_max, curvature_max_pos, curvature_max_change = measure_curvature( centerline, deforms) measurements["curvature mean"] = DeformInfo(curvature_mean) measurements["curvature max"] = DeformInfo(curvature_max) measurements["curvature max pos"] = DeformInfo(curvature_max_pos) measurements["curvature max change"] = curvature_max_change # Get ellipse and its center point ellipse = fitting.fit_ellipse(pp2) p0 = PointSet([ellipse[0], ellipse[1]]) # Sample ellipse to calculate its area pp_ellipse = fitting.sample_ellipse(ellipse, 256) # results in N + 1 points area = 0 for i in range(len(pp_ellipse) - 1): area += triangle_area(p0, pp_ellipse[i], pp_ellipse[i + 1]) # measurements["reference area"] = float(area) # Do a quick check to be sure that this triangle-approximation is close enough assert abs(area - fitting.area(ellipse) ) < 2, "area mismatch" # mm2 typically ~ 0.1 mm2 # Measure ellipse area (and how it changes) measurements["ellipse area"] = DeformInfo(unit="mm2") for pp_ellipse_def in deform_points_2d(pp_ellipse, plane): area = 0 for i in range(len(pp_ellipse_def) - 1): area += triangle_area(p0, pp_ellipse_def[i], pp_ellipse_def[i + 1]) measurements["ellipse area"].append(area) # # Measure expansion of ellipse in 256 locations? # # Measure distances from center to ellipse edge. We first get the distances # # in each face, for each point. Then we aggregate these distances to # # expansion measures. So in the end we have 256 expansion measures. # distances_per_point = [[] for i in range(len(pp_ellipse))] # for pp_ellipse_def in deform_points_2d(pp_ellipse, plane): # # todo: Should distance be measured to p0 or to p0 in that phase? # for i, d in enumerate(pp_ellipse_def.distance(p0)): # distances_per_point[i].append(float(d)) # distances_per_point = distances_per_point[:-1] # Because pp_ellipse[-1] == pp_ellipse[0] # # # measurements["expansions"] = DeformInfo() # 256 values, not 10 # for i in range(len(distances_per_point)): # distances = distances_per_point[i] # measurements["expansions"].append((max(distances) - min(distances)) / min(distances)) # Measure radii of ellipse major and minor axis (and how it changes) pp_ellipse4 = fitting.sample_ellipse(ellipse, 4) # major, minor, major, minor measurements["ellipse expansion major1"] = DeformInfo(unit="mm") measurements["ellipse expansion minor1"] = DeformInfo(unit="mm") measurements["ellipse expansion major2"] = DeformInfo(unit="mm") measurements["ellipse expansion minor2"] = DeformInfo(unit="mm") for pp_ellipse4_def in deform_points_2d(pp_ellipse4, plane): measurements["ellipse expansion major1"].append( float(pp_ellipse4_def[0].distance(p0))) measurements["ellipse expansion minor1"].append( float(pp_ellipse4_def[1].distance(p0))) measurements["ellipse expansion major2"].append( float(pp_ellipse4_def[2].distance(p0))) measurements["ellipse expansion minor2"].append( float(pp_ellipse4_def[3].distance(p0))) # Measure how the volume changes - THIS BIT IS COMPUTATIONALLY EXPENSIVE submesh = meshlib.Mesh(np.zeros((3, 3))) if measure_volume_change: # Update the submesh plane1 = fitting.fit_plane( get_plane_points_from_centerline_index(slider_ref.value)) plane2 = fitting.fit_plane( get_plane_points_from_centerline_index(slider_ves.value)) plane2 = [-x for x in plane2] # flip the plane upside doen submesh = vesselMesh.cut_plane(plane1).cut_plane(plane2) # Measure its motion measurements["volume"] = DeformInfo(unit="mm3") submesh._ori_vertices = submesh._vertices.copy() for phase in range(len(deforms)): deform = deforms[phase] submesh._vertices = submesh._ori_vertices.copy() dx = deform.get_field_in_points(submesh._vertices, 0) dy = deform.get_field_in_points(submesh._vertices, 1) dz = deform.get_field_in_points(submesh._vertices, 2) submesh._vertices[:, 0] += dx submesh._vertices[:, 1] += dy submesh._vertices[:, 2] += dz measurements["volume"].append(submesh.volume()) # Show measurements process_measurements(measurements) # Update line objects line_2d.SetPoints(pp2) line_ellipse1.SetPoints(fitting.sample_ellipse(ellipse)) major_minor = PointSet(2) for p in [ p0, pp_ellipse4[0], p0, pp_ellipse4[2], p0, pp_ellipse4[1], p0, pp_ellipse4[3] ]: major_minor.append(p) line_ellipse2.SetPoints(major_minor) axes2.SetLimits(margin=0.12) # Update submesh object vertices, faces = submesh.get_vertices_and_faces() vesselVisMesh2.SetVertices(vertices) vesselVisMesh2.SetFaces( np.zeros((3, 3), np.int32) if len(faces) == 0 else faces) vesselVisMesh2.SetNormals(None)
deforms = [pirt.DeformationFieldBackward(*fields) for fields in deforms] # Load vessel mesh (mimics) # We make sure that it is a mesh without faces, which makes our sampling easier try: ppvessel except NameError: # Load mesh with visvis, then put in our meshlib.Mesh() and let it ensure that # the mesh is closed, check the winding, etc. so that we can cut it with planes, # and reliably calculate volume. filename = '{}_{}_neck.stl'.format(ptcode, ctcode1) vesselMesh = loadmesh(basedirMesh, ptcode[-3:], filename) #inverts Z vv.processing.unwindFaces(vesselMesh) vesselMesh = meshlib.Mesh(vesselMesh._vertices) vesselMesh.ensure_closed() ppvessel = PointSet(vesselMesh.get_flat_vertices()) # Must be flat! # Load ring model try: modelmesh1 except NameError: s1 = loadmodel(basedir, ptcode, ctcode1, cropname, modelname) if drawRingMesh: if not ringMeshDisplacement: modelmesh1 = create_mesh(s1.model, 0.7) # Param is thickness else: modelmesh1 = create_mesh_with_abs_displacement(s1.model, radius=0.7, dim=dimensions) # Load vessel centerline (excel terarecon) (is very fast)
def __init__(self, ptcode, ctcode, StartPoints, EndPoints, basedir, modelname='modelavgreg'): """ with start and endpoints provided, calculate centerline and save as ssdf in basedir as model and dynamic model """ #todo: name of dynamic model is now deforms, unclear, should be dynamic #import numpy as np import visvis as vv import numpy as np import os import copy from stentseg.utils import PointSet, _utils_GUI from stentseg.utils.centerline import (find_centerline, points_from_nodes_in_graph, points_from_mesh, smooth_centerline) from stentseg.utils.datahandling import loadmodel, loadvol from stentseg.utils.visualization import show_ctvolume from stentseg.utils.picker import pick3d stentnr = len(StartPoints) cropname = 'prox' what = modelname what_vol = 'avgreg' vismids = True m = loadmodel(basedir, ptcode, ctcode, cropname, what) s = loadvol(basedir, ptcode, ctcode, cropname, what_vol) s.vol.sampling = [s.sampling[1], s.sampling[1], s.sampling[2]] s.sampling = s.vol.sampling start1 = StartPoints.copy() ends = EndPoints.copy() from stentseg.stentdirect import stentgraph ppp = points_from_nodes_in_graph(m.model) allcenterlines = [] # for pp allcenterlines_nosmooth = [] # for pp centerlines = [] # for stentgraph nodes_total = stentgraph.StentGraph() for j in range(stentnr): if j == 0 or not start1[j] == ends[j - 1]: # if first stent or when stent did not continue with this start point nodes = stentgraph.StentGraph() centerline = PointSet(3) # empty # Find main centerline # if j > 3: # for stent with midpoints # centerline1 = find_centerline(ppp, start1[j], ends[j], step= 1, # ndist=10, regfactor=0.5, regsteps=10, verbose=False) #else: centerline1 = find_centerline(ppp, start1[j], ends[j], step=1, ndist=10, regfactor=0.5, regsteps=1, verbose=False) # centerline1 is a PointSet print('Centerline calculation completed') # ========= Maaike ======= smoothfactor = 15 # Mirthe used 2 or 4 # check if cll continued here from last end point if not j == 0 and start1[j] == ends[j - 1]: # yes we continued ppart = centerline1[: -1] # cut last but do not cut first point as this is midpoint else: # do not use first points, as they are influenced by user selected points ppart = centerline1[1:-1] for p in ppart: centerline.append(p) # if last stent or stent does not continue with next start-endpoint if j == stentnr - 1 or not ends[j] == start1[j + 1]: # store non-smoothed for vis allcenterlines_nosmooth.append(centerline) pp = smooth_centerline(centerline, n=smoothfactor) # add pp to list allcenterlines.append(pp) # list with PointSet per centerline self.allcenterlines = allcenterlines # add pp as nodes for i, p in enumerate(pp): p_as_tuple = tuple(p.flat) nodes.add_node(p_as_tuple) nodes_total.add_node(p_as_tuple) # add pp as one edge so that pathpoints are in fixed order pstart = tuple(pp[0].flat) pend = tuple(pp[-1].flat) nodes.add_edge(pstart, pend, path=pp) nodes_total.add_edge(pstart, pend, path=pp) # add final centerline nodes model to list centerlines.append(nodes) # ========= Maaike ======= ## Store segmentation to disk # Build struct s2 = vv.ssdf.new() s2.sampling = s.sampling s2.origin = s.origin s2.stenttype = m.stenttype s2.croprange = m.croprange for key in dir(m): if key.startswith('meta'): suffix = key[4:] s2['meta' + suffix] = m['meta' + suffix] s2.what = what s2.params = s.params #reg s2.paramsseeds = m.params s2.stentType = 'nellix' s2.StartPoints = StartPoints s2.EndPoints = EndPoints # keep centerlines as pp also [Maaike] s2.ppallCenterlines = allcenterlines for k in range(len(allcenterlines)): suffix = str(k) pp = allcenterlines[k] s2['ppCenterline' + suffix] = pp s3 = copy.deepcopy(s2) s3['model'] = nodes_total.pack() # Store model for each centerline for j in range(len(centerlines)): suffix = str(j) model = centerlines[j] s2['model' + suffix] = model.pack() # Save model with seperate centerlines. filename = '%s_%s_%s_%s.ssdf' % (ptcode, ctcode, cropname, 'centerline_' + what) vv.ssdf.save(os.path.join(basedir, ptcode, filename), s2) print('saved to disk as {}.'.format(filename)) # Save model with combined centerlines filename = '%s_%s_%s_%s.ssdf' % (ptcode, ctcode, cropname, 'centerline_total_' + what) vv.ssdf.save(os.path.join(basedir, ptcode, filename), s3) print('saved to disk as {}.'.format(filename)) # remove intermediate centerline points # start1 = map(tuple, start1) # ends = map(tuple, ends) startpoints_clean = copy.deepcopy(start1) endpoints_clean = copy.deepcopy(ends) duplicates = list(set(start1) & set(ends)) for i in range(len(duplicates)): startpoints_clean.remove(duplicates[i]) endpoints_clean.remove(duplicates[i]) #Visualize f = vv.figure(10) vv.clf() a1 = vv.subplot(121) a1.daspect = 1, 1, -1 vv.plot(ppp, ms='.', ls='', alpha=0.6, mw=2) for j in range(len(startpoints_clean)): vv.plot(PointSet(list(startpoints_clean[j])), ms='.', ls='', mc='g', mw=20) # startpoint green vv.plot(PointSet(list(endpoints_clean[j])), ms='.', ls='', mc='r', mw=20) # endpoint red for j in range(len(allcenterlines)): vv.plot(allcenterlines[j], ms='.', ls='', mw=10, mc='y') vv.title('Centerlines and seed points') vv.xlabel('x (mm)') vv.ylabel('y (mm)') vv.zlabel('z (mm)') # for j in range(len(allcenterlines_nosmooth)): # vv.plot(allcenterlines_nosmooth[j], ms='o', ls='', mw=10, mc='c', alpha=0.6) a2 = vv.subplot(122) a2.daspect = 1, 1, -1 vv.plot(ppp, ms='.', ls='', alpha=0.6, mw=2) # vv.volshow(s.vol, clim=clim, renderStyle = 'mip') t = show_ctvolume(s.vol, axis=a2, showVol='ISO', clim=(0, 2500), isoTh=250, removeStent=False, climEditor=True) label = pick3d(vv.gca(), s.vol) for j in range(len(startpoints_clean)): vv.plot(PointSet(list(startpoints_clean[j])), ms='.', ls='', mc='g', mw=20, alpha=0.6) # startpoint green vv.plot(PointSet(list(endpoints_clean[j])), ms='.', ls='', mc='r', mw=20, alpha=0.6) # endpoint red for j in range(len(allcenterlines)): vv.plot(allcenterlines[j], ms='o', ls='', mw=10, mc='y', alpha=0.6) # show midpoints (e.g. duplicates) if vismids: for p in duplicates: vv.plot(p[0], p[1], p[2], mc='m', ms='o', mw=10, alpha=0.6) a2.axis.visible = False vv.title('Centerlines and seed points') a1.camera = a2.camera f.eventKeyDown.Bind( lambda event: _utils_GUI.RotateView(event, [a1, a2])) f.eventKeyDown.Bind( lambda event: _utils_GUI.ViewPresets(event, [a1, a2])) # Pick node for midpoint to redo get_centerline self.pickedCLLpoint = _utils_GUI.Event_pick_graph_point( nodes_total, s.vol, label, nodesOnly=True) # x,y,z # use key p to select point #=============================================================================== vv.figure(11) vv.gca().daspect = 1, 1, -1 t = show_ctvolume(s.vol, showVol='ISO', clim=(0, 2500), isoTh=250, removeStent=False, climEditor=True) label2 = pick3d(vv.gca(), s.vol) for j in range(len(startpoints_clean)): vv.plot(PointSet(list(startpoints_clean[j])), ms='.', ls='', mc='g', mw=20, alpha=0.6) # startpoint green vv.plot(PointSet(list(endpoints_clean[j])), ms='.', ls='', mc='r', mw=20, alpha=0.6) # endpoint red vv.xlabel('x (mm)') vv.ylabel('y (mm)') vv.zlabel('z (mm)') #=============================================================================== ## Make model dynamic (and store/overwrite to disk) import pirt from stentseg.motion.dynamic import incorporate_motion_nodes, incorporate_motion_edges # Load deforms filename = '%s_%s_%s_%s.ssdf' % (ptcode, ctcode, cropname, 'deforms') s1 = vv.ssdf.load(os.path.join(basedir, ptcode, filename)) deformkeys = [] for key in dir(s1): if key.startswith('deform'): deformkeys.append(key) deforms = [s1[key] for key in deformkeys] deforms = [ pirt.DeformationFieldBackward(*fields) for fields in deforms ] for i in range(len(deforms)): deforms[i]._field_sampling = tuple(s1.sampling) paramsreg = s1.params # Load model s2 = loadmodel(basedir, ptcode, ctcode, cropname, 'centerline_' + what) s3 = loadmodel(basedir, ptcode, ctcode, cropname, 'centerline_total_' + what) # Combine ... for key in dir(s2): if key.startswith('model'): incorporate_motion_nodes(s2[key], deforms, s.origin) incorporate_motion_edges(s2[key], deforms, s.origin) model = s2[key] s2[key] = model.pack() # Combine ... for key in dir(s3): if key.startswith('model'): incorporate_motion_nodes(s3[key], deforms, s.origin) incorporate_motion_edges(s3[key], deforms, s.origin) model = s3[key] s3[key] = model.pack() # Save s2.paramsreg = paramsreg filename = '%s_%s_%s_%s.ssdf' % (ptcode, ctcode, cropname, 'centerline_' + what + '_deforms') vv.ssdf.save(os.path.join(basedir, ptcode, filename), s2) print('saved to disk as {}.'.format(filename)) # Save s3.paramsreg = paramsreg filename = '%s_%s_%s_%s.ssdf' % ( ptcode, ctcode, cropname, 'centerline_total_' + what + '_deforms') vv.ssdf.save(os.path.join(basedir, ptcode, filename), s3) print('saved to disk as {}.'.format(filename))