def to_polygon(self): """Return a rectangular :class:`~planar.Polygon` object with the same vertices as the bounding box. :rtype: :class:`~planar.Polygon` """ return planar.Polygon([ self._min, (self._min.x, self._max.y), self._max, (self._max.x, self._min.y)], is_convex=True)
def __init__(self, verts, *args): # print('2: Creating polygon') # Attributes self.verts = verts # If it's not an array, make it be try: verts.shape except AttributeError: self.verts = np.array(verts) self.nverts = len(verts) self.x, self.y = self.verts.T # Compute attributes self.set_segments() self.set_angles() self.get_area() # self.get_centroid() self.get_circumcircle() # planar.Polygon. I need it for planar.Polygon.contains_point self.plpol = pl.Polygon(self.verts.tolist())
def build(self, params): """ Build the tessellation for the nerve """ # Get parameters # self.get_params(params) self.__dict__.update(params) mnd = self.mnd min_sep = self.min_sep rmin = self.rmin rmax = self.rmax circp_tol = self.circp_tol max_axs_pf = self.max_axs_pf numberofaxons = self.numberofaxons locations = self.locations radii = self.radii # models = self.models # Build contours self.build_contours() contour = self.contour contour_hd = self.contour_hd contour_pslg = self.contour_pslg contour_pslg_nerve = self.contour_pslg_nerve ################################################################## # Triangulate # Max. area for the triangles, # inverse to the minimum NAELC density maxarea = 1. / mnd # Triangulate # Instead, triangulate without taking the fascicles' contours # into account tri = triangle.triangulate(contour_pslg_nerve, 'a%f' % maxarea) tri = triangle.triangulate(contour_pslg, 'a%f' % maxarea) # Vertices tv = tri['vertices'] self.original_points = tv.T ################################################################## # Fill fascicles # If the axons have fixed locations, get their locations and # radii first, and then they will be added to the different # fascicles when needed if self.packing_type == "fixed locations": try: xx_, yy_ = np.array(locations).T except ValueError: # Something went wrong or simply there are no axons xx_ = yy_ = rr_ = np.array([]) else: rr_ = np.array(radii) # Axon models self.models = self.models['fixed'] # else: # # Axon models. Create them according to the proportions # Remove points inside the fascicles remove_these = [] axons = {} naxons = {} naxons_total = 0 print('about to fill the contours') for k, v in contour.items(): varr = np.array(v) # Remove points outside the nerve if 'Nerve' in k: plpol = pl.Polygon(v) for i, p in enumerate(tv): # Remove any points in tv falling outside the nerve # and outside its boundaries # Note: that means that I don't remove the points ON the # boundaries if not (plpol.contains_point(p) or np.isin(p, varr).all()): remove_these.append(i) # Remove points from the fascicles if 'Fascicle' in k: print(k) plpol = pl.Polygon(v) for i, p in enumerate(tv): # Remove the points of the fascicle's contours from tv inclc = plpol.contains_point(p) and (not np.isin( p, varr).all()) # Actually, don't remove the fascicle's contours # Remove any points in tv contained in a fascicle notic = plpol.contains_point(p) or np.isin(p, varr).all() if notic: remove_these.append(i) # Fill the fascicle # Dictionary of axons axons[k] = {} # Create the circles # Different packing strategies yield different results if self.packing_type == "uniform": xx, yy, rr = cp.fill(contour_hd[k], rmin, rmax, min_sep, nmaxpf=max_axs_pf, tolerance=circp_tol) elif self.packing_type == "gamma": distr_params = { "mean": self.avg_r, "shape": self.gamma_shape } xx, yy, rr = cp.fill(contour_hd[k], rmin, rmax, min_sep, nmaxpf=max_axs_pf, tolerance=circp_tol, distribution="gamma", distr_params=distr_params) print('Filled %s' % k) elif self.packing_type == "fixed locations": # Iterate over axons and get those inside # the fascicle xx, yy, rr = [], [], [] for x_, y_, r_ in zip(xx_, yy_, rr_): if plpol.contains_point((x_, y_)): xx.append(x_) yy.append(y_) rr.append(r_) xx = np.array(xx) yy = np.array(yy) rr = np.array(rr) # Store information in a clean way axons[k]['x'] = xx axons[k]['y'] = yy axons[k]['r'] = rr # axons[k]['models'] = models[:] naxons[k] = len(xx) naxons_total += naxons[k] rps = tv[remove_these] self.removed_points = rps keep_these = np.array( list(set(range(tv.shape[0])) - set(remove_these))) # print("keep_these:", keep_these) # print("remove_these:", remove_these) tv = tv[keep_these] # List x, y and r once some points have been removed tv = tv.T x, y = tv nc = x.size r = np.zeros_like(x) # Dictionaries for: # cables (their type) cables = OrderedDict() models = {} # Points corresponding to epineurium for ic in range(nc): cables[ic] = 'NAELC' # NAELC model indexing models[ic] = 'NAELC' print(ic, models, self.models) # Add axons to the existing points for the nerve for k in axons: x = np.array(x.tolist() + axons[k]['x'].tolist()) y = np.array(y.tolist() + axons[k]['y'].tolist()) r = np.array(r.tolist() + axons[k]['r'].tolist()) nc = x.size nNAELC = nc - naxons_total # Axon models if self.packing_type != 'fixed locations': # Now that the axons have been placed, determine their models according to the proportions # ninst: 'number of instances' (of each model) ninst = {} proportions = self.models['proportions'] keys = list(proportions.keys()) for m, p in proportions.items(): ninst[m] = int(p * naxons_total) # Remainder rem = naxons_total - sum(list(ninst.values())) # Just add the remaining in an arbitrary (not random) way for i in range(rem): ninst[keys[i]] += 1 # Now select the indices of the axons for each model axon_indices = (np.arange(naxons_total) + nNAELC).tolist() remaining_axon_indices = axon_indices[:] # Dictionary for the axon indices for each model inds = {} # Dictionary for the model names for each index model_by_index = {} for m, p in proportions.items(): sample = random.sample(remaining_axon_indices, ninst[m]) remaining_axon_indices = list( set(remaining_axon_indices) - set(sample)) inds[m] = sample[:] for i in sample: model_by_index[i] = m # Points corresponding to axons for ik, i in enumerate(np.arange(nNAELC, nc, 1)): cables[i] = 'Axon' # Axon model indexing if self.packing_type == 'fixed locations': models[i] = self.models[ik] else: models[i] = model_by_index[i] print(ik, i, models, self.models) ################################################################## # Power diagram # Zero-valued radii: Voronoi diagram # Build power diagram pd = tess.PowerDiagram(x, y, r, contour_pslg_nerve) pd.build() # Lengths of the segments and the connections pdpairs = pd.pairs pdsegments = pd.segments pairs = [] segments = {} len_seg = {} len_con = {} for pair in pdpairs: # if len(pdsegments[pair]) > 1: if pdsegments[pair] is not None: seg = pdsegments[pair] pairs.append(pair) segments[pair] = seg a, b = seg.a, seg.b len_seg[pair] = geo.dist(a, b).mean() i, j = pair len_con[pair] = geo.dist((x[i], y[i]), (x[j], y[j])) # Store relevant stuff in the attributes self.pd = pd self.x = x self.y = y self.r = r self.nc = nc self.axons_dict = axons self.pairs = pairs self.cables = cables self.models = models # Unique list of models self.models_set = set(self.models.values()) self.segments = segments self.trios = pd.trios self.len_con = len_con self.len_seg = len_seg self.free_areas = pd.free_areas 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() self.naxons = naxons self.naxons_total = naxons_total self.nNAELC = nNAELC
def fill(contour, rmin=0., rmax=1.e99, min_sep=0., nmaxpf=None, tolerance=1e4, tol_ta=1, distribution="uniform", distr_params=None): # In case it's not an array: try: _ = contour.size except: contour = np.array(contour) # Polygon pts = [tuple(item) for item in contour.tolist()] polygon = pl.Polygon(pts) center = (contour[:-1, 0].mean(), contour[:-1, 1].mean()) diameter = 2 * np.hypot(contour[:-1, 0] - center[0], contour[:-1, 1] - center[1]).max() radius = 0.5 * diameter # List of fibers rlist = [] positions = [] tryanother = 0 nonstop = True # Cell counter cc = 0 # minsep = min_sep * 0.5 while nonstop: # Random radius if distribution == "uniform": r = np.random.uniform(rmin, rmax, 1) elif distribution == "gamma": valid = False while not valid: # Work with diameters first, then back to radius mean = 2. * distr_params["mean"] shape = distr_params["shape"] scale = mean / shape r = 0.5 * np.random.gamma(shape, scale, 1) valid = (r >= rmin) & (r <= rmax) allowed_r = r + min_sep # Go trying different positions k = 0 while (k < tolerance): # Random position inside the polygon correct = False while not correct: # Try a random position dx = np.random.uniform(-radius, -radius + diameter) dy = np.random.uniform(-radius, -radius + diameter) x = center[0] + dx y = center[1] + dy # 1: Check if the center is inside the polygon correct = polygon.contains_point((x, y)) # 2: Check that the whole fiber is inside the polygon # For this, I actually check that all the points of the # polygon are farther than the fiber radius cx, cy = contour[:, 0], contour[:, 1] correct = correct & (np.hypot(cx - x, cy - y) > allowed_r).all() k += 1 # Check for clashes with other existing sub-units clash = False for i, xy in enumerate(positions): xc, yc = xy if (np.hypot(x - xc, y - yc) < allowed_r + rlist[i]): # Clash. This sub-unit doesn't fit here clash = True break if clash: k += 1 # If I tried too many times with a sub-unit and it # didn't fit, stop if k >= tolerance: tryanother += 1 if tryanother >= tol_ta: nonstop = False # Else, keep trying else: # No clash at all. Good position xy = (x, y) positions.append(xy) rlist.append(r) cc += 1 k = tolerance if cc >= nmaxpf: nonstop = False positions, rlist = np.array(positions), np.array(rlist) r = rlist[..., 0] x, y = positions.T return x, y, r