def create_dict(g: gt.Graph, n_att_name, e_att_name): node_dict = {n_att_name: {'size': g.num_vertices()}} # find all the total values unique_keys = np.unique([g.vp[n_att_name][n] for n in g.vertices()]) for k in unique_keys: node_dict[n_att_name][k] = set( [n for n in g.vertices() if (g.vp[n_att_name][n] == k)]) edge_dict = {e_att_name: {'size': int(g.num_edges())}} # find all the total values unique_keys = np.unique([g.ep[e_att_name][e] for e in g.edges()]) for k in unique_keys: edge_dict[e_att_name][k] = set([(e.source(), e.target()) for e in g.edges() if g.ep[e_att_name][e] == k]) edge_dict[e_att_name][k].update( set([(e.target(), e.source()) for e in g.edges() if g.ep[e_att_name][e] == k])) # mirror_edges = [] # for e in edge_dict[e_att_name][k]: # mirror_edges.append((e[1], e[0])) # edge_dict[e_att_name][k].update(mirror_edges) return node_dict, edge_dict
def from_graph(gt_graph: gt.Graph, is_directed, is_weighted): g = DWGraph() v_ind = gt_graph.vertex_index e_ind = gt_graph.edge_index old_to_new_v = {} old_to_new_e = {} s = set() for e in gt_graph.edges(): u, v = e s.add(v_ind[u]) s.add(v_ind[v]) for v in gt_graph.vertices(): if v_ind[v] in s: old_to_new_v[v_ind[v]] = g.add_vertex() for e in gt_graph.edges(): u, v = e old_to_new_e[e_ind[e]] = g.add_edge(old_to_new_v[v_ind[u]], old_to_new_v[v_ind[v]]) for p_type, vp_name in gt_graph.vp.properties: if p_type != 'v': continue old_vp = gt_graph.vp[vp_name] g.vp[vp_name] = g.new_vp(old_vp.value_type()) new_vp = g.vp[vp_name] for v in gt_graph.vertices(): if v_ind[v] in s: new_vp[old_to_new_v[v_ind[v]]] = deepcopy(old_vp[v]) for p_type, ep_name in gt_graph.ep.properties: if p_type != 'e': continue old_ep = gt_graph.ep[ep_name] g.ep[ep_name] = g.new_ep(old_ep.value_type()) new_ep = g.ep[ep_name] for e in gt_graph.edges(): new_ep[old_to_new_e[e_ind[e]]] = deepcopy(old_ep[e]) g.is_weighted_prop = is_weighted g.is_directed_prop = is_directed # g.orig_nodes = list(vertices.keys()) # g.nodes_orig_new_mapping = vertices return g
def burtFig4(directed=False): ''' Returns the graph presented at Burt's "Role Equivalence" paper, at Figure_x ''' g = Graph(directed=directed) g.add_vertex(9) g.add_edge(0, 4) g.add_edge(1, 4) g.add_edge(2, 4) g.add_edge(3, 4) g.add_edge(4, 5) g.add_edge(4, 6) # 4-clique g.add_edge(5, 6) g.add_edge(5, 7) g.add_edge(5, 8) g.add_edge(6, 7) g.add_edge(6, 8) g.add_edge(7, 8) if directed: g.add_edge(6, 5) g.add_edge(7, 5) g.add_edge(8, 5) g.add_edge(7, 6) g.add_edge(8, 6) g.add_edge(8, 7) for ed in g.edges(): print ed return g
def ring(num_vtx=100, k=2, p=0.0): g = Graph(directed=False) vtx = list(g.add_vertex(num_vtx)) # connect neighbors for i in vtx: for j in xrange(1, k + 1): dest = g.vertex((g.vertex_index[i] - j) % num_vtx) if g.edge(i, dest) is None: g.add_edge(i, dest) # redirect edges # old_edges = list(g.edges()) old_edges = [(x.source(), x.target()) for x in g.edges()] for i in old_edges: n = random.random() if n < p: # redirect edge; choose random vertex as new destination vtx_tmp = vtx[:] vtx_tmp.remove(i[1]) if i[0] in vtx_tmp: vtx_tmp.remove(i[0]) dest = random.choice(vtx_tmp) while g.edge(i[0], dest) is not None: vtx_tmp.remove(dest) dest = random.choice(vtx_tmp) g.remove_edge(g.edge(i[0], i[1])) g.add_edge(i[0], dest) return g
def _filter_short_branch(self, filter=False, short=30): """ filter out very short branches: do this maybe not right for some models, for models with flat part, it is right I will test how this effect the final matching results need to delete nodes, switch with the last one then delete last """ if filter == False: self.verts = self.verts_init self.edges = self.edges_init else: init_graph = Graph(directed=False) init_graph.add_vertex(len(self.verts_init)) for edge in self.edges_init: init_graph.add_edge(init_graph.vertex(edge[0]), init_graph.vertex(edge[1])) terminal_node = [] for v in init_graph.vertices(): if v.out_degree() == 1: terminal_node.append(v) visitor = DepthVisitor() short_nodes = [] for tn in terminal_node: search.dfs_search(init_graph, tn, visitor) tmp_node = visitor.get_short_branch(min_length=short) visitor.reset() for n in tmp_node: short_nodes.append(n) ## get edges on the short paths short_nodes = list(set(short_nodes)) short_edges = [] temp_verts = self.verts_init[:] v_num = len(self.verts_init) if len(short_nodes): for v in reversed(sorted(short_nodes)): for ve in init_graph.vertex(v).out_edges(): short_edges.append(ve) ## delete edges first, then vertex short_edges = list(set(short_edges)) for e in short_edges: init_graph.remove_edge(e) print 'deleting vertex', for v in reversed(sorted(short_nodes)): print v, temp_verts[int(v)] = temp_verts[v_num-1] init_graph.remove_vertex(v, fast=True) v_num -= 1 print '\ndeleting related edges' # already done above, just info user else: print 'no short branches' ######## new vertices and edges ######## self.verts = temp_verts[:v_num] self.edges = [] for e in init_graph.edges(): self.edges.append([int(e.source()), int(e.target())])
def _to_directed(G: graph_tool.Graph) -> graph_tool.Graph: H = graph_tool.Graph(directed=True) W = G.edge_properties['weights'] U = H.new_edge_property('double') H.edge_properties['weights'] = U for e in G.edges(): s, t = int(e.source()), int(e.target()) e1 = H.add_edge(s, t, add_missing=True) e2 = H.add_edge(t, s) w = W[e] U[e1] = w U[e2] = w return H
def print_graph(g: gt.Graph): nodes = g.vertices() print('Directed' if g.is_directed() else 'Undirected') for node in nodes: prop = {} for key in g.vp.keys(): prop[key] = g.vp[key][node] print(node, ' : ', prop) for e in g.edges(): prop = {} for key in g.ep.keys(): prop[key] = g.ep[key][e] print(e, ' : ', prop)
def collect_edges_from_graph( g: gt.Graph, vertex_color_to_slice_map: Dict[int, str] ) -> Tuple[pd.DataFrame, List[Dict[str, Any]]]: edge_collections = [] edge_to_color_map = g.vertex_properties["colors"] edge_to_data_index_map = g.vertex_properties["data_indices"] for edge in tqdm(g.edges()): source_vertex = edge.source() target_vertex = edge.target() source_vertex_color = edge_to_color_map[source_vertex] target_vertex_color = edge_to_color_map[target_vertex] source_vertex_data_index = edge_to_data_index_map[source_vertex] target_vertex_data_index = edge_to_data_index_map[target_vertex] # source vertex should be training data if not source_vertex_data_index.startswith("train"): raise ValueError # target vertex should be evaluation data if not target_vertex_data_index.startswith("eval"): raise ValueError # train vertex should have this color if source_vertex_color != DEFAULT_TRAIN_VERTEX_COLOR: raise ValueError # eval vertex should not have this color if target_vertex_color == DEFAULT_TRAIN_VERTEX_COLOR: raise ValueError edge_collection = { "edge": edge, "target_slice": vertex_color_to_slice_map[target_vertex_color], "source_vertex_data_index": source_vertex_data_index, "target_vertex_data_index": target_vertex_data_index, } for property_name, property_map in g.edge_properties.items(): if property_name in edge_collection.keys(): raise ValueError(f"Duplicate key {property_name}") edge_collection[property_name] = property_map[edge] edge_collections.append(edge_collection) return pd.DataFrame(edge_collections), edge_collections
def score_solution(q: gt.Graph, a: gt.Graph, solution): # Archive graph A # query graph Q # Solution = list of tuples, length |G|, e.g. (1,3),(2,8),(3,12) solution_score = 0 sol_dict = {} sol_dict_new = {} q_original = original_vp(q) a_original = original_vp(a) q_o = dict_from_property_map(q, q_original) a_o = dict_from_property_map(a, a_original) q_v_map = vp_map(q, 'nValue') a_v_map = vp_map(a, 'nValue') q_e_map = ep_map(q, 'eValue') a_e_map = ep_map(a, 'eValue') # Current impl simply adds 1 for each matching node or edge for s in solution: # s should be a tuple e.g. (1,3) q_node, a_node = s if q_node in q_o and a_node in a_o: solution_score += q_v_map[q_o[q_node]] == a_v_map[a_o[a_node]] sol_dict[q_node] = a_node sol_dict_new[q_o[q_node]] = a_o[a_node] for e_q in q.edges(): u_q, v_q = e_q if u_q in sol_dict_new and v_q in sol_dict_new: edges_a = a.edge(sol_dict_new[u_q], sol_dict_new[v_q], all_edges=True) for e_a in edges_a: if q_e_map[e_q] == a_e_map[e_a]: u_a, v_a = e_a solution_score += 1 u1 = q_original[u_q] v1 = q_original[v_q] u2 = a_original[u_a] v2 = a_original[v_a] sol_dict[(u1, v1)] = (u2, v2) break return sol_dict, solution_score
def largest_strongly_connected_component(self, graph): from graph_tool import Graph import graph_tool.all as gt largest_connected_component = Graph(directed=True) if not self.is_relationship: edge_prop_time = largest_connected_component.new_edge_property( "int") edge_prop_type = largest_connected_component.new_edge_property( "string") for edge in tqdm(graph.edges(data=True)): e = tuple(edge[:2]) largest_connected_component.add_edge(e[0], e[1]) if not self.is_relationship: edge_prop_time[e] = edge[-1]["time"] edge_prop_type[e] = edge[-1]["type"] largest_connected_component_view = gt.label_largest_component( largest_connected_component) largest_connected_component = gt.GraphView( largest_connected_component, vfilt=largest_connected_component_view) print( "Total nodes {0} in largest strongly connected component.".format( largest_connected_component.num_vertices())) print( "Total edges {0} in largest strongly connected component.".format( largest_connected_component.num_edges())) with open(self.output, "w+") as output_file: for edge in tqdm(largest_connected_component.edges()): if not self.is_relationship: output_file.write("{0} {1} {2} {3}\n".format( edge.source(), edge.target(), edge_prop_time[edge], edge_prop_type[edge])) else: output_file.write("{0} {1}\n".format( edge.source(), edge.target()))
def insert_targets(a: gt.Graph, q: gt.Graph, n_targets, target_solutions, is_clutter): print("Inserting targets, n_targets = {}.".format(n_targets)) mp_q = vp_map(q, 'nValue', 'int') mp_a = vp_map(a, 'nValue', 'int') for i in range(n_targets): print("Target {}...".format(i)) # Map Q to a corresponding set of random nodes in A map_to_a = {} new_target = {'nodes': [], 'edges': [], 'isClutter': is_clutter} for node in q.vertices(): while True: ind = np.random.choice(list(range(a.num_vertices()))) if not is_node_in_existing_target(ind, target_solutions): break map_to_a[node] = ind new_target['nodes'].append(ind) # copy node attributes exactly mp_a[ind] = mp_q[node] # Add the same edges in Q to A for edge in q.edges(): source_a = map_to_a[edge.source()] destination_a = map_to_a[edge.target()] # edgeData = Q.get_edge_data(edge[0], edge[1]).copy() e_a = a.add_edge(source_a, destination_a) # print('bf: {}, {}'.format(a.ep['eValue'][e_a], q.ep['eValue'][edge])) copy_edge_attributes(a, e_a, q, edge) # print('af: {}'.format(a.ep['eValue'][e_a])) # for edge_attr, attr_val in edgeData.items(): # nx.set_edge_attributes(Q, {(source_a, destination_a): attr_val}, edge_attr) new_target['edges'].append(e_a) target_solutions.append(new_target) print(a)
class lqg(object): def __init__(self, dim=3): self.dim = dim return def read_systre_key(self, skey, dim=3): self.dim = dim dfac = 2 + self.dim skey = skey.split() self.nedges = len(skey) / dfac self.nvertices = 1 self.edges = [] self.labels = [] for i in range(self.nedges): edge = map(int, skey[i * dfac:i * dfac + 2]) for j in edge: if j > self.nvertices: self.nvertices = j edge = list(numpy.array(edge) - 1) label = map(int, skey[i * dfac + 2:i * dfac + dfac]) self.edges.append(edge) self.labels.append(label) return def write_systre_pgr(self, id="mfpb"): pgr = "PERIODIC_GRAPH\nID %s\nEDGES\n" % id for e, l in zip(self.edges, self.labels): entry = (" %s %s" + self.dim * " %1.0f" + "\n") % tuple(list(numpy.array(e) + 1) + l) pgr += entry pgr += "END" return pgr def get_lqg_from_topo(self, topo): # be careful not working for nets where an vertex is connected to itself self.dim = 3 self.nvertices = topo.get_natoms() self.nedges = 0 self.edges = [] self.labels = [] for i in range(self.nvertices): for j, v in enumerate(topo.conn[i]): if v > i: self.nedges += 1 self.edges.append([i, v]) #pdb.set_trace() self.labels.append(list(topo.pconn[i][j])) return def get_lqg_from_lists(self, edges, labels, nvertices, dim): assert len(edges) == len(labels) self.edges = edges self.labels = labels self.dim = dim self.nedges = len(edges) self.nvertices = nvertices return def build_lqg(self): self.nbasevec = self.nedges - self.nvertices + 1 self.molg = Graph(directed=True) self.molg.ep.label = self.molg.new_edge_property("vector<double>") self.molg.ep.number = self.molg.new_edge_property("int") for i in range(self.nvertices): iv = self.molg.add_vertex() for i, e in enumerate(self.edges): ie = self.molg.add_edge(self.molg.vertex(e[0]), self.molg.vertex(e[1])) self.molg.ep.label[ie] = self.labels[i] self.molg.ep.number[ie] = i return def get_cyclic_basis(self): nbasevec = self.nbasevec basis = numpy.zeros([nbasevec, self.nedges], dtype="int") self.molg.set_directed(False) tree = min_spanning_tree(self.molg) i = 0 for e in self.molg.edges(): if tree[e] == 0: self.molg.set_edge_filter(tree) vl, el = shortest_path(self.molg, self.molg.vertex(int(e.target())), self.molg.vertex(int(e.source()))) self.molg.set_edge_filter(None) basis[i, self.molg.ep.number[e]] = 1 neg = False for eb in el: idx = self.molg.ep.number[eb] ebt = self.get_edge_with_idx(idx) if ebt.target() == e.target(): if neg != True: basis[i, self.molg.ep.number[eb]] = -1 neg = True else: basis[i, self.molg.ep.number[eb]] = 1 neg = False elif ebt.source() == e.source(): if neg != True: basis[i, self.molg.ep.number[eb]] = -1 neg = True else: basis[i, self.molg.ep.number[eb]] = 1 neg = False elif ebt.source() == e.target(): if neg != True: basis[i, self.molg.ep.number[eb]] = 1 neg = False else: basis[i, self.molg.ep.number[eb]] = -1 neg = True elif ebt.target() == e.source(): if neg != True: basis[i, self.molg.ep.number[eb]] = 1 neg = False else: basis[i, self.molg.ep.number[eb]] = -1 neg = True e = ebt i += 1 self.cyclic_basis = basis self.molg.set_directed(True) return self.cyclic_basis def get_cocycle_basis(self): n = self.nedges - (self.nedges - self.nvertices + 1) cocycles = numpy.zeros([n, self.nedges]) self.molg.set_directed(False) i = 0 for v in self.molg.vertices(): el = v.out_edges() for eb in el: idx = self.molg.ep.number[eb] ebt = self.get_edge_with_idx(idx) if ebt.source() == v: cocycles[i, idx] = 1 else: cocycles[i, idx] = -1 i += 1 if i == n: break self.cocycle_basis = cocycles return self.cocycle_basis def get_ncocycles(self, n): self.molg.set_directed(False) cocycles = numpy.zeros([n, self.nedges]) i = 0 for v in self.molg.vertices(): el = v.out_edges() for eb in el: idx = self.molg.ep.number[eb] ebt = self.get_edge_with_idx(idx) if ebt.source() == v: cocycles[i, idx] = 1 else: cocycles[i, idx] = -1 i += 1 if i == n: break return cocycles def get_B_matrix(self): n = self.nedges - (self.nedges - self.nvertices + 1) if n > 0: self.B = numpy.append(self.cyclic_basis, self.cocycle_basis, axis=0) else: self.B = self.cyclic_basis return self.B def get_alpha(self): vimg = [] labels = numpy.array(self.labels) for i in range(numpy.shape(self.cyclic_basis)[0]): img = numpy.sum(self.cyclic_basis[i] * labels.T, axis=1) vimg.append(img) for i in range(self.nedges - self.nbasevec): if self.dim == 2: vimg.append([0, 0]) else: vimg.append([0, 0, 0]) self.alpha = numpy.array(vimg) return self.alpha def get_image(self, vec): labels = numpy.array(self.labels) return numpy.sum(vec * labels.T, axis=1) def get_fracs(self): self.fracs = numpy.dot(numpy.linalg.inv(self.B), self.alpha) return self.fracs def get_lattice_basis(self): idx = self.find_li_vectors(self.alpha) latbase = self.alpha[idx] Lr = self.cyclic_basis[idx] ### we need to orthonormalize the latbase ### L = numpy.zeros([self.dim, self.nedges]) olatbase = numpy.eye(self.dim, self.dim) for i in range(self.dim): b = numpy.linalg.solve(latbase.T, olatbase[i, :]) for j in range(self.dim): L[i, :] += b[j] * Lr[j, :] self.lattice_basis = L return self.lattice_basis def get_kernel(self): k = numpy.zeros( [self.nbasevec - self.dim + self.nvertices - 1, self.nedges]) idx = self.find_li_vectors(self.alpha) latbase = self.alpha[idx] counter = 0 ### TODO: switch to other basis to make it more beautiful for i in range(self.nbasevec): if i not in idx: b = numpy.linalg.solve(latbase.T, self.alpha[i]) bb = numpy.zeros(self.nedges) for j in range(self.dim): bb += b[j] * self.cyclic_basis[idx[j]] k[counter] = self.cyclic_basis[i] - bb #print(self.get_image(k[counter])) counter += 1 if self.nvertices > 1: k[self.nbasevec - self.dim:, :] = self.cocycle_basis[0:self.nvertices - 1, :] self.kernel = k return self.kernel def get_cell(self): k = self.kernel L = self.lattice_basis S = numpy.dot(k, k.T) P = numpy.eye(self.nedges, self.nedges) - numpy.dot( k.T, numpy.dot(numpy.linalg.inv(S), k)) self.cell = numpy.dot(L, numpy.dot(P, L.T)) return self.cell def place_vertices(self, first=numpy.array([0.0, 0.0, 0.0])): frac_xyz = numpy.zeros([self.nvertices, 3]) frac_xyz[0, :] = first done = [0] counter = 0 while len(done) != self.nvertices: for i, e in enumerate(self.edges): if self.labels[i] == [0, 0, 0]: if ((e[0] in done) and (e[1] not in done)): #print(e, self.fracs[i,:]) frac_xyz[e[1], :] = (frac_xyz[e[0], :] + self.fracs[i, :]) done.append(e[1]) elif ((e[1] in done) and (e[0] not in done)): nc = (frac_xyz[e[1], :] - self.fracs[i, :]) frac_xyz[e[0], :] = nc done.append(e[0]) counter += 1 if counter > 10: break #frac_xyz = frac_xyz%1 print(len(done)) if len(done) != self.nvertices: print('proceed') for i, e in enumerate(self.edges): if ((e[0] in done) and (e[1] not in done)): print(e) frac_xyz[e[1], :] = frac_xyz[e[0], :] + self.fracs[i, :] done.append(e[1]) elif ((e[1] in done) and (e[0] not in done)): print(e, self.labels[i], self.fracs[i, :]) #### problem!!!!! frac_xyz[e[0], :] = frac_xyz[e[1], :] - self.fracs[i, :] done.append(e[0]) ### perhaps a flooring has to be performe self.frac_xyz = frac_xyz return self.frac_xyz def to_mol(self): t = topo() t.natoms = self.nvertices t.set_cell(self.cell) t.set_xyz_from_frac(self.frac_xyz) t.set_atypes(self.nvertices * ['1']) t.set_empty_conn() t.set_empty_pconn() for i, e in enumerate(self.edges): t.conn[e[0]].append(e[1]) t.conn[e[1]].append(e[0]) t.pconn[e[0]].append(numpy.array(self.labels[i])) t.pconn[e[1]].append(-1 * numpy.array(self.labels[i])) #t.wrap_in_box() t.set_elems_by_coord_number() return t def get_edge_with_idx(self, idx): for i in self.molg.edges(): if self.molg.edge_index[i] == idx: return i #if self.molg.ep.number[i] == idx: return i def find_li_vectors(self, R): rank = numpy.linalg.matrix_rank(R) idx = [] ### get first non zero vector of R fn = numpy.nonzero(R)[0][0] idx.append(fn) for i in range(fn + 1, R.shape[0]): indep = True for j in idx: if i != j: inner_product = numpy.dot( R[i, :], R[j, :]) #compute the scalar product norm_i = numpy.linalg.norm(R[i, :]) #compute norms norm_j = numpy.linalg.norm(R[j, :]) if abs(inner_product - norm_j * norm_i) < 1e-4: # vector i is linear dependent, iterate i indep = False break if indep == True: idx.append(i) if numpy.linalg.matrix_rank(R[idx]) != len(idx): idx.pop() if len(idx) == rank: break return idx def vertex_positions(self, edges, used, pos={}): if self.dim == 2: return 'Not yet implemented' if len(pos.keys()) == self.nvertices: return pos self.molg.set_directed(True) for i, ed in enumerate(edges): e = ed if i == 0: break if int(str(e.source())) not in pos.keys() and int(str( e.target())) not in pos.keys(): pass elif int(str(e.source())) not in pos.keys() or int(str( e.target())) not in pos.keys(): from_v = int(str(e.source())) if int(str( e.source())) in pos.keys() else int(str(e.target())) to_v = int(str(e.target())) if int(str( e.target())) not in pos.keys() else int(str(e.source())) coeff = 0 for i, ed in enumerate(self.molg.vertex(from_v).out_edges()): if e == ed: coeff = 1 break if coeff == 0: coeff = -1 index = self.molg.ep.number[e] to_pos = coeff * numpy.array(self.fracs)[index] + pos[from_v] newedges = [] to_pos = numpy.array([i % 1 for i in to_pos]) pos[to_v] = to_pos used.append(e) self.molg.set_directed(False) ee = self.molg.vertex(to_v).out_edges() newedges = [i for i in ee if i not in used and i not in edges] print(newedges) edges = newedges + edges[1:] else: used.append(e) edges = edges[1:] return self.vertex_positions(edges, used, pos) def __call__(self): self.build_lqg() self.get_cyclic_basis() self.get_cocycle_basis() self.get_B_matrix() self.get_alpha() self.get_lattice_basis() self.get_kernel() self.get_cell() self.get_fracs() self.place_vertices()
def sgm_match(t_graph: gt.Graph, g_graph: gt.Graph, delta, tau, n_idx, e_idx) -> gt.Graph: # T is a query tree # G is a query graph # Delta is the score delta that we can accept from perfect match # tau is how far off this tree is from the graph, at most. # nIdx is an index containing node attributes # eIdx is an index containing edge attributes # root_match = [n for n, d in list(T.in_degree().items()) if d == 0] root_match = [v for v in t_graph.vertices() if v.in_degree() == 0] root = root_match[0] n_keys = list(n_idx.keys())[0] e_keys = list(e_idx.keys())[0] # print 'Building matching graph' print('Printing MDST Graph') print(root) print_graph(t_graph) # Step 1: Get all the matches for the nodes node_matches = dict() for v in t_graph.vertices(): if t_graph.vp[n_keys][v] in list(n_idx[n_keys].keys()): node_matches[v] = n_idx[n_keys][t_graph.vp[n_keys][v]] else: node_matches[v] = set() # Step 2: Get all the edge matches for the node edge_matches = dict() for e in t_graph.edges(): if t_graph.ep[e_keys][e] in list(e_idx[e_keys].keys()): edge_matches[e] = e_idx[e_keys][t_graph.ep[e_keys][e]] else: edge_matches[e] = set() # Make sure you count just the ones that have matching nodes too. edge_matches[e] = set([ em for em in edge_matches[e] if em[0] in node_matches[e.source()] and em[1] in node_matches[e.target()] ]) # Scoring, initially, is going to be super-simple: # You get a 1 if you match, and a 0 if you don't. Everything's created equal. # Score everything and put it in a graph. for k in list(edge_matches.keys()): if len(edge_matches[k]) == 0: pass # stop_here = 1 match_graph = gt.Graph(directed=True) # for nT in T.nodes(): # for nG in node_matches[nT]: # MatchGraph.add_node(tuple([nT,nG]),score=1,solo_score=1) mg_edges = set() mg_vertices = set() mg_vertices_to_index = {} for eT in t_graph.edges(): for eG in edge_matches[eT]: v1 = (eT.source(), eG[0]) v2 = (eT.target(), eG[1]) mg_vertices.add(v1) mg_vertices.add(v2) mg_edges.add((v1, v2)) # match_graph.add_edge([(eT.source(), eG.source()), (eT.target(), eG.target())]) zero_id = vp_map(match_graph, 'zero_id') one_id = vp_map(match_graph, 'one_id') for tup in mg_vertices: v = match_graph.add_vertex() zero_id[v], one_id[v] = tup mg_vertices_to_index[tup] = v # it = iter(mg_vertices) # for v in match_graph.vertices(): # tup = next(it) # zero_id[v], one_id[v] = tup # mg_vertices_to_index[tup] = v for t1, t2 in mg_edges: match_graph.add_edge(mg_vertices_to_index[t1], mg_vertices_to_index[t2]) # debug_match_graph(match_graph) solo_score_vp = vp_map(match_graph, 'solo_score', 'int') score_vp = vp_map(match_graph, 'score_v', 'int') score_ep = ep_map(match_graph, 'score_e', 'int') path_vp = vp_map(match_graph, 'path', 'object') g_graph_original = original_vp(g_graph) t_graph_original = original_vp(t_graph) for v in match_graph.vertices(): solo_score_vp[v] = 1 score_vp[v] = 1 # Here we insert original nodes d = coll.deque() d.append((t_graph_original[zero_id[v]], g_graph_original[one_id[v]])) path_vp[v] = d for e in match_graph.edges(): score_ep[e] = 1 # gt_draw.graph_draw(match_graph, vprops={'text': zero_id}) # Get rid of anybody flying solo match_graph = clear_unconnected(match_graph, root) # this is clearly not working. # Now acquire/organize all hypotheses with scores above Max_Score - tau - delta # Figure out how much score you could possibly get at every node in the query. max_score_v = vp_map(t_graph, 'max_score_v', 'int') max_score_e = ep_map(t_graph, 'max_score_e', 'int') score_vp = vp_map(match_graph, 'score_v', 'int') score_ep = ep_map(match_graph, 'score_e', 'int') path_vp = vp_map(match_graph, 'path', 'object') zero_id = vp_map(match_graph, 'zero_id') # gt_draw.graph_draw(match_graph, vprops={'text': zero_id}) for n in t_graph.vertices(): max_score_v[n] = 1 for e in t_graph.edges(): max_score_e[e] = 1 bfs_edges = list(gt_s.bfs_iterator(t_graph, source=root)) reversed_bfs_edges = list(reversed(bfs_edges)) t_index = t_graph.vertex_index # debug_match_graph(match_graph) for e in reversed_bfs_edges: # Reverse BFS search - should do leaf nodes first. # What's the best score we could get at this node? v1, v2 = e max_score_v[v1] += max_score_v[v2] + max_score_e[e] # Find all the edges equivalent to this one in the match graph edge_matches = [ (eG1, eG2) for eG1, eG2 in match_graph.edges() if zero_id[eG1] == t_index[v1] and zero_id[eG2] == t_index[v2] ] parent_nodes = set([eM1 for eM1, eM2 in edge_matches]) for p in parent_nodes: child_nodes = [eM2 for eM1, eM2 in edge_matches if eM1 == p] # First, check if the bottom node has a score best_score = 0 # best_node = None c_path = None for c in child_nodes: c_edge = match_graph.edge(p, c) c_score = score_vp[c] + score_ep[c_edge] c_path = path_vp[c] if c_score > best_score: best_score = c_score # best_child_path = c_path score_vp[p] += best_score for pathNode in c_path: path_vp[p].appendleft(pathNode) leave_prop = match_graph.new_vertex_property('bool') # CLEAN IT UP. for n in match_graph.vertices(): leave_prop[n] = score_vp[n] >= max_score_v[t_graph.vertex( zero_id[n])] - delta sub = gt.GraphView(match_graph, leave_prop) new_match_graph = create_q_graph(sub, add_back_reference=False) # Get rid of anybody flying solo match_graph = save_root_children(new_match_graph, root) zero_id = vp_map(match_graph, 'zero_id') one_id = vp_map(match_graph, 'one_id') path_list_vp = vp_map(match_graph, 'path_list', 'object') for n in match_graph.vertices(): d = coll.deque() d.append((t_graph_original[zero_id[n]], g_graph_original[one_id[n]])) path_list_vp[n] = [d] # Get a list of solutions alive in the graph for e in reversed_bfs_edges: v1, v2 = e edge_matches = [ (eG1, eG2) for eG1, eG2 in match_graph.edges() if zero_id[eG1] == t_index[v1] and zero_id[eG2] == t_index[v2] ] parent_nodes = set([eM1 for eM1, eM2 in edge_matches]) for p in parent_nodes: child_nodes = [eM2 for eM1, eM2 in edge_matches if eM1 == p] # First, check if the bottom node has a score tmpList = [] for c in child_nodes: for _p in path_list_vp[p]: for _c in path_list_vp[c]: tmpList.append(_p + _c) path_list_vp[p] = tmpList # debug_match_graph(match_graph) # Score the root solutions return match_graph
class BoardGraphGraphtool(BoardGraphBase): def __init__(self, number_of_vertices, graph_type): super().__init__(number_of_vertices, graph_type) # Graph tool creates directed multigraph by default. self._graph = Graph() self._graph.add_vertex(number_of_vertices) self._graph.vertex_properties["cell"] = self._graph.new_vertex_property( "object", number_of_vertices * [BoardCell()] ) self._graph.edge_properties["direction" ] = self._graph.new_edge_property("object") self._graph.edge_properties["weight" ] = self._graph.new_edge_property("int") def __getitem__(self, position): return self._graph.vp.cell[self._graph.vertex(position)] def __setitem__(self, position, board_cell): self._graph.vp.cell[self._graph.vertex(position)] = board_cell def __contains__(self, position): return position in range(0, self.vertices_count()) def vertices_count(self): return self._graph.num_vertices() def edges_count(self): return self._graph.num_edges() def has_edge(self, source_vertice, target_vertice, direction): for e in self._graph.vertex(source_vertice).out_edges(): if ( int(e.target()) == target_vertice and self._graph.ep.direction[e] == direction ): return True return False def out_edges_count(self, source_vertice, target_vertice): return len([ 1 for e in self._graph.vertex(source_vertice).out_edges() if int(e.target()) == target_vertice ]) def reconfigure_edges(self, width, height, tessellation): """ Uses tessellation object to create all edges in graph. """ self._graph.clear_edges() for source_vertice in self._graph.vertices(): for direction in tessellation.legal_directions: neighbor_vertice = tessellation.neighbor_position( int(source_vertice), direction, board_width=width, board_height=height ) if neighbor_vertice is not None: e = self._graph.add_edge( source_vertice, neighbor_vertice, add_missing=False ) self._graph.ep.direction[e] = direction # TODO: Faster version? # def reconfigure_edges(self, width, height, tessellation): # """ # Uses tessellation object to create all edges in graph. # """ # self._graph.clear_edges() # edges_to_add = [] # directions_to_add = dict() # for source_vertice in self._graph.vertices(): # for direction in tessellation.legal_directions: # neighbor_vertice = tessellation.neighbor_position( # int(source_vertice), direction, # board_width=width, board_height=height # ) # if neighbor_vertice is not None: # edge = (int(source_vertice), neighbor_vertice,) # edges_to_add.append(edge) # if edge not in directions_to_add: # directions_to_add[edge] = deque() # directions_to_add[edge].append(direction) # self._graph.add_edge_list(edges_to_add) if edges_to_add else None # for e in edges_to_add: # e_descriptors = self._graph.edge( # s = self._graph.vertex(e[0]), # t = self._graph.vertex(e[1]), # all_edges = True # ) # for e_descriptor in e_descriptors: # if len(directions_to_add[e]) > 0: # self._graph.ep.direction[e_descriptor] = directions_to_add[e][0] # directions_to_add[e].popleft() def calculate_edge_weights(self): for e in self._graph.edges(): self._graph.ep.weight[e] = self.out_edge_weight(int(e.target())) def neighbor(self, from_position, direction): try: for e in self._graph.vertex(from_position).out_edges(): if self._graph.ep.direction[e] == direction: return int(e.target()) except ValueError as e: raise IndexError(e.args) return None def wall_neighbors(self, from_position): return [ int(n) for n in self._graph.vertex(from_position).out_neighbours() if self[int(n)].is_wall ] def all_neighbors(self, from_position): return [ int(n) for n in self._graph.vertex(from_position).out_neighbours() ] def shortest_path(self, start_position, end_position): try: return [ int(v) for v in shortest_path( g=self._graph, source=self._graph.vertex(start_position), target=self._graph.vertex(end_position), )[0] ] except ValueError: return [] def dijkstra_path(self, start_position, end_position): try: self.calculate_edge_weights() return [ int(v) for v in shortest_path( g=self._graph, source=self._graph.vertex(start_position), target=self._graph.vertex(end_position), weights=self._graph.ep.weight, )[0] ] except ValueError: return [] def position_path_to_direction_path(self, position_path): retv = [] src_vertice_index = 0 for target_vertice in position_path[1:]: source_vertice = position_path[src_vertice_index] src_vertice_index += 1 for out_edge in self._graph.vertex(source_vertice).out_edges(): if int(out_edge.target()) == target_vertice: retv.append(self._graph.ep.direction[out_edge]) return { 'source_position': position_path[0] if position_path else None, 'path': retv }
def add_edge_attributes(g: gt.Graph, att_dist, att_name, p_type: str = 'int'): for e in g.edges(): add_single_edge_attribute(g, e, att_dist, att_name, p_type) return g
def calculate_mdst_v2(g: gt.Graph, n_idx, e_idx, used_stuff=set()): # Step 1: Figure out the weights. n_att_name = list(n_idx.keys())[0] e_att_name = list(e_idx.keys())[0] # Create an MDSTWeight vector on the nodes and edges. v_weight = vp_map(g, 'MDST_v_weight', 'float') e_weight = ep_map(g, 'MDST_e_weight', 'float') v_attribute_list = list(n_idx[n_att_name].keys()) e_attribute_list = list(e_idx[e_att_name].keys()) v_a_map = vp_map(g, n_att_name) e_a_map = ep_map(g, e_att_name) for n in g.vertices(): if v_a_map[n] in v_attribute_list: v_weight[n] = len( n_idx[n_att_name][v_a_map[n]]) / n_idx[n_att_name]['size'] else: v_weight[n] = 0 for e in g.edges(): if e in used_stuff: e_weight[e] = 1 else: if e_a_map[e] in e_attribute_list: e_weight[e] = len( e_idx[e_att_name][e_a_map[e]]) / e_idx[e_att_name]['size'] else: e_weight[e] = 0 # for e1,e2 in G.edges(): # G.adj[e1][e2]['Nonsense'] = 5 # Step 2: Calculate the MST. # gt.draw.graph_draw(g, vertex_text=g.vp['old'], vertex_font_size=18, output_size=(300, 300), output='G.png') t_map = gt_top.min_spanning_tree(g, e_weight, g.vertex(0)) # T = nx.algorithms.minimum_spanning_tree(G,weight='Nonsense') # Step 3: Figure out which root results in us doing the least work. t = gt.GraphView(g, efilt=t_map, directed=False) # gt.draw.graph_draw(t, vertex_text=t.vp['old'], vertex_font_size=18, output_size=(300, 300), output='T.png') best_t = None best_score = np.inf for root in t.vertices(): # Generate a new tree it = gt_s.bfs_iterator(t, root) nodes = [] edges = [] for e in it: edges.append(e) nodes.extend([e.source(), e.target()]) nodes = np.unique(nodes) new_t = create_q_graph(t, q_nodes=nodes, q_edges=edges, directed=True) new_t_score = mdst_score_v2(t, root) if new_t_score < best_score: # print(best_score) best_t = new_t best_score = new_t_score return best_t, best_score
def create_q_graph(a_graph: gt.Graph, q_nodes: Union[None, Iterable[gt.Vertex]] = None, q_edges: Union[None, Iterable[gt.Edge]] = None, add_back_reference=True, directed: Union[bool, None] = None) -> gt.Graph: _directed = directed if directed is not None else a_graph.is_directed() q = gt.Graph(directed=_directed) a_q_v = {} a_q_e = {} q_a_v = {} q_a_e = {} if q_nodes is None: q_nodes = set(a_graph.vertices()) for v in q_nodes: nv = a_q_v[v] = q.add_vertex() q_a_v[nv] = v for p_type, vp_name in a_graph.vp.properties: if p_type != 'v': continue old_vp = a_graph.vp[vp_name] q.vp[vp_name] = q.new_vp(old_vp.value_type()) new_vp = q.vp[vp_name] for v in a_graph.vertices(): if a_graph.vertex_index[v] in q_nodes: new_vp[a_q_v[v]] = deepcopy(old_vp[v]) if q_edges is None: q_edges = set(a_graph.edges()) for e in q_edges: e_start, e_end = e if e_start in q_nodes and e_end in q_nodes: ne = q.add_edge(a_q_v[e_start], a_q_v[e_end]) a_q_e[e] = ne q_a_e[ne] = e for p_type, ep_name in a_graph.ep.properties: if p_type != 'e': continue old_ep = a_graph.ep[ep_name] q.ep[ep_name] = q.new_ep(old_ep.value_type()) new_ep = q.ep[ep_name] for e, ne in a_q_e.items(): new_ep[ne] = deepcopy(old_ep[e]) if add_back_reference: from_a_node = q.new_vp('int') q.vp['fromANode'] = from_a_node from_a_edge = q.new_ep('object') q.ep['fromAEdge'] = from_a_edge for v in q.vertices(): from_a_node[v] = a_graph.vertex_index[q_a_v[v]] for e in q.edges(): e_a = q_a_e[e] vs, ve = e_a from_a_edge[e] = (a_graph.vertex_index[vs], a_graph.vertex_index[ve]) return q
break if not(request in all_requests): all_requests.append(request) #print("Number of requests are " + str(len(all_requests))) ## Defining the graph properties ## graph_weight = g.new_edge_property("float") g.ep.weight = graph_weight graph_pred_tree = g.new_vertex_property("int") pred_tree = graph_pred_tree edges_logger = {} for e in g.edges(): flags_of_edges = [] # Temporary flag to ensure that alternative path is not on the primary path itself flags_of_edges.append(1) # Flags to see which channels are currently in use for i in range(number_frequency_bands): flags_of_edges.append(1) # Flags to keep record of the extent of the usage of a particular channel in a link for i in range(number_frequency_bands): flags_of_edges.append(0) # Flags to fulfil the single point failure protection between those who share their primary paths for i in range(number_frequency_bands): flags_of_edges.append(1) edges_logger[str(e.source()) + " --> " + str(e.target())] = flags_of_edges edges_logger[str(e.target()) + " --> " + str(e.source())] = flags_of_edges g.ep.weight[e] = 0
class BaseGraph(object): """ Class representing a graph. We do not use pure graph_tool.Graph for we want to be able to easily change this library. Neither we use inheritance as graph_tool has inconvenient licence. """ def __init__(self): self._g = None self._node_dict = {} self._syn_to_vertex_map = None self._lemma_to_nodes_dict = None self._lu_on_vertex_dict = None def use_graph_tool(self): """ Returns underlying graph_tool.Graph. It should be avoided at all costs. """ return self._g def get_node_for_synset_id(self, syn_id): """ Lazy function to makes the map of synset identifiers to nodes into the graph. The building of map is made only on the first funcion call. The first and the next calls of this function will return the built map. """ if not self._syn_to_vertex_map: self._syn_to_vertex_map = {} for node in self.all_nodes(): if node.synset: synset_id = node.synset.synset_id self._syn_to_vertex_map[synset_id] = node return self._syn_to_vertex_map.get(syn_id, None) def pickle(self, filename): self._g.save(filename) def unpickle(self, filename): self._g = load_graph(filename) def init_graph(self, drctd=False): self._g = Graph(directed=drctd) def copy_graph_from(self, g): self._g = g._g.copy() def set_directed(self, drctd): self._g.set_directed(drctd) def is_directed(self): return self._g.is_directed() def merge_graphs(self, g1, g2): self._g = graph_union(g1._g, g2._g, internal_props=True) # Node operations: def all_nodes(self): for node in self._g.vertices(): yield BaseNode(self._g, node) def create_node_attribute(self, name, kind, value=None): if not self.has_node_attribute(name): node_attr = self._g.new_vertex_property(kind, value) self._g.vertex_properties[name] = node_attr def create_node_attributes(self, node_attributes_list): for attr in node_attributes_list: if not self.has_node_attribute(attr[0]): node_attr = self._g.new_vertex_property(attr[1]) self._g.vertex_properties[attr[0]] = node_attr def has_node_attribute(self, name): """ Checks if a node attribute already exists """ return name in self._g.vertex_properties def delete_node_attribute(self, name): """ Delete node attribute """ del self._g.vertex_properties[name] def add_node(self, name, node_attributes_list=None): if node_attributes_list is None: node_attributes_list = [] if name not in self._node_dict: new_node = self._g.add_vertex() self._node_dict[name] = BaseNode(self._g, new_node) for attr in node_attributes_list: self._g.vertex_properties[attr[0]][new_node] = attr[1] return self._node_dict[name] def get_node(self, name): return self._node_dict[name] def remove_node(self, name): self._g.remove_vertex(self._node_dict[name]._node) del self._node_dict[name] def nodes_filter(self, nodes_to_filter_set, inverted=False, replace=False, soft=False): """ Filters out nodes from set Args: nodes_to_filter_set (Iterable): Nodes which fill be filtered out. inverted (bool): If True, nodes NOT in set will be filtered out. Defaults to False. replace (bool): Replace current filter instead of combining the two. Defaults to False. soft (bool): Hide nodes without removing them so they can be restored with reset_nodes_filter. Defaults to False. """ predicate = lambda node: node not in nodes_to_filter_set self.nodes_filter_conditional(predicate, inverted, replace, soft) def nodes_filter_conditional(self, predicate, inverted=False, replace=False, soft=False): """ Filters node based on a predicate Args: predicate (Callable): Predicate returning False for nodes that should be filtered out. inverted (bool): Invert condition. Defaults to False. replace (bool): Replace current filter instead of combining the two. Defaults to False. soft (bool): Hide nodes without removing them so they can be restored with reset_nodes_filter. Defaults to False. """ (old_filter, old_inverted) = self._g.get_vertex_filter() new_filter = self._g.new_vertex_property("bool") for node in self.all_nodes(): kept = predicate(node) != inverted if not replace and old_filter: old_kept = bool(old_filter[node._node]) != old_inverted kept = kept and old_kept new_filter[node._node] = kept self._g.set_vertex_filter(new_filter, False) if not soft: self.apply_nodes_filter() def apply_nodes_filter(self): """ Removes nodes that are currently filtered out """ self._g.purge_vertices() def reset_nodes_filter(self): """ Clears node filter """ self._g.set_vertex_filter(None) # Edge operations: def num_edges(self): return self._g.num_edges() def all_edges(self): for e in self._g.edges(): yield BaseEdge(self._g, e) def get_edges_between(self, source, target): """ Return all edges between source and target. Source and target can be either BaseNode or integer. """ if isinstance(source, BaseNode): source = source._node if isinstance(target, BaseNode): target = target._node for e in self._g.edge(source, target, all_edges=True): yield BaseEdge(self._g, e) def get_edge(self, source, target, add_missing=False): """ Return some edge between source and target. Source and target can be either BaseNode or integer. """ if isinstance(source, BaseNode): source = source._node if isinstance(target, BaseNode): target = target._node e = self._g.edge(source, target, add_missing) if e is not None: return BaseEdge(self._g, e) else: return None def create_edge_attribute(self, name, kind, value=None): if not self.has_edge_attribute(name): edge_attr = self._g.new_edge_property(kind, value) self._g.edge_properties[name] = edge_attr def alias_edge_attribute(self, name, alias): self._g.edge_properties[alias] = self._g.edge_properties[name] def create_edge_attributes(self, edge_attributes_list): for attr in edge_attributes_list: if not self.has_edge_attribute(attr[0]): edge_attr = self._g.new_edge_property(attr[1]) self._g.edge_properties[attr[0]] = edge_attr def has_edge_attribute(self, name): """ Checks if an edge attribute already existst """ return name in self._g.edge_properties def delete_edge_attribute(self, name): """ Delete edge attribute """ del self._g.edge_properties[name] def add_edge(self, parent, child, edge_attributes_list=None): if edge_attributes_list is None: edge_attributes_list = [] new_edge = self._g.add_edge(parent._node, child._node) for attr in edge_attributes_list: self._g.edge_properties[attr[0]][new_edge] = attr[1] return BaseEdge(self._g, new_edge) def edges_filter(self, edges_to_filter_set): edge_filter = self._g.new_edge_property("bool") for e in self.all_edges(): if e in edges_to_filter_set: edge_filter[e._edge] = False else: edge_filter[e._edge] = True self._g.set_edge_filter(edge_filter) self._g.purge_edges() def ungraph_tool(self, thingy, lemma_on_only_synset_node_dict): """ Converts given data structure so that it no longer have any graph_tool dependencies. """ logger = logging.getLogger(__name__) if type(thingy) == dict: return { self.ungraph_tool(k, lemma_on_only_synset_node_dict): self.ungraph_tool(thingy[k], lemma_on_only_synset_node_dict) for k in thingy } nodes_to_translate = set() for vset in lemma_on_only_synset_node_dict.values(): for v in vset: nodes_to_translate.add(v) if type(thingy) == gt.PropertyMap: dct = {} if thingy.key_type() == 'v': for node in nodes_to_translate: dct[node] = thingy[node.use_graph_tool()] elif thingy.key_type() == 'e': for edge in self.all_edges(): dct[edge] = thingy[edge.use_graph_tool()] else: logger.error('Unknown property type %s', thingy.key_type()) raise NotImplemented return dct def generate_lemma_to_nodes_dict_synsets(self): """ This method generates a utility dictionary, which maps lemmas to corresponding node objects. It is expensive in menas of time needed to generate the dictionary. It should therefore be executed at the beginning of the runtime and later its results should be reused as many times as needed without re-executing the function. """ lemma_to_nodes_dict = defaultdict(set) for node in self.all_nodes(): try: lu_set = node.synset.lu_set except KeyError: continue for lu in lu_set: lemma = lu.lemma.lower() lemma_to_nodes_dict[lemma].add(node) self._lemma_to_nodes_dict = lemma_to_nodes_dict def generate_lemma_to_nodes_dict_lexical_units(self): """ This method generates a utility dictionary, which maps lemmas to corresponding node objects. It is expensive in menas of time needed to generate the dictionary. It should therefore be executed at the beginning of the runtime and later its results should be reused as many times as needed without re-executing the function. """ lemma_to_nodes_dict = defaultdict(set) for node in self.all_nodes(): try: lemma = node.lu.lemma.lower() lemma_to_nodes_dict[lemma].add(node) except: continue self._lemma_to_nodes_dict = lemma_to_nodes_dict @property def lemma_to_nodes_dict(self): return self._lemma_to_nodes_dict def _make_lu_on_v_dict(self): """ Makes dictionary lu on vertex """ lu_on_vertex_dict = defaultdict(set) for node in self.all_nodes(): try: nl = node.lu except Exception: continue if nl: lu_on_vertex_dict[node.lu.lu_id] = node self._lu_on_vertex_dict = lu_on_vertex_dict
# In[27]: g.add_vertex(len(all_nodes)) # In[28]: edges = list(zip(df['u'].as_matrix(), df['v'].as_matrix())) # In[29]: g.add_edge_list(edges) # In[30]: # add edges_iter = list(g.edges()) for e in tqdm(edges_iter): u, v = int(e.source()), int(e.target()) if g.edge(v, u) is None: g.add_edge(v, u) # In[31]: weight = g.new_edge_property('float') weight.set_value(EPS) # In[32]: for i, r in tqdm(df.iterrows(), total=df.shape[0]): u, v, w = int(r['u']), int(r['v']), r['w'] weight[g.edge(u, v)] = w
def shortest_path_visiting_most_nodes(g: gt.Graph, adjusted_weight: gt.EdgePropertyMap, covered_vertices, summed_edge_weight): dist_map = gt.topology.shortest_distance(g, weights=adjusted_weight) not_visited_source_vertex = np.ones(g.num_vertices(), dtype=np.bool) not_visited_source_vertex[list(covered_vertices)] = False not_visited_source_vertex = not_visited_source_vertex.reshape( g.num_vertices(), 1) all_dists = dist_map.get_2d_array( range(g.num_vertices()) ).T #shortest path does only count the edges. so we have add one if the starting vertex was not visited. all_dists[(all_dists > summed_edge_weight) | (all_dists < 0)] = 0 all_dists = (g.num_vertices() + 1 - all_dists) % (g.num_vertices() + 1) shortest_paths = [] all_currently_covered_vertices = set() current_length = -1 z = 0 n = g.num_vertices() #if the longest shortest path covers only <= 2 new nodes go to fast mode: #simply add edges covering two vertices until not possible and then the remaining vertices. if (all_dists + not_visited_source_vertex).max() <= 2: covered_now = np.zeros(n, dtype=np.bool) for e in g.edges(): if int(e.source()) == int(e.target()): continue if int(e.source()) not in covered_vertices and int( e.target()) not in covered_vertices and not covered_now[ int(e.source())] and not covered_now[int(e.target())]: shortest_paths.append([int(e.source()), int(e.target())]) all_currently_covered_vertices.add(int(e.source())) all_currently_covered_vertices.add(int(e.target())) covered_now[int(e.source())] = True covered_now[int(e.target())] = True single_vertices = set(range(n)).difference( covered_vertices.union(all_currently_covered_vertices)) for i in single_vertices: shortest_paths.append([i]) return shortest_paths else: max_value = (all_dists + not_visited_source_vertex).max() had_source = np.zeros(n, dtype=np.bool) for source, target in np.array( np.where(all_dists + not_visited_source_vertex == max_value)).T: if had_source[ source] or source in all_currently_covered_vertices or target in all_currently_covered_vertices: continue shortest_path, _ = gt.topology.shortest_path( g, source, target, adjusted_weight) shortest_path = [int(v) for v in shortest_path] if (all_dists + not_visited_source_vertex).max() != len( set(shortest_path).difference(covered_vertices)): exit(10) if len(all_currently_covered_vertices.intersection( shortest_path)) != 0: continue if len(shortest_path) > 1 and len(shortest_path) < current_length: #print(len(shortest_paths)) return shortest_paths shortest_paths.append(shortest_path) all_currently_covered_vertices = all_currently_covered_vertices.union( shortest_path) if current_length < 0: current_length = len(shortest_path) #trim covered vertices from start and end #... #better: build this step directly into the weight function s.t. |P| is minimized as a third priority? if len(shortest_path) <= 2: # and z >=10: break had_source[source] = True return shortest_paths
class ob_viz(QWidget): def __init__(self, bg_color): QWidget.__init__(self) self.background_color = bg_color self.c = 0 # K = 0.5 # how many iterations the realignment is supposed to take self.step = 15 self.rwr_c = 0 # dumper([qt_coords]) dumper(['obv viz init']) # self.show() # with open("/tmp/eaf3.csv", "a") as fo: # wr = csv.writer(fo) # wr.writerow([self.c, "runs4"]) # dumper([self.c, "runs4"]) # self.node_names [g_id[i] for i in g.vertices()] def init2(self, emacs_var_dict): self.emacs_var_dict = emacs_var_dict self.link_str = self.emacs_var_dict['links'] self.g = Graph() self.label_ep = self.g.new_edge_property("string") self.links = self.link_str.split(";") link_tpls = [i.split(" -- ") for i in self.links] dumper([str(i) for i in link_tpls]) self.g_id = self.g.add_edge_list(link_tpls, hashed=True, string_vals=True, eprops=[self.label_ep]) self.adj = np.array([(int(i.source()), int(i.target())) for i in self.g.edges()]) self.node_names = [self.g_id[i] for i in self.g.vertices()] self.vd = {} for i in self.g.vertices(): self.vd[self.g_id[i]] = int(i) # self.pos_vp = sfdp_layout(self.g, K=0.5) self.pos_vp = fruchterman_reingold_layout(self.g) self.base_pos_ar = self.pos_vp.get_2d_array((0, 1)).T self.qt_coords = self.nolz_pos_ar(self.base_pos_ar) dumper([str(self.qt_coords)]) # dumper([link_str]) def update_graph(self, emacs_var_dict): """set new links and nodes""" new_link_str = emacs_var_dict['links'] new_links = new_link_str.split(";") new_link_tpls = [i.split(" -- ") for i in new_links] links_to_add = list(set(new_links) - set(self.links)) links_to_del = list(set(self.links) - set(new_links)) # setting new stuff self.links = new_links new_nodes = [] for tpl in new_link_tpls: new_nodes.append(tpl[0]) new_nodes.append(tpl[1]) new_nodes_unique = list(set(new_nodes)) nodes_to_del = list(set(self.node_names) - set(new_nodes_unique)) nodes_to_add = list(set(new_nodes_unique) - set(self.node_names)) dumper([ "nodes_to_add: ", nodes_to_add, "nodes_to_del: ", nodes_to_del, "links_to_add: ", links_to_add, "links_to_del: ", links_to_del ]) # first add nodes + index them, but not there yet (first links) for n in nodes_to_add: dumper(['adding node']) v = self.g.add_vertex() # how to new nodes pos to parents? separate loop afterwards self.vd[n] = int(v) self.g_id[v] = n del_node_ids = [self.vd[i] for i in nodes_to_del] self.g.remove_vertex(del_node_ids) # have to reindex after deletion self.vd = {} for i in self.g.vertices(): self.vd[self.g_id[i]] = int(i) dumper(['node deleted']) # nodes_to_del_id = # dumper(['old nodes deleted, add new links']) for l in links_to_add: tpl = l.split(" -- ") n0, n1 = tpl[0], tpl[1] self.g.add_edge(self.vd[n0], self.vd[n1]) # dumper(['new links added, delete old links']) for l in links_to_del: tpl = l.split(" -- ") n0 = tpl[0] n1 = tpl[1] dumper([list(self.vd.keys())]) # only remove edge when neither of nodes removed if n0 in self.vd.keys() and n1 in self.vd.keys(): self.g.remove_edge(self.g.edge(self.vd[n0], self.vd[n1])) # dumper(['graph modifications done']) # set positions of new nodes to parent nodes for n in nodes_to_add: v = self.g.vertex(self.vd[n]) v_prnt = list(v.all_neighbors())[0] self.pos_vp[v] = self.pos_vp[v_prnt] # dumper(['node positions adjusted']) self.adj = np.array([(int(i.source()), int(i.target())) for i in self.g.edges()]) self.node_names = [self.g_id[i] for i in self.g.vertices()] # dumper(['storage objects updated']) # dumper(["nbr_edges new: ", str(len([i for i in self.g.edges()]))]) # dumper(['nodes_to_add'] + nodes_to_add) # seems to work dumper(['to here']) self.recalculate_layout() dumper(['to here2']) def recalculate_layout(self): """calculate new change_array, set rwr_c counter""" dumper(['recalculating starting']) self.base_pos_ar = self.pos_vp.get_2d_array((0, 1)).T # set_dict = {'p': 2, 'max_level': 20, 'adaptive_cooling': False, # 'gamma': 1, 'theta': 1, 'cooling_step': 0.3, 'C': 0.6, 'mu_p': 1.2} # self.goal_vp = sfdp_layout(self.g, K=0.5, pos=self.pos_vp, **set_dict) self.goal_vp = fruchterman_reingold_layout(self.g, pos=self.pos_vp) goal_ar = self.goal_vp.get_2d_array([0, 1]).T self.chng_ar = (goal_ar - self.base_pos_ar) / self.step self.rwr_c = self.step dumper(["base_pos_ar: ", self.base_pos_ar]) dumper(["goal_ar: ", goal_ar]) dumper(["chng_ar: ", self.chng_ar]) dumper(['recalculating done']) def redraw_layout(self): """actually do the drawing, run multiple (step (rwr_c)) times""" self.cur_pos_ar = np.round( self.base_pos_ar + self.chng_ar * (self.step - self.rwr_c), 3) self.qt_coords = self.nolz_pos_ar(self.cur_pos_ar) self.rwr_c -= 1 self.update() # dumper(['redrawing']) # def draw_arrow(qp, p1x, p1y, p2x, p2y): def draw_arrow(self, qp, p1x, p1y, p2x, p2y, node_width): """draw arrow from p1 to rad units before p2""" # get arrow angle, counterclockwise from center -> east line # dumper(['painting time']) angle = degrees(atan2((p1y - p2y), (p1x - p2x))) # calculate attach point arw_goal_x = p2x + node_width * cos(radians(angle)) arw_goal_y = p2y + node_width * sin(radians(angle)) # calculate start point: idk how trig works but does start_px = p1x - node_width * cos(radians(angle)) start_py = p1y - node_width * sin(radians(angle)) # arrow stuff: +/- 30 deg ar1 = angle + 25 ar2 = angle - 25 arw_len = 10 # need to focus on vector from p2 to p1 ar1_x = arw_goal_x + arw_len * cos(radians(ar1)) ar1_y = arw_goal_y + arw_len * sin(radians(ar1)) ar2_x = arw_goal_x + arw_len * cos(radians(ar2)) ar2_y = arw_goal_y + arw_len * sin(radians(ar2)) # qp.drawLine(p1x, p1y, p2x, p2y) # qp.drawLine(p1x, p1y, arw_goal_x, arw_goal_y) qp.drawLine(start_px, start_py, arw_goal_x, arw_goal_y) qp.drawLine(ar1_x, ar1_y, arw_goal_x, arw_goal_y) qp.drawLine(ar2_x, ar2_y, arw_goal_x, arw_goal_y) def paintEvent(self, event): # dumper(['start painting']) node_width = 10 qp = QPainter(self) edges = [(self.qt_coords[i[0]], self.qt_coords[i[1]]) for i in self.adj] # dumper([str(i) for i in edges]) qp.setPen(QPen(Qt.green, 2, Qt.SolidLine)) # [qp.drawLine(e[0][0], e[0][1], e[1][0], e[1][1]) for e in edges] [ self.draw_arrow(qp, e[0][0], e[0][1], e[1][0], e[1][1], (node_width / 2) + 5) for e in edges ] qp.setPen(QColor(168, 34, 3)) # qp.setPen(Qt.green) qp.setFont(QFont('Decorative', 10)) [ qp.drawText(t[0][0] + node_width, t[0][1], t[1]) for t in zip(self.qt_coords, self.node_names) ] # dumper(['done painting']) qp.setPen(QPen(Qt.black, 3, Qt.SolidLine)) # qp.setBrush(QBrush(Qt.green, Qt.SolidPattern)) dumper(['painting nodes']) for i in zip(self.qt_coords, self.node_names): if self.emacs_var_dict['cur_node'] == i[1]: qp.setPen(QPen(Qt.black, 4, Qt.SolidLine)) qp.drawEllipse(i[0][0] - (node_width / 2), i[0][1] - (node_width / 2), node_width, node_width) qp.setPen(QPen(Qt.black, 3, Qt.SolidLine)) else: qp.drawEllipse(i[0][0] - (node_width / 2), i[0][1] - (node_width / 2), node_width, node_width) # qp.drawEllipse(self.c, self.c, 7, 7) # qp.end() def nolz_pos_ar(self, pos_ar_org): """normalize pos ar to window limits""" # pos_ar_org = goal_ar size = self.size() limits = [[20, size.width() - 50], [20, size.height() - 20]] x_max = max(pos_ar_org[:, 0]) x_min = min(pos_ar_org[:, 0]) y_max = max(pos_ar_org[:, 1]) y_min = min(pos_ar_org[:, 1]) # need linear maping function again pos_ar2 = pos_ar_org pos_ar2[:, 0] = (((pos_ar2[:, 0] - x_min) / (x_max - x_min)) * (limits[0][1] - limits[0][0])) + limits[0][0] pos_ar2[:, 1] = (((pos_ar2[:, 1] - y_min) / (y_max - y_min)) * (limits[1][1] - limits[1][0])) + limits[1][0] return (pos_ar2)
class SegmentationGraph(object): """ Class defining the abstract SegmentationGraph object, its attributes and implements methods common to all derived graph classes. The constructor requires the following parameters of the underlying segmentation that will be used to build the graph. Args: scale_factor_to_nm (float): pixel size in nanometers for scaling the graph scale_x (int): x axis length in pixels of the segmentation scale_y (int): y axis length in pixels of the segmentation scale_z (int): z axis length in pixels of the segmentation """ def __init__(self, scale_factor_to_nm, scale_x, scale_y, scale_z): """ Constructor. Args: scale_factor_to_nm (float): pixel size in nanometers for scaling the graph scale_x (int): x axis length in pixels of the segmentation scale_y (int): y axis length in pixels of the segmentation scale_z (int): z axis length in pixels of the segmentation Returns: None """ self.graph = Graph(directed=False) """graph_tool.Graph: a graph object storing the segmentation graph topology, geometry and properties. """ self.scale_factor_to_nm = scale_factor_to_nm """float: pixel size in nanometers for scaling the coordinates and distances in the graph """ self.scale_x = scale_x """int: x axis length in pixels of the segmentation""" self.scale_y = scale_y """int: y axis length in pixels of the segmentation""" self.scale_z = scale_z """int: z axis length in pixels of the segmentation""" # Add "internal property maps" to the graph. # vertex property for storing the xyz coordinates in nanometers of the # corresponding vertex: self.graph.vp.xyz = self.graph.new_vertex_property("vector<float>") # edge property for storing the distance in nanometers between the # connected vertices: self.graph.ep.distance = self.graph.new_edge_property("float") self.coordinates_to_vertex_index = {} """dist: a dictionary mapping the vertex coordinates in nanometers (x, y, z) to the vertex index. """ self.coordinates_pair_connected = {} """dict: a dictionary storing pairs of vertex coordinates in nanometers that are connected by an edge as a key in a tuple form ((x1, y1, z1), (x2, y2, z2)) with value True. """ @staticmethod def distance_between_voxels(voxel1, voxel2): """ Calculates and returns the Euclidean distance between two voxels. Args: voxel1 (tuple): first voxel coordinates in form of a tuple of integers of length 3 (x1, y1, z1) voxel2 (tuple): second voxel coordinates in form of a tuple of integers of length 3 (x2, y2, z2) Returns: the Euclidean distance between two voxels (float) """ if (isinstance(voxel1, tuple) and (len(voxel1) == 3) and isinstance(voxel2, tuple) and (len(voxel2) == 3)): sum_of_squared_differences = 0 for i in range(3): # for each dimension sum_of_squared_differences += (voxel1[i] - voxel2[i]) ** 2 return math.sqrt(sum_of_squared_differences) else: error_msg = ('Tuples of integers of length 3 required as first and ' 'second input.') raise pexceptions.PySegInputError( expr='distance_between_voxels (SegmentationGraph)', msg=error_msg ) def update_coordinates_to_vertex_index(self): """ Updates graph's dictionary coordinates_to_vertex_index. The dictionary maps the vertex coordinates (x, y, z) scaled in nanometers to the vertex index. It has to be updated after purging the graph, because vertices are renumbered, as well as after reading a graph from a file (e.g. before density calculation). Returns: None """ self.coordinates_to_vertex_index = {} for vd in self.graph.vertices(): [x, y, z] = self.graph.vp.xyz[vd] self.coordinates_to_vertex_index[ (x, y, z)] = self.graph.vertex_index[vd] def calculate_density(self, mask=None, target_coordinates=None, verbose=False): """ Calculates ribosome density for each membrane graph vertex. Calculates shortest geodesic distances (d) for each vertex in the graph to each reachable ribosome center mapped on the membrane given by a binary mask with coordinates in pixels or an array of coordinates in nm. Then, calculates a density measure of ribosomes at each vertex or membrane voxel: D = sum {over all reachable ribosomes} (1 / (d + 1)). Adds the density as vertex PropertyMap to the graph. Returns an array with the same shape as the underlying segmentation with the densities plus 1, in order to distinguish membrane voxels with 0 density from the background. Args: mask (numpy.ndarray, optional): a binary mask of the ribosome centers as 3D array where indices are coordinates in pixels (default None) target_coordinates (numpy.ndarray, optional): the ribosome centers coordinates in nm as 2D array in format [[x1, y1, z1], [x2, y2, z2], ...] (default None) verbose (boolean, optional): if True (default False), some extra information will be printed out Returns: a 3D numpy ndarray with the densities + 1 Note: One of the first two parameters, mask or target_coordinates, has to be given. """ import ribosome_density as rd # If a mask is given, find the set of voxels of ribosome centers mapped # on the membrane, 'target_voxels', and rescale them to nm, # 'target_coordinates': if mask is not None: if mask.shape != (self.scale_x, self.scale_y, self.scale_z): error_msg = ("Scales of the input 'mask' have to be equal to " "those set during the generation of the graph.") raise pexceptions.PySegInputError( expr='calculate_density (SegmentationGraph)', msg=error_msg ) # output as a list of tuples [(x1,y1,z1), (x2,y2,z2), ...] in pixels target_voxels = rd.get_foreground_voxels_from_mask(mask) # for rescaling have to convert to an ndarray target_ndarray_voxels = rd.tupel_list_to_ndarray_voxels( target_voxels ) # rescale to nm, output an ndarray [[x1,y1,z1], [x2,y2,z2], ...] target_ndarray_coordinates = (target_ndarray_voxels * self.scale_factor_to_nm) # convert to a list of tuples, which are in nm now target_coordinates = rd.ndarray_voxels_to_tupel_list( target_ndarray_coordinates ) # If target_coordinates are given (in nm), convert them from a numpy # ndarray to a list of tuples: elif target_coordinates is not None: target_coordinates = rd.ndarray_voxels_to_tupel_list( target_coordinates ) # Exit if the target_voxels list is empty: if len(target_coordinates) == 0: error_msg = ("No target voxels were found! Check your input " "('mask' or 'target_coordinates').") raise pexceptions.PySegInputError( expr='calculate_density (SegmentationGraph)', msg=error_msg ) print '%s target voxels' % len(target_coordinates) if verbose: print target_coordinates # Pre-filter the target coordinates to those existing in the graph # (should already all be in the graph, but just in case): target_coordinates_in_graph = [] for target_xyz in target_coordinates: if target_xyz in self.coordinates_to_vertex_index: target_coordinates_in_graph.append(target_xyz) else: error_msg = ('Target (%s, %s, %s) not inside the membrane!' % (target_xyz[0], target_xyz[1], target_xyz[2])) raise pexceptions.PySegInputWarning( expr='calculate_density (SegmentationGraph)', msg=error_msg ) print '%s target coordinates in graph' % len( target_coordinates_in_graph) if verbose: print target_coordinates_in_graph # Get all indices of the target coordinates: target_vertices_indices = [] for target_xyz in target_coordinates_in_graph: v_target_index = self.coordinates_to_vertex_index[target_xyz] target_vertices_indices.append(v_target_index) # Density calculation # Add a new vertex property to the graph, density: self.graph.vp.density = self.graph.new_vertex_property("float") # Dictionary mapping voxel coordinates (for the volume returned later) # to a list of density values falling within that voxel: voxel_to_densities = {} # For each vertex in the graph: for v_membrane in self.graph.vertices(): # Get its coordinates: membrane_xyz = self.graph.vp.xyz[v_membrane] if verbose: print ('Membrane vertex (%s, %s, %s)' % (membrane_xyz[0], membrane_xyz[1], membrane_xyz[2])) # Get a distance map with all pairs of distances between current # graph vertex (membrane_xyz) and target vertices (ribosome # coordinates): dist_map = shortest_distance(self.graph, source=v_membrane, target=target_vertices_indices, weights=self.graph.ep.distance) # Iterate over all shortest distances from the membrane vertex to # the target vertices, while calculating the density: # Initializing: membrane coordinates with no reachable ribosomes # will have a value of 0, those with reachable ribosomes > 0. density = 0 # If there is only one target voxel, dist_map is a single value - # wrap it into a list. if len(target_coordinates_in_graph) == 1: dist_map = [dist_map] for d in dist_map: if verbose: print '\tTarget vertex ...' # if unreachable, the maximum float64 is stored if d == np.finfo(np.float64).max: if verbose: print '\t\tunreachable' else: if verbose: print '\t\td = %s' % d density += 1 / (d + 1) # Add the density of the membrane vertex as a property of the # current vertex in the graph: self.graph.vp.density[v_membrane] = density # Calculate the corresponding voxel of the vertex and add the # density to the list keyed by the voxel in the dictionary: # Scaling the coordinates back from nm to voxels. (Without round # float coordinates are truncated to the next lowest integer.) voxel_x = int(round(membrane_xyz[0] / self.scale_factor_to_nm)) voxel_y = int(round(membrane_xyz[1] / self.scale_factor_to_nm)) voxel_z = int(round(membrane_xyz[2] / self.scale_factor_to_nm)) voxel = (voxel_x, voxel_y, voxel_z) if voxel in voxel_to_densities: voxel_to_densities[voxel].append(density) else: voxel_to_densities[voxel] = [density] if verbose: print '\tdensity = %s' % density if (self.graph.vertex_index[v_membrane] + 1) % 1000 == 0: now = datetime.now() print ('%s membrane vertices processed on: %s-%s-%s %s:%s:%s' % (self.graph.vertex_index[v_membrane] + 1, now.year, now.month, now.day, now.hour, now.minute, now.second)) # Initialize an array scaled like the original segmentation, which will # hold in each membrane voxel the maximal density among the # corresponding vertex coordinates in the graph plus 1 and 0 in each # background (non-membrane) voxel: densities = np.zeros((self.scale_x, self.scale_y, self.scale_z), dtype=np.float16) # The densities array membrane voxels are initialized with 1 in order to # distinguish membrane voxels from the background. for voxel in voxel_to_densities: densities[voxel[0], voxel[1], voxel[2]] = 1 + max( voxel_to_densities[voxel]) if verbose: print 'densities:\n%s' % densities return densities def graph_to_points_and_lines_polys(self, vertices=True, edges=True, verbose=False): """ Generates a VTK PolyData object from the graph with vertices as vertex-cells (containing 1 point) and edges as line-cells (containing 2 points). Args: vertices (boolean, optional): if True (default) vertices are stored a VTK PolyData object as vertex-cells edges (boolean, optional): if True (default) edges are stored a VTK PolyData object as line-cells verbose (boolean, optional): if True (default False), some extra information will be printed out Returns: - vtk.vtkPolyData with vertex-cells - vtk.vtkPolyData with edges as line-cells """ # Initialization poly_verts = vtk.vtkPolyData() poly_lines = vtk.vtkPolyData() points = vtk.vtkPoints() vertex_arrays = list() edge_arrays = list() # Vertex property arrays for prop_key in self.graph.vp.keys(): data_type = self.graph.vp[prop_key].value_type() if (data_type != 'string' and data_type != 'python::object' and prop_key != 'xyz'): if verbose: print '\nvertex property key: %s' % prop_key print 'value type: %s' % data_type if data_type[0:6] != 'vector': # scalar num_components = 1 else: # vector num_components = len( self.graph.vp[prop_key][self.graph.vertex(0)] ) array = TypesConverter().gt_to_vtk(data_type) array.SetName(prop_key) if verbose: print 'number of components: %s' % num_components array.SetNumberOfComponents(num_components) vertex_arrays.append(array) # Edge property arrays for prop_key in self.graph.ep.keys(): data_type = self.graph.ep[prop_key].value_type() if data_type != 'string' and data_type != 'python::object': if verbose: print '\nedge property key: %s' % prop_key print 'value type: %s' % data_type if data_type[0:6] != 'vector': # scalar num_components = 1 else: # vector (all edge properties so far are scalars) # num_components = len( # self.graph.ep[prop_key][self.graph.edge(0, 1)] # ) num_components = 3 if verbose: print ('Sorry, not implemented yet, assuming a vector ' 'with 3 components.') array = TypesConverter().gt_to_vtk(data_type) array.SetName(prop_key) if verbose: print 'number of components: %s' % num_components array.SetNumberOfComponents(num_components) edge_arrays.append(array) if verbose: print '\nvertex arrays length: %s' % len(vertex_arrays) print 'edge arrays length: %s' % len(edge_arrays) # Geometry lut = np.zeros(shape=self.graph.num_vertices(), dtype=np.int) for i, vd in enumerate(self.graph.vertices()): [x, y, z] = self.graph.vp.xyz[vd] points.InsertPoint(i, x, y, z) lut[self.graph.vertex_index[vd]] = i if verbose: print 'number of points: %s' % points.GetNumberOfPoints() # Topology # Vertices verts = vtk.vtkCellArray() if vertices: for vd in self.graph.vertices(): # vd = vertex descriptor verts.InsertNextCell(1) verts.InsertCellPoint(lut[self.graph.vertex_index[vd]]) for array in vertex_arrays: prop_key = array.GetName() n_comp = array.GetNumberOfComponents() data_type = self.graph.vp[prop_key].value_type() data_type = TypesConverter().gt_to_numpy(data_type) array.InsertNextTuple(self.get_vertex_prop_entry( prop_key, vd, n_comp, data_type)) if verbose: print 'number of vertex cells: %s' % verts.GetNumberOfCells() # Edges lines = vtk.vtkCellArray() if edges: for ed in self.graph.edges(): # ed = edge descriptor lines.InsertNextCell(2) lines.InsertCellPoint(lut[self.graph.vertex_index[ed.source()]]) lines.InsertCellPoint(lut[self.graph.vertex_index[ed.target()]]) for array in edge_arrays: prop_key = array.GetName() n_comp = array.GetNumberOfComponents() data_type = self.graph.ep[prop_key].value_type() data_type = TypesConverter().gt_to_numpy(data_type) array.InsertNextTuple(self.get_edge_prop_entry( prop_key, ed, n_comp, data_type)) if verbose: print 'number of line cells: %s' % lines.GetNumberOfCells() # vtkPolyData construction poly_verts.SetPoints(points) poly_lines.SetPoints(points) if vertices: poly_verts.SetVerts(verts) if edges: poly_lines.SetLines(lines) for array in vertex_arrays: poly_verts.GetCellData().AddArray(array) for array in edge_arrays: poly_lines.GetCellData().AddArray(array) return poly_verts, poly_lines def get_vertex_prop_entry(self, prop_key, vertex_descriptor, n_comp, data_type): """ Gets a property value of a vertex for inserting into a VTK vtkDataArray object. This private function is used by the methods graph_to_points_and_lines_polys and graph_to_triangle_poly (the latter of the derived class surface_graphs.TriangleGraph). Args: prop_key (str): name of the desired vertex property vertex_descriptor (graph_tool.Vertex): vertex descriptor of the current vertex n_comp (int): number of components of the array (length of the output tuple) data_type: numpy data type converted from a graph-tool property value type by TypesConverter().gt_to_numpy Returns: a tuple (with length like n_comp) with the property value of the vertex converted to a numpy data type """ prop = list() if n_comp == 1: prop.append(data_type(self.graph.vp[prop_key][vertex_descriptor])) else: for i in range(n_comp): prop.append(data_type( self.graph.vp[prop_key][vertex_descriptor][i])) return tuple(prop) def get_edge_prop_entry(self, prop_key, edge_descriptor, n_comp, data_type): """ Gets a property value of an edge for inserting into a VTK vtkDataArray object. This private function is used by the method graph_to_points_and_lines_polys. Args: prop_key (str): name of the desired vertex property edge_descriptor (graph_tool.Edge): edge descriptor of the current edge n_comp (int): number of components of the array (length of the output tuple) data_type: numpy data type converted from a graph-tool property value type by TypesConverter().gt_to_numpy Returns: a tuple (with length like n_comp) with the property value of the edge converted to a numpy data type """ prop = list() if n_comp == 1: prop.append(data_type(self.graph.ep[prop_key][edge_descriptor])) else: for i in range(n_comp): prop.append(data_type( self.graph.ep[prop_key][edge_descriptor][i])) return tuple(prop) # * The following SegmentationGraph methods are needed for the normal vector # voting algorithm. * def calculate_average_edge_length(self, prop_e=None, value=1): """ Calculates the average edge length in the graph. If a special edge property is specified, includes only the edges where this property equals the given value. If there are no edges in the graph, the given property does not exist or there are no edges with the given property equaling the given value, None is returned. Args: prop_e (str, optional): edge property, if specified only edges where this property equals the given value will be considered value (int, optional): value of the specified edge property an edge has to have in order to be considered (default 1) Returns: the average edge length in the graph (float) or None """ total_edge_length = 0 average_edge_length = None if prop_e is None: print "Considering all edges:" for ed in self.graph.edges(): total_edge_length += self.graph.ep.distance[ed] if self.graph.num_edges() > 0: average_edge_length = total_edge_length / self.graph.num_edges() else: print "There are no edges in the graph!" elif prop_e in self.graph.edge_properties: print ("Considering only edges with property %s equaling value %s " % (prop_e, value)) num_special_edges = 0 for ed in self.graph.edges(): if self.graph.edge_properties[prop_e][ed] == value: num_special_edges += 1 total_edge_length += self.graph.ep.distance[ed] if num_special_edges > 0: average_edge_length = total_edge_length / num_special_edges else: print ("There are no edges with the property %s equaling value " "%s!" % (prop_e, value)) print "Average length: %s" % average_edge_length return average_edge_length def find_geodesic_neighbors(self, v, g_max, verbose=False): """ Finds geodesic neighbor vertices of a given vertex v in the graph that are within a given maximal geodesic distance g_max from it. Also finds the corresponding geodesic distances. All edges are considered. Args: v (graph_tool.Vertex): the source vertex g_max: maximal geodesic distance (in nanometers, if the graph was scaled) verbose (boolean, optional): if True (default False), some extra information will be printed out Returns: a dictionary mapping a neighbor vertex index to the geodesic distance from vertex v """ dist_v = shortest_distance(self.graph, source=v, target=None, weights=self.graph.ep.distance, max_dist=g_max) dist_v = dist_v.get_array() neighbor_id_to_dist = dict() idxs = np.where(dist_v <= g_max)[0] for idx in idxs: dist = dist_v[idx] if dist != 0: # ignore the source vertex itself neighbor_id_to_dist[idx] = dist if verbose: print "%s neighbors" % len(neighbor_id_to_dist) return neighbor_id_to_dist def get_vertex_property_array(self, property_name): """ Gets a numpy array with all values of a vertex property of the graph, printing out the number of values, the minimal and the maximal value. Args: property_name (str): vertex property name Returns: an array (numpy.ndarray) with all values of the vertex property """ if (isinstance(property_name, str) and property_name in self.graph.vertex_properties): values = self.graph.vertex_properties[property_name].get_array() print '%s "%s" values' % (len(values), property_name) print 'min = %s, max = %s' % (min(values), max(values)) return values else: error_msg = ('The input "%s" is not a str object or is not found ' 'in vertex properties of the graph.' % property_name) raise pexceptions.PySegInputError( expr='get_vertex_property_array (SegmentationGraph)', msg=error_msg)
alternate_path = child_graph.new_edge_property("int") flag_path = child_graph.new_edge_property("int") ## Property Assignment child_graph.gp.layer_name = graph_name child_graph.ep.edge_capacity = layer_capacities child_graph.ep.residual_capacity = layer_res_capacity child_graph.ep.edge_flow = layer_flow child_graph.ep.shared_path = alternate_path child_graph.ep.path_flag = flag_path ## Setting the name of the graph child_graph.gp.layer_name = "Layer_" + str(i) ## For finding the total number of shared edges in use ## for e in child_graph.edges(): child_graph.ep.shared_path[e] = 1 ## Adding layered graphs to a list ## all_child_graphs.append(child_graph) ######## Variables and instances to be used for finding the primary and the alternate paths ######## paths = [] alternative_paths =[] s = Stack() routes_in_use = {} all_requests_n_paths = {} substitute_paths = {}
class SegmentationGraph(object): """ Class defining the abstract SegmentationGraph object, its attributes and implements methods common to all derived graph classes. The constructor requires the following parameters of the underlying segmentation that will be used to build the graph. """ def __init__(self): """ Constructor of the abstract SegmentationGraph object. Returns: None """ self.graph = Graph(directed=False) """graph_tool.Graph: a graph object storing the segmentation graph topology, geometry and properties (initially empty). """ # Add "internal property maps" to the graph. # vertex property for storing the xyz coordinates of the corresponding # vertex: self.graph.vp.xyz = self.graph.new_vertex_property("vector<float>") # edge property for storing the distance between the connected vertices: self.graph.ep.distance = self.graph.new_edge_property("float") self.coordinates_to_vertex_index = {} """dict: a dictionary mapping the vertex coordinates (x, y, z) to the vertex index. """ self.coordinates_pair_connected = set() """set: a set storing pairs of vertex coordinates that are connected by an edge in a tuple form ((x1, y1, z1), (x2, y2, z2)). """ @staticmethod def distance_between_voxels(voxel1, voxel2): """ Calculates and returns the Euclidean distance between two voxels. Args: voxel1 (tuple): first voxel coordinates in form of a tuple of floats of length 3 (x1, y1, z1) voxel2 (tuple): second voxel coordinates in form of a tuple of floats of length 3 (x2, y2, z2) Returns: the Euclidean distance between two voxels (float) """ if (isinstance(voxel1, tuple) and (len(voxel1) == 3) and isinstance(voxel2, tuple) and (len(voxel2) == 3)): sum_of_squared_differences = 0 for i in range(3): # for each dimension sum_of_squared_differences += (voxel1[i] - voxel2[i])**2 return math.sqrt(sum_of_squared_differences) else: raise pexceptions.PySegInputError( expr='distance_between_voxels (SegmentationGraph)', msg=('Tuples of integers of length 3 required as first and ' 'second input.')) def update_coordinates_to_vertex_index(self): """ Updates graph's dictionary coordinates_to_vertex_index. The dictionary maps the vertex coordinates (x, y, z) to the vertex index. It has to be updated after purging the graph, because vertices are renumbered, as well as after reading a graph from a file (e.g. before density calculation). Returns: None """ self.coordinates_to_vertex_index = {} for vd in self.graph.vertices(): [x, y, z] = self.graph.vp.xyz[vd] self.coordinates_to_vertex_index[(x, y, z)] = self.graph.vertex_index[vd] def calculate_density(self, size, scale, mask=None, target_coordinates=None, verbose=False): """ Calculates ribosome density for each membrane graph vertex. Calculates shortest geodesic distances (d) for each vertex in the graph to each reachable ribosome center mapped on the membrane given by a binary mask with coordinates in pixels or an array of coordinates in given units. Then, calculates a density measure of ribosomes at each vertex or membrane voxel: D = sum {over all reachable ribosomes} (1 / (d + 1)). Adds the density as vertex PropertyMap to the graph. Returns an array with the same shape as the underlying segmentation with the densities plus 1, in order to distinguish membrane voxels with 0 density from the background. Args: size (tuple): size in voxels (X, Y, Z) of the original segmentation scale (tuple): pixel size (X, Y, Z) in given units of the original segmentation mask (numpy.ndarray, optional): a binary mask of the ribosome centers as 3D array where indices are coordinates in pixels (default None) target_coordinates (numpy.ndarray, optional): the ribosome centers coordinates in given units as 2D array in format [[x1, y1, z1], [x2, y2, z2], ...] (default None) verbose (boolean, optional): if True (default False), some extra information will be printed out Returns: a 3D numpy ndarray with the densities + 1 Note: One of the two parameters, mask or target_coordinates, has to be given. """ from . import ribosome_density as rd # If a mask is given, find the set of voxels of ribosome centers mapped # on the membrane, 'target_voxels', and rescale them to units, # 'target_coordinates': if mask is not None: if mask.shape != size: raise pexceptions.PySegInputError( expr='calculate_density (SegmentationGraph)', msg=("Size of the input 'mask' have to be equal to those " "set during the generation of the graph.")) # output as a list of tuples [(x1,y1,z1), (x2,y2,z2), ...] in pixels target_voxels = rd.get_foreground_voxels_from_mask(mask) # for rescaling have to convert to an ndarray target_ndarray_voxels = rd.tupel_list_to_ndarray_voxels( target_voxels) # rescale to units, output an ndarray [[x1,y1,z1], [x2,y2,z2], ...] target_ndarray_coordinates = (target_ndarray_voxels * np.asarray(scale)) # convert to a list of tuples, which are in units now target_coordinates = rd.ndarray_voxels_to_tupel_list( target_ndarray_coordinates) # If target_coordinates are given (in units), convert them from a numpy # ndarray to a list of tuples: elif target_coordinates is not None: target_coordinates = rd.ndarray_voxels_to_tupel_list( target_coordinates) # Exit if the target_voxels list is empty: if len(target_coordinates) == 0: raise pexceptions.PySegInputError( expr='calculate_density (SegmentationGraph)', msg="No target voxels were found! Check your input ('mask' or " "'target_coordinates').") print('{} target voxels'.format(len(target_coordinates))) if verbose: print(target_coordinates) # Pre-filter the target coordinates to those existing in the graph # (should already all be in the graph, but just in case): target_coordinates_in_graph = [] for target_xyz in target_coordinates: if target_xyz in self.coordinates_to_vertex_index: target_coordinates_in_graph.append(target_xyz) else: raise pexceptions.PySegInputWarning( expr='calculate_density (SegmentationGraph)', msg=('Target ({}, {}, {}) not inside the membrane!'.format( target_xyz[0], target_xyz[1], target_xyz[2]))) print('{} target coordinates in graph'.format( len(target_coordinates_in_graph))) if verbose: print(target_coordinates_in_graph) # Get all indices of the target coordinates: target_vertices_indices = [] for target_xyz in target_coordinates_in_graph: v_target_index = self.coordinates_to_vertex_index[target_xyz] target_vertices_indices.append(v_target_index) # Density calculation # Add a new vertex property to the graph, density: self.graph.vp.density = self.graph.new_vertex_property("float") # Dictionary mapping voxel coordinates (for the volume returned later) # to a list of density values falling within that voxel: voxel_to_densities = {} # For each vertex in the graph: for v_membrane in self.graph.vertices(): # Get its coordinates: membrane_xyz = self.graph.vp.xyz[v_membrane] if verbose: print('Membrane vertex ({}, {}, {})'.format( membrane_xyz[0], membrane_xyz[1], membrane_xyz[2])) # Get a distance map with all pairs of distances between current # graph vertex (membrane_xyz) and target vertices (ribosome # coordinates): dist_map = shortest_distance(self.graph, source=v_membrane, target=target_vertices_indices, weights=self.graph.ep.distance) # Iterate over all shortest distances from the membrane vertex to # the target vertices, while calculating the density: # Initializing: membrane coordinates with no reachable ribosomes # will have a value of 0, those with reachable ribosomes > 0. density = 0 # If there is only one target voxel, dist_map is a single value - # wrap it into a list. if len(target_coordinates_in_graph) == 1: dist_map = [dist_map] for d in dist_map: if verbose: print('\tTarget vertex ...') # if unreachable, the maximum float64 is stored if d == np.finfo(np.float64).max: if verbose: print('\t\tunreachable') else: if verbose: print('\t\td = {}'.format(d)) density += 1 / (d + 1) # Add the density of the membrane vertex as a property of the # current vertex in the graph: self.graph.vp.density[v_membrane] = density # Calculate the corresponding voxel of the vertex and add the # density to the list keyed by the voxel in the dictionary: # Scaling the coordinates back from units to voxels. (Without round # float coordinates are truncated to the next lowest integer.) voxel_x = int(round(membrane_xyz[0] / scale[0])) voxel_y = int(round(membrane_xyz[1] / scale[1])) voxel_z = int(round(membrane_xyz[2] / scale[2])) voxel = (voxel_x, voxel_y, voxel_z) if voxel in voxel_to_densities: voxel_to_densities[voxel].append(density) else: voxel_to_densities[voxel] = [density] if verbose: print('\tdensity = {}'.format(density)) if (self.graph.vertex_index[v_membrane] + 1) % 1000 == 0: now = datetime.now() print('{} membrane vertices processed on: {}-{}-{} {}:{}:{}'. format(self.graph.vertex_index[v_membrane] + 1, now.year, now.month, now.day, now.hour, now.minute, now.second)) # Initialize an array scaled like the original segmentation, which will # hold in each membrane voxel the maximal density among the # corresponding vertex coordinates in the graph plus 1 and 0 in each # background (non-membrane) voxel: densities = np.zeros(size, dtype=np.float16) # The densities array membrane voxels are initialized with 1 in order to # distinguish membrane voxels from the background. for voxel in voxel_to_densities: densities[voxel[0], voxel[1], voxel[2]] = 1 + max(voxel_to_densities[voxel]) if verbose: print('densities:\n{}'.format(densities)) return densities def graph_to_points_and_lines_polys(self, vertices=True, edges=True, verbose=False): """ Generates a VTK PolyData object from the graph with vertices as vertex-cells (containing 1 point) and edges as line-cells (containing 2 points). Args: vertices (boolean, optional): if True (default) vertices are stored a VTK PolyData object as vertex-cells edges (boolean, optional): if True (default) edges are stored a VTK PolyData object as line-cells verbose (boolean, optional): if True (default False), some extra information will be printed out Returns: - vtk.vtkPolyData with vertex-cells - vtk.vtkPolyData with edges as line-cells """ # Initialization poly_verts = vtk.vtkPolyData() poly_lines = vtk.vtkPolyData() points = vtk.vtkPoints() vertex_arrays = list() edge_arrays = list() # Vertex property arrays for prop_key in list(self.graph.vp.keys()): data_type = self.graph.vp[prop_key].value_type() if (data_type != 'string' and data_type != 'python::object' and prop_key != 'xyz'): if verbose: print('\nvertex property key: {}'.format(prop_key)) print('value type: {}'.format(data_type)) if data_type[0:6] != 'vector': # scalar num_components = 1 else: # vector num_components = len( self.graph.vp[prop_key][self.graph.vertex(0)]) array = TypesConverter().gt_to_vtk(data_type) array.SetName(prop_key) if verbose: print('number of components: {}'.format(num_components)) array.SetNumberOfComponents(num_components) vertex_arrays.append(array) # Edge property arrays for prop_key in list(self.graph.ep.keys()): data_type = self.graph.ep[prop_key].value_type() if data_type != 'string' and data_type != 'python::object': if verbose: print('\nedge property key: {}'.format(prop_key)) print('value type: {}'.format(data_type)) if data_type[0:6] != 'vector': # scalar num_components = 1 else: # vector (all edge properties so far are scalars) # num_components = len( # self.graph.ep[prop_key][self.graph.edge(0, 1)]) num_components = 3 if verbose: print('Sorry, not implemented yet, assuming a vector ' 'with 3 components.') array = TypesConverter().gt_to_vtk(data_type) array.SetName(prop_key) if verbose: print('number of components: {}'.format(num_components)) array.SetNumberOfComponents(num_components) edge_arrays.append(array) if verbose: print('\nvertex arrays length: {}'.format(len(vertex_arrays))) print('edge arrays length: {}'.format(len(edge_arrays))) # Geometry lut = np.zeros(shape=self.graph.num_vertices(), dtype=np.int) for i, vd in enumerate(self.graph.vertices()): [x, y, z] = self.graph.vp.xyz[vd] points.InsertPoint(i, x, y, z) lut[self.graph.vertex_index[vd]] = i if verbose: print('number of points: {}'.format(points.GetNumberOfPoints())) # Topology # Vertices verts = vtk.vtkCellArray() if vertices: for vd in self.graph.vertices(): # vd = vertex descriptor verts.InsertNextCell(1) verts.InsertCellPoint(lut[self.graph.vertex_index[vd]]) for array in vertex_arrays: prop_key = array.GetName() n_comp = array.GetNumberOfComponents() data_type = self.graph.vp[prop_key].value_type() data_type = TypesConverter().gt_to_numpy(data_type) array.InsertNextTuple( self.get_vertex_prop_entry(prop_key, vd, n_comp, data_type)) if verbose: print('number of vertex cells: {}'.format( verts.GetNumberOfCells())) # Edges lines = vtk.vtkCellArray() if edges: for ed in self.graph.edges(): # ed = edge descriptor lines.InsertNextCell(2) lines.InsertCellPoint( lut[self.graph.vertex_index[ed.source()]]) lines.InsertCellPoint( lut[self.graph.vertex_index[ed.target()]]) for array in edge_arrays: prop_key = array.GetName() n_comp = array.GetNumberOfComponents() data_type = self.graph.ep[prop_key].value_type() data_type = TypesConverter().gt_to_numpy(data_type) array.InsertNextTuple( self.get_edge_prop_entry(prop_key, ed, n_comp, data_type)) if verbose: print('number of line cells: {}'.format( lines.GetNumberOfCells())) # vtkPolyData construction poly_verts.SetPoints(points) poly_lines.SetPoints(points) if vertices: poly_verts.SetVerts(verts) if edges: poly_lines.SetLines(lines) for array in vertex_arrays: poly_verts.GetCellData().AddArray(array) for array in edge_arrays: poly_lines.GetCellData().AddArray(array) return poly_verts, poly_lines def get_vertex_prop_entry(self, prop_key, vertex_descriptor, n_comp, data_type): """ Gets a property value of a vertex for inserting into a VTK vtkDataArray object. This function is used by the methods graph_to_points_and_lines_polys and graph_to_triangle_poly (the latter of the derived classes PointGraph and TriangleGraph (in surface_graphs). Args: prop_key (str): name of the desired vertex property vertex_descriptor (graph_tool.Vertex): vertex descriptor of the current vertex n_comp (int): number of components of the array (length of the output tuple) data_type: numpy data type converted from a graph-tool property value type by TypesConverter().gt_to_numpy Returns: a tuple (with length like n_comp) with the property value of the vertex converted to a numpy data type """ prop = list() if n_comp == 1: prop.append(data_type(self.graph.vp[prop_key][vertex_descriptor])) else: for i in range(n_comp): prop.append( data_type(self.graph.vp[prop_key][vertex_descriptor][i])) return tuple(prop) def get_edge_prop_entry(self, prop_key, edge_descriptor, n_comp, data_type): """ Gets a property value of an edge for inserting into a VTK vtkDataArray object. This private function is used by the method graph_to_points_and_lines_polys. Args: prop_key (str): name of the desired vertex property edge_descriptor (graph_tool.Edge): edge descriptor of the current edge n_comp (int): number of components of the array (length of the output tuple) data_type: numpy data type converted from a graph-tool property value type by TypesConverter().gt_to_numpy Returns: a tuple (with length like n_comp) with the property value of the edge converted to a numpy data type """ prop = list() if n_comp == 1: prop.append(data_type(self.graph.ep[prop_key][edge_descriptor])) else: for i in range(n_comp): prop.append( data_type(self.graph.ep[prop_key][edge_descriptor][i])) return tuple(prop) # * The following SegmentationGraph methods are needed for the normal vector # voting algorithm. * def calculate_average_edge_length(self, prop_e=None, value=1, verbose=False): """ Calculates the average edge length in the graph. If a special edge property is specified, includes only the edges where this property equals the given value. If there are no edges in the graph, the given property does not exist or there are no edges with the given property equaling the given value, None is returned. Args: prop_e (str, optional): edge property, if specified only edges where this property equals the given value will be considered value (int, optional): value of the specified edge property an edge has to have in order to be considered (default 1) verbose (boolean, optional): if True (default False), some extra information will be printed out Returns: the average edge length in the graph (float) or None """ total_edge_length = 0 average_edge_length = None if prop_e is None: if verbose: print("Considering all edges:") if self.graph.num_edges() > 0: if verbose: print("{} edges".format(self.graph.num_edges())) average_edge_length = np.mean(self.graph.ep.distance.a) else: print("There are no edges in the graph!") elif prop_e in self.graph.edge_properties: if verbose: print("Considering only edges with property {} equaling value " "{}!".format(prop_e, value)) num_special_edges = 0 for ed in self.graph.edges(): if self.graph.edge_properties[prop_e][ed] == value: num_special_edges += 1 total_edge_length += self.graph.ep.distance[ed] if num_special_edges > 0: if verbose: print("{} such edges".format(num_special_edges)) average_edge_length = total_edge_length / num_special_edges else: print("There are no edges with the property {} equaling value " "{}!".format(prop_e, value)) if verbose: print("Average length: {}".format(average_edge_length)) return average_edge_length def find_geodesic_neighbors(self, v, g_max, full_dist_map=None, only_surface=False, verbose=False): """ Finds geodesic neighbor vertices of a given vertex v in the graph that are within a given maximal geodesic distance g_max from it. Also finds the corresponding geodesic distances. All edges are considered. The distances are calculated with Dijkstra's algorithm. Args: v (graph_tool.Vertex): the source vertex g_max: maximal geodesic distance (in the units of the graph) full_dist_map (graph_tool.PropertyMap, optional): the full distance map for the whole graph; if None, a local distance map is calculated for each vertex (default) only_surface (boolean, optional): if True (default False), only neighbors classified as surface patch (class 1) are considered verbose (boolean, optional): if True (default False), some extra information will be printed out Returns: a dictionary mapping a neighbor vertex index to the geodesic distance from vertex v """ if full_dist_map is not None: dist_v = full_dist_map[v].get_array() else: dist_v = shortest_distance(self.graph, source=v, target=None, weights=self.graph.ep.distance, max_dist=g_max) dist_v = dist_v.get_array() # numpy array of distances from v to all vertices, in vertex index order vertex = self.graph.vertex orientation_class = self.graph.vp.orientation_class neighbor_id_to_dist = dict() idxs = np.where(dist_v <= g_max)[0] # others are INF for idx in idxs: dist = dist_v[idx] if dist != 0: # ignore the source vertex itself v_i = vertex(idx) if (not only_surface) or orientation_class[v_i] == 1: neighbor_id_to_dist[idx] = dist if verbose: print("{} neighbors".format(len(neighbor_id_to_dist))) return neighbor_id_to_dist def find_geodesic_neighbors_exact(self, o, g_max, only_surface=False, verbose=False, debug=False): """ Finds geodesic neighbor vertices of the origin vertex o in the graph that are within a given maximal geodesic distance g_max from it. Also finds the corresponding geodesic distances. All edges and faces are considered. The distances are calculated with Sun's and Abidi's algorithm, a simplification of Kimmels' and Sethian's fast marching algorithm. Args: o (graph_tool.Vertex): the source vertex g_max: maximal geodesic distance (in the units of the graph) only_surface (boolean, optional): if True (default False), only neighbors classified as surface patch (class 1) are considered verbose (boolean, optional): if True (default False), some extra information will be printed out debug (boolean, optional): if True (default False), some more extra information will be printed out Returns: a dictionary mapping a neighbor vertex index to the geodesic distance from vertex o """ # Shortcuts xyz = self.graph.vp.xyz vertex = self.graph.vertex orientation_class = self.graph.vp.orientation_class distance_between_voxels = self.distance_between_voxels calculate_geodesic_distance = self._calculate_geodesic_distance insert_geo_dist_vertex_id = self._insert_geo_dist_vertex_id # Initialization geo_dist_heap = [] # heap has the smallest geodesic distance first # dictionaries to keep track which geodesic distance belongs to which # vertex or vertices and vice versa geo_dist_to_vertex_ids = {} vertex_id_to_geo_dist = {} neighbor_id_to_dist = {} # output dictionary # Tag the center point (o) as Alive: self.graph.vp.tag = self.graph.new_vertex_property("string") tag = self.graph.vp.tag # shortcut tag[o] = "Alive" if debug: print("Vertex o={}: Alive".format(int(o))) vertex_id_to_geo_dist[int(o)] = 0 # need it for geo. dist. calculation xyz_o = tuple(xyz[o]) for n in o.all_neighbours(): # Tag all neighboring points of the center point (n) as Close tag[n] = "Close" # Geodesic distance in this case = Euclidean between o and n xyz_n = tuple(xyz[n]) on = distance_between_voxels(xyz_o, xyz_n) if debug: print("Vertex n={}: Close with distance {}".format(int(n), on)) heappush(geo_dist_heap, on) insert_geo_dist_vertex_id(geo_dist_to_vertex_ids, on, int(n)) vertex_id_to_geo_dist[int(n)] = on # Repeat while the smallest distance is <= g_max while len(geo_dist_heap) >= 1 and geo_dist_heap[0] <= g_max: if debug: print("\n{} distances in heap, first={}".format( len(geo_dist_heap), geo_dist_heap[0])) # 1. Change the tag of the point in Close with the smallest # geodesic distance (a) from Close to Alive smallest_geo_dist = heappop(geo_dist_heap) closest_vertices_ids = geo_dist_to_vertex_ids[smallest_geo_dist] a = vertex(closest_vertices_ids[0]) if len(closest_vertices_ids) > 1: # move the first one (a) to the # back, so it's not taken again next time closest_vertices_ids.pop(0) closest_vertices_ids.append(int(a)) tag[a] = "Alive" # only proceed if a is a surface patch: if only_surface and orientation_class[a] != 1: continue neighbor_id_to_dist[int(a)] = smallest_geo_dist # add a to output if debug: print("Vertex a={}: Alive".format(int(a))) neighbors_a = set(a.all_neighbours()) # actually don't have # duplicates, but like this can use fast sets' intersection method for c in neighbors_a: # 2. Tag all neighboring points (c) of this point as Close, # but skip those which are Alive already if tag[c] == "Alive": if debug: print("Skipping Alive neighbor {}".format(int(c))) continue tag[c] = "Close" if debug: print("Vertex c={}: Close".format(int(c))) # 3. Recompute the geodesic distance of these neighboring # points, using only values of points that are Alive, and renew # it only if the recomputed result is smaller # Find Alive point b, belonging to the same triangle as a and c: # iterate over an intersection of the neighbors of a and c neighbors_c = set(c.all_neighbours()) common_neighbors_a_c = neighbors_a.intersection(neighbors_c) for b in common_neighbors_a_c: # check if b is tagged Alive if tag[b] == "Alive": if debug: print("\tUsing vertex b={}".format(int(b))) new_geo_dist_c = calculate_geodesic_distance( a, b, xyz[c].a, vertex_id_to_geo_dist, verbose=verbose) if int(c) not in vertex_id_to_geo_dist: # add c if debug: print("\tadding new distance {}".format( new_geo_dist_c)) vertex_id_to_geo_dist[int(c)] = new_geo_dist_c heappush(geo_dist_heap, new_geo_dist_c) insert_geo_dist_vertex_id(geo_dist_to_vertex_ids, new_geo_dist_c, int(c)) else: old_geo_dist_c = vertex_id_to_geo_dist[int(c)] if new_geo_dist_c < old_geo_dist_c: # update c if debug: print( "\tupdating distance {} to {}".format( old_geo_dist_c, new_geo_dist_c)) vertex_id_to_geo_dist[int(c)] = new_geo_dist_c if old_geo_dist_c in geo_dist_heap: # check because it is sometimes not there geo_dist_heap.remove(old_geo_dist_c) heappush(geo_dist_heap, new_geo_dist_c) old_geo_dist_vertex_ids = geo_dist_to_vertex_ids[ old_geo_dist_c] if len(old_geo_dist_vertex_ids) == 1: del geo_dist_to_vertex_ids[old_geo_dist_c] else: old_geo_dist_vertex_ids.remove(int(c)) insert_geo_dist_vertex_id( geo_dist_to_vertex_ids, new_geo_dist_c, int(c)) elif debug: print("\tkeeping the old distance={}, because " "it's <= the new={}".format( old_geo_dist_c, new_geo_dist_c)) # if debug: # print(geo_dist_heap) # print(geo_dist_to_vertex_ids) # print(vertex_id_to_geo_dist) # print(neighbor_id_to_dist) break # one Alive b is expected, stop iteration else: if debug: print("\tNo common neighbors of a and c are Alive") del self.graph.vertex_properties["tag"] if debug: print("Vertex o={} has {} geodesic neighbors".format( int(o), len(neighbor_id_to_dist))) if verbose: print("{} neighbors".format(len(neighbor_id_to_dist))) return neighbor_id_to_dist def _calculate_geodesic_distance(self, a, b, xyz_c, vertex_id_to_geo_dist, verbose=False): geo_dist_a = vertex_id_to_geo_dist[int(a)] geo_dist_b = vertex_id_to_geo_dist[int(b)] xyz_a = self.graph.vp.xyz[a].a xyz_b = self.graph.vp.xyz[b].a ab = euclidean_distance(xyz_a, xyz_b) ac = euclidean_distance(xyz_a, xyz_c) bc = euclidean_distance(xyz_b, xyz_c) # maybe faster to use linalg.euclidean_distance directly on np.ndarrays alpha = nice_acos((ab**2 + ac**2 - bc**2) / (2 * ab * ac)) beta = nice_acos((ab**2 + bc**2 - ac**2) / (2 * ab * bc)) if alpha < (math.pi / 2) and beta < (math.pi / 2): if verbose: print("\ttriangle abc is acute") theta = nice_acos((geo_dist_a**2 + ab**2 - geo_dist_b**2) / (2 * ab * geo_dist_a)) geo_dist_c = math.sqrt(ac**2 + geo_dist_a**2 - 2 * ac * geo_dist_a * math.cos(alpha + theta)) else: if verbose: print("\ttriangle abc is obtuse") geo_dist_c = min(geo_dist_a + ac, geo_dist_b + bc) return geo_dist_c @staticmethod def _insert_geo_dist_vertex_id(geo_dist_to_vertices, geo_dist, vertex_ind): if geo_dist in geo_dist_to_vertices: geo_dist_to_vertices[geo_dist].append(vertex_ind) else: geo_dist_to_vertices[geo_dist] = [vertex_ind] def get_vertex_property_array(self, property_name): """ Gets a numpy array with all values of a vertex property of the graph, printing out the number of values, the minimal and the maximal value. Args: property_name (str): vertex property name Returns: an array (numpy.ndarray) with all values of the vertex property """ if (isinstance(property_name, str) and property_name in self.graph.vertex_properties): values = np.array( self.graph.vertex_properties[property_name].get_array()) print('{} "{}" values'.format(len(values), property_name)) print('min = {}, max = {}, mean = {}'.format( min(values), max(values), np.mean(values))) return values else: raise pexceptions.PySegInputError( expr='get_vertex_property_array (SegmentationGraph)', msg=('The input "{}" is not a str object or is not found in ' 'vertex properties of the graph.'.format(property_name)))
print('fraciton of nodes in largest cc: {}'.format(f)) prop_question_id = g.new_vertex_property('int') prop_question_id.a = np.array(list(id2q_map.values())) # focus on largest CC g.set_vertex_filter(vfilt) # re-index the graph # SO qustion: https://stackoverflow.com/questions/46264296/graph-tool-re-index-vertex-ids-to-be-consecutive-integers n2i = {n: i for i, n in enumerate(g.vertices())} i2n = dict(zip(n2i.values(), n2i.keys())) new_g = Graph() new_g.add_edge_list([(n2i[e.source()], n2i[e.target()]) for e in g.edges()]) # update question ids new_prop_question_id = new_g.new_vertex_property('int') new_prop_question_id.a = [prop_question_id[i2n[i]] for i in range(new_g.num_vertices())] new_g.vertex_properties['question_id'] = new_prop_question_id print('saving largest CC in graph') new_g.save('{}/question_graph.gt'.format(data_dir)) print('saving connected_question_ids') pkl.dump(list(new_prop_question_id.a), open('{}/connected_question_ids.pkl'.format(data_dir), 'wb'))