def test_parallel_segments_must_be_1_5_4_5_and_1_1_4_1_for_1_3_4_3_at_2_distance( self): segment = geometry.Segment(1, 3, 4, 3) s1, s2 = segment.parallel_segments(2) self.assertEqual((s1._x1, s1._y1, s1._x2, s1._y2), (1, 5, 4, 5)) self.assertEqual((s2._x1, s2._y1, s2._x2, s2._y2), (1, 1, 4, 1))
def build_laguerre(gc, trios): """ Build the Laguerre diagram from a cloud of points and its given triangulation """ # Update the triangulation gc.update_triangulation(trios) trios = gc.trios # Lines connecting centers lcc = LinesConnectingCenters(gc) lcc.build() # Power lines pl = PowerLines(gc, lcc) pl.build() # Possible Vertices (PV) # Intersections among power lines xi, yi = geo.intersections_many_lines([pl]) lpairs, xi = alg.one_side(xi) lpairs, yi = alg.one_side(yi) # Determine the group of circles for each possible vertex pvnc = len(yi) gcpvc = pl.pairs[lpairs] gcpvc = gcpvc.reshape((pvnc, 4)) gcpvc = np.sort(gcpvc) lgp = np.array(list(map(lambda l: len(set(l)), gcpvc))) keep_these = np.where(lgp == 3) xi = xi[keep_these] yi = yi[keep_these] lpairs = lpairs[keep_these] gcpvc = gcpvc[keep_these] pvnc = len(xi) gcpvc = np.array(list(map(lambda l: sorted(list(set(l))), gcpvc))) # Now I have to iterate over trios # Coordinates of the possible vertex for each triangle xpv = {} ypv = {} # Segment for each pair segments = {} # Number of PV points for each pair pvpts = {} # One (if the pair is on the boundary) or two trios per pair pairs_trios = {} # Other pairs of the quad whose diagonal is a certain pair pairs_oprs = {} # Other points of the quad whose diagonal is a certain pair pairs_opts = {} # All points of the quad whose diagonal is a certain pair pairs_allpts = {} # Iterate for trio in trios: trio_extended = np.tile(trio, pvnc).reshape(gcpvc.shape) matches = np.equal(gcpvc, trio_extended) matches = np.all(matches, axis=1) matches = np.where(matches == True)[0] matches = np.unique(matches) xim = xi[matches] yim = yi[matches] # Now reduce the duplicated points for this triangle if len(xim) > 1: xim, yim = remove_repeated_points(xim, yim) # Store them if len(xim) > 0: tt = tuple(trio) xim, yim = xim.mean(), yim.mean() pairs_sorted = [ tuple(sorted(item)) for item in gc.triangles[tt].pairs ] # Pairs for pair in pairs_sorted: other_pairs = [item for item in pairs_sorted if item != pair] try: segments[pair].append((xim, yim)) pvpts[pair] += 1 except KeyError: segments[pair] = [(xim, yim)] pvpts[pair] = 1 # For this pair, store the trio and the other pairs pairs_trios[pair] = [tt] pairs_oprs[pair] = other_pairs else: pairs_trios[pair].append(tt) pairs_oprs[pair] = pairs_oprs[pair] + other_pairs pa = set(np.array(pairs_oprs[pair]).flatten().tolist() \ + list(pair)) pairs_allpts[pair] = list(pa) pairs_opts[pair] = sorted(list(pa - set(pair))) # List pairs pairs = list(pairs_oprs.keys()) # for k, v in pvpts.items(): # print(k, v) # Get far points segments = get_far_points(gc, segments, pvpts, pl.lines) # Update segments dictionary for k, seg in segments.items(): # if len(seg) != 2: if not isinstance(seg, geo.Segment): if len(seg) < 2: segments[k] = seg # pass if len(seg) == 2: segments[k] = geo.Segment(seg) # Return return pairs, trios, segments, pairs_trios, pairs_oprs, pairs_opts, pairs_allpts, pvpts, pl.lines
def build(self): """ Build it """ ws.log("Building the power diagram") x, y, r = self.x, self.y, self.r # Generating circles gc = GCircles(x, y, r) gc.triangulate() # First iteration pairs, trios, segments, pairs_trios, pairs_oprs, pairs_opts, \ pairs_allpts, pvpts, lines = build_laguerre(gc, gc.trios) # First correction new_trios, new_pairs, rmv_trios, rmv_pairs, finished = \ one_correction(pairs_oprs, pairs_opts, pairs_trios, segments) trios = update_trios(new_trios, rmv_trios, trios) # Iterate until there's no more bad crossings # Iteration counter corrcount = 0 while not finished: pairs, trios, segments, pairs_trios, pairs_oprs, pairs_opts, \ pairs_allpts, pvpts, lines = build_laguerre(gc, trios) new_trios, new_pairs, rmv_trios, rmv_pairs, finished = \ one_correction(pairs_oprs, pairs_opts, pairs_trios, segments) trios = update_trios(new_trios, rmv_trios, trios) # Go fix the only-two-connections issue poss_lost = [] poss_lost, rmv_trios, new_trios = fix_biconnections( poss_lost, pairs, gc, pairs_trios) if len(poss_lost) > 0: trios = update_trios(new_trios, rmv_trios, trios) pairs, trios, segments, pairs_trios, pairs_oprs, pairs_opts, \ pairs_allpts, pvpts, lines = build_laguerre(gc, trios) # 'This should be fixed now' whoslost = poss_lost[0] corrcount += 1 enough = corrcount == gc.nc / 4 # Check if this is finished finished = finished or enough ws.log('Number of corrections: %i' % corrcount) if enough: error_msg = 'TESSELLATION ERROR: Something went ' \ + 'wrong with this set of circles\n'\ + 'This is probably due to a bug in the algorithm\n' \ + 'Please try a different set of circles\n' ws.log(error_msg) self.any_errors = True print('TESSELLATION ERROR') return ws.log('Correction iterations: %i' % corrcount) # Check if two connections cross for ip, pair1 in enumerate(pairs): a = segments[pair1] for pair2 in pairs[ip + 1:]: b = segments[pair2] if isinstance(a, geo.Segment) and isinstance(b, geo.Segment): crossing, _ = geo.crossing_segments(a, b) overlapping = geo.overlapping_segments(a, b) if crossing or overlapping: ws.log( 'TESSELLATION ERROR: An error was found for pairs: %s, %s' % (str(pair1), str(pair2))) ws.log('This is due to a bug in the algorithm') ws.log('Please try a different set of circles') self.any_errors = True print('TESSELLATION ERROR') return else: pass # If finished, crop the diagram using the contour if self.contour is not None: # print('') # print('segments:') # for k, v in segments.items(): # print(k, v) # Crop it segments = crop_diagram(gc, pairs, segments, pvpts, lines, self.contour) # Finally, remove the segments which are not geo.Segment # instances for k, seg in segments.items(): if not isinstance(seg, geo.Segment): if len(seg) != 2: segments[k] = None if len(seg) == 2: segments[k] = geo.Segment(seg) self.pairs = pairs self.segments = segments self.trios = trios self.get_polygons()
points = generator.points segments = generator.segments for point_ in points: point = geometry.Point(1, 1) point.y = -point_[0].y point.x = point_[0].x symbol = point_[1] canvas.create_oval(x0 + point.x, y0 + point.y, x0 + point.x, y0 + point.y) canvas.create_text(x0 + point.x, y0 + point.y - 8, text=symbol, font='Arial 8', fill='darkblue') for segment in segments: once_segment = [ geometry.Segment(geometry.Point(0, 0), geometry.Point(0, 0)), '00' ] once_segment[0].A.y = -segment[0].A.y once_segment[0].B.y = -segment[0].B.y once_segment[0].A.x = segment[0].A.x once_segment[0].B.x = segment[0].B.x once_segment[1] = segment[1] canvas.create_line(x0 + once_segment[0].A.x, y0 + once_segment[0].A.y, x0 + once_segment[0].B.x, y0 + once_segment[0].B.y, fill='#' + segment[1][:2] + segment[1][1] + segment[1][-2] + segment[1][-2:]) canvas.pack() root.mainloop()
def test_angle_must_be_0_for_1_1_1_3(self): segment = geometry.Segment(1, 1, 1, 3) self.assertEqual(segment.angle, 0)
def test_angle_must_be_90_for_1_1_3_1(self): segment = geometry.Segment(1, 1, 3, 1) self.assertEqual(segment.angle, 90)
def test_length_must_equal_5_for_345_triangle(self): hypotenuse = geometry.Segment(3, 4, 6, 8) self.assertEqual(hypotenuse.length, 5)
def build_from_json(self): """ Build the nerve using the parameters stored in json files """ # Build contours self.c_reduction = ws.anatomy_settings["cross-section"][ "contours point reduction"] self.build_contours() contour = self.contour contour_hd = self.contour_hd contour_pslg = self.contour_pslg contour_pslg_nerve = self.contour_pslg_nerve # Build internal elements x = [] y = [] r = [] cables = OrderedDict() free_areas = [] start_positions = [] cables_tissues = OrderedDict() segments = {} len_seg = {} len_con = {} numberof = {'Axon': 0, 'NAELC': 0} models = {} itpath = ws.anatomy_settings["cross-section"]["internal topology file"] topology = read_from_json(itpath, object_pairs_hook=OrderedDict) # Read the dictionary and crete the necessary variables from it for i, c in topology['cables'].items(): i = int(i) cables[i] = c['type'] x.append(c['x']) y.append(c['y']) r.append(c['r']) free_areas.append(c['free extracellular area']) start_positions.append(c['start position']) cables_tissues[i] = OrderedDict() cables_tissues[i]['endoneurium'] = c['endoneurium'] cables_tissues[i]['epineurium'] = c['epineurium'] numberof[c['type']] += 1 # Axon model try: models[i] = c['model'] except KeyError: # It's not an axon models[i] = cables[i] # Turn the cables dictionary into a sorted list # And also sort everything else sortorder = np.argsort(np.array(list(cables.keys()))) cables = np.array(list(cables.values()))[sortorder] x = np.array(x)[sortorder] y = np.array(y)[sortorder] r = np.array(r)[sortorder] free_areas = np.array(free_areas)[sortorder] start_positions = np.array(start_positions)[sortorder] # Now pairs... for p in topology['pairs'].values(): i, j = p['pair'] pair = (i, j) s = p['separator segment'] a = s['a'] b = s['b'] seg = geo.Segment(((a['x'], a['y']), (b['x'], b['y']))) segments[pair] = seg len_seg[pair] = seg.length len_con[pair] = geo.dist((x[i], y[i]), (x[j], y[j])) # Save things as attributes # self.x = np.array(x) # self.y = np.array(y) # self.r = np.array(r) # self.free_areas = np.array(free_areas) # self.start_positions = np.array(start_positions) self.x = x self.y = y self.r = r self.free_areas = free_areas self.start_positions = start_positions self.cables_tissues = cables_tissues self.segments = segments self.len_seg = len_seg self.len_con = len_con self.pairs = len_con.keys() self.cables = cables self.models = models # Unique list of models self.models_set = set(self.models.values()) # print('cables:', cables) # print('r:', r) # for c_, r_ in zip(cables, r): # print(c_, r_) self.nc = len(cables) self.naxons_total = numberof['Axon'] self.nNAELC = numberof['NAELC'] # Build power diagram pd = tess.PowerDiagram(self.x, self.y, self.r, contour_pslg_nerve) # for p, s in zip(self.pairs, self.segments.values()): # print(p, str(s)) pd.build_preexisting(self.pairs, self.segments) self.trios = pd.trios self.pd = pd self.circ_areas = pd.circ_areas # Total endoneurial cross-sectional free area self.endo_free_cs_area = self.fas_total_area - self.circ_areas.sum()
def test_line_line_no_segment(self): # No segment intersect, but lines do a = geometry.Segment((2, 1), (4, 2)) b = geometry.Segment((0, 1), (2, 0)) self.assertEqual(geometry.line_intersect(a.p0, a.p1, b.p0, b.p1), (1, 0.5))
def test_segment_line_parallel(self): # Parallel segments a = geometry.Segment((0, 0), (4, 2)) b = geometry.Segment((1, 0), (3, 1)) self.assertEqual(a.intersect_segment(b), None)
def test_line_line(self): # No segment intersect, but lines do a = geometry.Segment((2, 1), (4, 2)) b = geometry.Segment((0, 1), (2, 0)) self.assertEqual(a.intersect_segment(b), None)
def test_segment_line_105(self): # Intersecting at (1, 0.5) a = geometry.Segment((0, 0), (4, 2)) b = geometry.Segment((2, 0), (0, 1)) self.assertEqual(a.intersect_segment(b), (1, 0.5))
def test_segment_line_21(self): # Intersecting at (2, 1) a = geometry.Segment((0, 0), (4, 2)) b = geometry.Segment((0, 3), (3, 0)) self.assertEqual(a.intersect_segment(b), (2, 1))
def get_far_points(gc, segments, pvpts, lines): """ Get the points being far away """ # Pairs that only have one point (on the boundary) iwhobdr = np.where(np.array(list(pvpts.values())) == 1) whobdr = np.array(list(pvpts.keys()))[iwhobdr] # Give them a second point very far away # Calculate limits xmin, xmax = gc.x.min(), gc.x.max() ymin, ymax = gc.y.min(), gc.y.max() dx = xmax - xmin dy = ymax - ymin xfar_left = xmin - 1e3 * dx xfar_rght = xmax + 1e3 * dx yfar_bot = ymin - 1e3 * dy yfar_top = ymax + 1e3 * dy for pair in whobdr: # Format the pair to amke it a tuple pair = tuple(pair.tolist()) # Get point and clean it try: point = tuple([float(x) for x in segments[pair][0]]) except TypeError: point = segments[pair].a if point is not None: powl = lines[pair] # If it's a vertical line, do it differently slope = powl.slope vline = (slope == np.inf) or (slope == -np.inf) or (np.isnan(slope)) if vline: xfar = point[0] ypnt = point[1] farpt_up = geo.Segment([(xfar, ypnt), (xfar, yfar_top)]) farpt_dn = geo.Segment([(xfar, ypnt), (xfar, yfar_bot)]) possible_segs = [farpt_dn, farpt_up] else: yfar_left = powl.equation(xfar_left) yfar_rght = powl.equation(xfar_rght) # Only one of the segments does not intersect with any other sleft = geo.Segment([point, (xfar_left, yfar_left)]) srght = geo.Segment([point, (xfar_rght, yfar_rght)]) possible_segs = [sleft, srght] # Try the left-hand segment and see if that's the good one valid_segment = np.array([False, False]) for i, tryseg in enumerate(possible_segs): this_one_not_valid = False for othpair, othseg in segments.items(): if (pair != othpair) and isinstance(othseg, geo.Segment): crossing, crosspoint = geo.crossing_segments( tryseg, othseg) overlapping = geo.overlapping_segments(tryseg, othseg) this_one_not_valid = crossing or overlapping if this_one_not_valid: # Not this one break if not this_one_not_valid: valid_segment[i] = True if (valid_segment == True).all(): # Choose the shortest one s0 = possible_segs[0] s1 = possible_segs[1] if s0.length < s1.length: segments[pair] = s0 else: segments[pair] = s1 elif (valid_segment == True).any(): segments[pair] = possible_segs[np.where( valid_segment == True)[0][0]] else: print('No far point could be given to:', pair) return segments
def test_center_must_be_3_4_for_0_0_6_8(self): hypotenuse = geometry.Segment(0, 0, 6, 8) self.assertEqual(hypotenuse.center, (3, 4))
def one_correction(pairs_oprs, pairs_opts, pairs_trios, segments): """ Perform one single trio change """ # New trios new_trios = [] # New pairs new_pairs = [] # Removed trios rmv_trios = [] # Removed pairs rmv_pairs = [] # Iterate over pairs to check crossing segments pairs = list(pairs_trios.keys())[:] for pair in pairs: other_pairs = pairs_oprs[pair] for i, op1 in enumerate(other_pairs): a = segments[op1] for op2 in other_pairs[i + 1:]: b = segments[op2] # if (len(a) == 2) & (len(b) == 2): # if True: if isinstance(a, geo.Segment) and isinstance(b, geo.Segment): crossing, _ = geo.crossing_segments(a, b) if crossing: # Change the connection: use the other diagonal of the # quad # Remove bad pair rmv_pairs.append(pair) # Update with new pair new_pair = tuple(pairs_opts[pair]) new_pairs.append(new_pair) # Update its segment xyint = geo.intersection_segments(a, b) try: isgeoSeg = isinstance(segments[new_pair], geo.Segment) except KeyError: segments = tools.append_items( segments, new_pair, [xyint]) else: if not isgeoSeg: segments = tools.append_items( segments, new_pair, [xyint]) # if not isinstance(segments[new_pair], geo.Segment): # try: # segments[new_pair].append(xyint) # except KeyError: # segments[new_pair] = [xyint] # Update the trios # Remove bad trios for trio in pairs_trios[pair]: rmv_trios.append(trio) # Update with new trios for p in pair: new_trio = tuple(sorted(list(new_pair) + [p])) new_trios.append(new_trio) # Update segments dictionary for k, seg in segments.items(): if not isinstance(seg, geo.Segment): # if len(seg) != 2: if len(seg) < 2: segments[k] = seg if len(seg) == 2: segments[k] = geo.Segment(seg) # The important thing here return new_trios, new_pairs, rmv_trios, rmv_pairs, False return new_trios, new_pairs, rmv_trios, rmv_pairs, True
def build_preexisting(self): """ Build the nerve using the parameters stored in files """ # Build contours self.c_reduction = ws.anatomy_settings["cross-section"][ "contours point reduction"] self.build_contours() contour = self.contour contour_hd = self.contour_hd contour_pslg = self.contour_pslg contour_pslg_nerve = self.contour_pslg_nerve # Build internal elements x = [] y = [] r = [] cables = [] free_areas = [] start_positions = [] cables_tissues = OrderedDict() segments = {} len_seg = {} len_con = {} numberof = {'Axon': 0, 'NAELC': 0} itpath = ws.anatomy_settings["cross-section"]["internal topology file"] with open(itpath, 'r') as f: # Skip header frl = list(csv.reader(f, delimiter=';'))[1:] # k is a cable counter k = 0 for row in frl: key = row[0] if key != 'Pair': cables.append(key) x.append(float(row[1])) y.append(float(row[2])) r.append(float(row[3])) free_areas.append(float(row[4])) start_positions.append(float(row[5])) try: cables_tissues[k] = { 'epineurium': float(row[7]), 'endoneurium': float(row[6]) } except IndexError: # There's no such information cables_tissues[k] = { 'epineurium': 0., 'endoneurium': 0. } numberof[key] += 1 k += 1 else: i = int(row[1]) j = int(row[2]) segments[(i, j)] = geo.Segment([ (float(row[3]), float(row[4])), (float(row[5]), float(row[6])) ]) len_seg[(i, j)] = float(row[7]) len_con[(i, j)] = float(row[8]) # Save things as attributes self.x = np.array(x) self.y = np.array(y) self.r = np.array(r) self.free_areas = np.array(free_areas) self.start_positions = np.array(start_positions) self.cables_tissues = cables_tissues self.segments = segments self.len_seg = len_seg self.len_con = len_con self.pairs = len_con.keys() self.cables = cables self.nc = len(cables) self.naxons_total = numberof['Axon'] self.nNAELC = numberof['NAELC'] # Build power diagram pd = tess.PowerDiagram(self.x, self.y, self.r, contour_pslg_nerve) for p, s in zip(self.pairs, self.segments.values()): print(p, str(s)) pd.build_preexisting(self.pairs, self.segments) self.trios = pd.trios self.pd = pd self.circ_areas = pd.circ_areas # Total endoneurial cross-sectional free area self.endo_free_cs_area = self.fas_total_area - self.circ_areas.sum()