def _plot_2d_breakdowns(ax, seeds, seed_args): breakdowns = np.zeros((0, 3)) ec_kwargs = {} for seed, args in zip(seeds, seed_args): breakdowns = np.concatenate((breakdowns, seed.breakdown)) n_c = len(seed.breakdown) for key, val in args.items(): val_list = ec_kwargs.get(key, []) val_list.extend(n_c * [val]) ec_kwargs[key] = val_list d = 2 * breakdowns[:, -1] xy = breakdowns[:, :-1] a = np.full(len(breakdowns), 0) # abbreviate kwargs if all the same for key, val in ec_kwargs.items(): v1 = val[0] same = True for v in val: same &= v == v1 if same: ec_kwargs[key] = v1 ec = collections.EllipseCollection(d, d, a, units='x', offsets=xy, transOffset=ax.transData, **ec_kwargs) ax.add_collection(ec) ax.autoscale_view()
def ellipse_collection(ax, P): xy = np.array([p.x[::2].flatten() for p in P]) sizes = np.array([p.a for p in P]) coll = collections.EllipseCollection(sizes, sizes, np.zeros_like(sizes), offsets=xy, units='x', facecolors=[p.color for p in P], transOffset=ax.transData, alpha=0.7) return coll
def test_size_in_xy(): fig, ax = plt.subplots() widths, heights, angles = (10, 10), 10, 0 widths = 10, 10 coords = [(10, 10), (15, 15)] e = mcollections.EllipseCollection( widths, heights, angles, units='xy', offsets=coords, offset_transform=ax.transData) ax.add_collection(e) ax.set_xlim(0, 30) ax.set_ylim(0, 30)
def ellipse_collection(self, ax): "for matplotlib plotting" xz = self.x[:, [0, 2]] #xz = self.rz sizes = 2. * self.params.rMolecule * np.ones(self.N) colors = ["b"] * self.N coll = collections.EllipseCollection(sizes, sizes, np.zeros_like(sizes), offsets=xz, units="xy", facecolors=colors, transOffset=ax.transData, alpha=0.7) return coll
def __init__(self, fig, ax, configuration, anchors): self.fig = fig self.ax = ax self.conf = configuration self.anchors = anchors ax.set(title = "Particle filter state", xlabel = "$x$, meters", ylabel = "$y$, meters") conf = self.conf self.robot = Robot(conf['robot_start_x'], conf['robot_start_y'], conf['robot_start_theta']) self.particleFilter = ParticleFilter( conf['MIN_X'], conf['MAX_X'], conf['MIN_Y'], conf['MAX_Y'], conf['PARTICLES_NUM']) self.ax.set(aspect = "equal", xlim = (conf['MIN_X'], conf['MAX_X']), ylim=(conf['MIN_Y'], conf['MAX_Y'])) normcolor = plt.Normalize(0.0, 0.01) self.particlesScatter = self.ax.scatter( [p.x for p in self.particleFilter.particles], [p.y for p in self.particleFilter.particles], c=np.arange(conf['PARTICLES_NUM']), marker='.', alpha=0.5, cmap=plt.cm.rainbow, norm=normcolor, s=0.5, label='particles') cb = fig.colorbar(plt.cm.ScalarMappable(cmap=plt.cm.rainbow), ax = ax, shrink=0.8) cb.set_label('Relative particle weight') self.anchorsScatter = self.ax.scatter([a.x for a in anchors], [a.y for a in anchors], color='red', marker='x', label='anchors') lines_coordinates = [[(0.0, 0.0), (0.0, 0.0)]] lc = mc.LineCollection(lines_coordinates, linewidths=1) self.lines = self.ax.add_collection(lc) self.positionEst, = self.ax.plot([0.0], [0.0], marker='x', markersize=9, color="green", alpha=0.98, label='Estimated position') self.positionTrue, = self.ax.plot([0.0], [0.0], marker='+', markersize=9, color="black", label='True position') self.text = ax.text(conf['MIN_X'] + (conf['MIN_X'] + conf['MAX_X']) * 0.5, conf['MIN_Y'] + (conf['MIN_Y'] + conf['MAX_Y']) * 0.05, "") centers = np.array([[a.x, a.y] for a in anchors]) cl = mc.EllipseCollection([1.] * len(anchors), [1.] * len(anchors), [1.] * len(anchors), offsets=centers, transOffset=ax.transData, units='xy', facecolors='none', color='black', alpha=0.5) self.circles = ax.add_collection(cl) self.circles.set_facecolor('none') self.ax.legend(loc = 'upper left')
def test_EllipseCollection(): # Test basic functionality fig, ax = plt.subplots() x = np.arange(4) y = np.arange(3) X, Y = np.meshgrid(x, y) XY = np.vstack((X.ravel(), Y.ravel())).T ww = X / x[-1] hh = Y / y[-1] aa = np.ones_like(ww) * 20 # first axis is 20 degrees CCW from x axis ec = mcollections.EllipseCollection( ww, hh, aa, units='x', offsets=XY, offset_transform=ax.transData, facecolors='none') ax.add_collection(ec) ax.autoscale_view()
def init_matplotlib(self): plt.ion() fig = plt.figure(figsize=(6, 6)) ax = fig.add_subplot(111, aspect="equal") ax.set_xlim(0.0 - self.thickness, 1.0 + self.thickness) ax.set_ylim(0.0 - self.thickness, 1.0 + self.thickness) ax.set_xticks([]) ax.set_yticks([]) obstacles = self.geoms.geom_objs rects = [] for i, obst in enumerate(obstacles): x, y = obst.placement.translation[:2] half_side = obst.geometry.halfSide w, h = 2 * half_side[:2] rects.append( patches.Rectangle( (x - w / 2, y - h / 2), w, h # (x,y) # width # height )) coll = collections.PatchCollection(rects, zorder=1) coll.set_alpha(0.6) ax.add_collection(coll) size = self.robot_props["dist_goal"] offsets = np.stack((self.state.q, self.goal_state.q), 0)[:, :2] sg = collections.EllipseCollection( widths=size, heights=size, facecolors=[(1, 0, 0, 0.8), (0, 1, 0, 0.8)], angles=0, units="xy", offsets=offsets, transOffset=ax.transData, ) ax.add_collection(sg) plt.tight_layout() self.fig = fig self.ax = ax
def addCircles(centers, radii, **kwargs): """Adds a set of circles to an already initialized figure. Parameters ---------- centers: object Complex numpy array with the circle central coordinates. radii: object Numpy array with the circle radii. kwargs: collections.EllipseCollection properties Any additional property that should be passed to the ellipse collection. Returns ------- object The circles ellipse collection. """ # Create the ellipse collection diameters = 2 * radii offsets = np.hstack( (centers.real[:, np.newaxis], centers.imag[:, np.newaxis])) angles = np.zeros(len(centers)) params = { "offsets": offsets, "units": "xy", "transOffset": plt.gca().transData } params.update(kwargs) ellipseCollection = collections.EllipseCollection(diameters, diameters, angles, **params) # Plot the ellipses in the current figure plt.gca().add_collection(ellipseCollection) return ellipseCollection
def plot_breakdown(self, index_by='seed', material=[], loc=0, **kwargs): """Plot the breakdowns of the seeds in seed list. This function plots the breakdowns of seeds contained in the seed list. In 2D, the breakdowns are grouped into matplotlib collections to reduce the computational load. In 3D, matplotlib does not have patches, so each breakdown is rendered as its own surface. Additional keyword arguments can be specified and passed through to matplotlib. These arguments should be either single values (e.g. ``edgecolors='k'``), or lists of values that have the same length as the seed list. Args: index_by (str): *(optional)* {'material' | 'seed'} Flag for indexing into the other arrays passed into the function. For example, ``plot(index_by='material', color=['blue', 'red'])`` will plot the seeds with ``phase`` equal to 0 in blue, and seeds with ``phase`` equal to 1 in red. Defaults to 'seed'. material (list): *(optional)* Names of material phases. One entry per material phase (the ``index_by`` argument is ignored). If this argument is set, a legend is added to the plot with one entry per material. loc (int or str): *(optional)* The location of the legend, if 'material' is specified. This argument is passed directly through to :func:`matplotlib.pyplot.legend`. Defaults to 0, which is 'best' in matplotlib. **kwargs: Keyword arguments to pass to matplotlib """ seed_args = [{} for seed in self] for seed_num, seed in enumerate(self): phase_num = seed.phase for key, val in kwargs.items(): if type(val) in (list, np.array): if index_by == 'seed' and len(val) > seed_num: seed_args[seed_num][key] = val[seed_num] elif index_by == 'material' and len(val) > phase_num: seed_args[seed_num][key] = val[phase_num] else: seed_args[seed_num][key] = val n = self[0].geometry.n_dim if n == 2: ax = plt.gca() else: ax = plt.gcf().gca(projection=Axes3D.name) n_obj = _misc.ax_objects(ax) if n_obj > 0: xlim = ax.get_xlim() ylim = ax.get_ylim() else: xlim = [float('inf'), -float('inf')] ylim = [float('inf'), -float('inf')] if n == 3: if n_obj > 0: zlim = ax.get_zlim() else: zlim = [float('inf'), -float('inf')] for seed, args in zip(self, seed_args): seed.plot_breakdown(**args) elif n == 2: breakdowns = np.zeros((0, 3)) ec_kwargs = {} for seed, args in zip(self, seed_args): breakdowns = np.concatenate((breakdowns, seed.breakdown)) n_c = len(seed.breakdown) for key, val in args.items(): val_list = ec_kwargs.get(key, []) val_list.extend(n_c * [val]) ec_kwargs[key] = val_list d = 2 * breakdowns[:, -1] xy = breakdowns[:, :-1] a = np.full(len(breakdowns), 0) # abbreviate kwargs if all the same for key, val in ec_kwargs.items(): v1 = val[0] same = True for v in val: same &= v == v1 if same: ec_kwargs[key] = v1 ax = plt.gca() ec = collections.EllipseCollection(d, d, a, units='x', offsets=xy, transOffset=ax.transData, **ec_kwargs) ax.add_collection(ec) ax.autoscale_view() # Add legend if material: p_kwargs = [{'label': m} for m in material] if index_by == 'seed': for seed_kwargs, seed in zip(seed_args, self): p = seed.phase p_kwargs[p].update(seed_kwargs) else: for key, val in kwargs.items(): if type(val) in (list, np.array): for i, elem in enumerate(val): p_kwargs[i][key] = elem else: for i in range(len(p_kwargs)): p_kwargs[i][key] = val # Replace plural keywords for p_kw in p_kwargs: for kw in _misc.mpl_plural_kwargs: if kw in p_kw: p_kw[kw[:-1]] = p_kw[kw] del p_kw[kw] handles = [patches.Patch(**p_kw) for p_kw in p_kwargs] if n == 2: ax.legend(handles=handles, loc=loc) else: plt.gca().legend(handles=handles, loc=loc) # Adjust Axes seed_lims = [np.array(s.geometry.limits).flatten() for s in self] mins = np.array(seed_lims)[:, 0::2].min(axis=0) maxs = np.array(seed_lims)[:, 1::2].max(axis=0) xlim = (min(xlim[0], mins[0]), max(xlim[1], maxs[0])) ylim = (min(ylim[0], mins[1]), max(ylim[1], maxs[1])) if n == 2: plt.axis('square') ax.set_xlim(xlim) ax.set_ylim(ylim) if n == 3: zlim = (min(zlim[0], mins[2]), max(zlim[1], maxs[2])) ax.set_xlim(xlim) ax.set_ylim(ylim) ax.set_zlim(zlim) _misc.axisEqual3D(ax)
def plot(self, index_by='seed', material=[], loc=0, **kwargs): """Plot the seeds in the seed list. This function plots the seeds contained in the seed list. In 2D, the seeds are grouped into matplotlib collections to reduce the computational load. In 3D, matplotlib does not have patches, so each seed is rendered as its own surface. Additional keyword arguments can be specified and passed through to matplotlib. These arguments should be either single values (e.g. ``edgecolors='k'``), or lists of values that have the same length as the seed list. Args: index_by (str): *(optional)* {'material' | 'seed'} Flag for indexing into the other arrays passed into the function. For example, ``plot(index_by='material', color=['blue', 'red'])`` will plot the seeds with ``phase`` equal to 0 in blue, and seeds with ``phase`` equal to 1 in red. Defaults to 'seed'. material (list): *(optional)* Names of material phases. One entry per material phase (the ``index_by`` argument is ignored). If this argument is set, a legend is added to the plot with one entry per material. loc (int or str): *(optional)* The location of the legend, if 'material' is specified. This argument is passed directly through to :func:`matplotlib.pyplot.legend`. Defaults to 0, which is 'best' in matplotlib. **kwargs: Keyword arguments to pass to matplotlib """ seed_args = [{} for seed in self] for seed_num, seed in enumerate(self): phase_num = seed.phase for key, val in kwargs.items(): if type(val) in (list, np.array): if index_by == 'seed' and len(val) > seed_num: seed_args[seed_num][key] = val[seed_num] elif index_by == 'material' and len(val) > phase_num: seed_args[seed_num][key] = val[phase_num] else: seed_args[seed_num][key] = val n = self[0].geometry.n_dim if n == 2: ax = plt.gca() else: ax = plt.gcf().gca(projection=Axes3D.name) n_obj = _misc.ax_objects(ax) if n_obj > 0: xlim = ax.get_xlim() ylim = ax.get_ylim() else: xlim = [float('inf'), -float('inf')] ylim = [float('inf'), -float('inf')] if n == 3: if n_obj > 0: zlim = ax.get_zlim() else: zlim = [float('inf'), -float('inf')] for seed, args in zip(self, seed_args): seed.plot(**args) elif n == 2: ellipse_data = {'w': [], 'h': [], 'a': [], 'xy': []} ec_kwargs = {} rect_data = {'xy': [], 'w': [], 'h': [], 'angle': []} rect_kwargs = {} pc_verts = [] pc_kwargs = {} for seed, args in zip(self, seed_args): geom_name = type(seed.geometry).__name__.lower().strip() if geom_name == 'ellipse': a, b = seed.geometry.axes cen = np.array(seed.position) t = seed.geometry.angle_deg ellipse_data['w'].append(2 * a) ellipse_data['h'].append(2 * b) ellipse_data['a'].append(t) ellipse_data['xy'].append(cen) for key, val in args.items(): val_list = ec_kwargs.get(key, []) val_list.append(val) ec_kwargs[key] = val_list elif geom_name == 'circle': diam = seed.geometry.diameter cen = np.array(seed.position) ellipse_data['w'].append(diam) ellipse_data['h'].append(diam) ellipse_data['a'].append(0) ellipse_data['xy'].append(cen) for key, val in args.items(): val_list = ec_kwargs.get(key, []) val_list.append(val) ec_kwargs[key] = val_list elif geom_name in ['rectangle', 'square']: w, h = seed.geometry.side_lengths corner = seed.geometry.corner t = seed.geometry.angle_deg rect_data['w'].append(w) rect_data['h'].append(h) rect_data['angle'].append(t) rect_data['xy'].append(corner) for key, val in args.items(): val_list = rect_kwargs.get(key, []) val_list.append(val) rect_kwargs[key] = val_list elif geom_name == 'curl': xy = seed.geometry.plot_xy() pc_verts.append(xy) for key, val in args.items(): val_list = pc_kwargs.get(key, []) val_list.append(val) pc_kwargs[key] = val_list elif geom_name == 'nonetype': pass else: e_str = 'Cannot plot groups of ' + geom_name e_str += ' yet.' raise NotImplementedError(e_str) # abbreviate kwargs if all the same for key, val in ec_kwargs.items(): v1 = val[0] same = True for v in val: same &= v == v1 if same: ec_kwargs[key] = v1 for key, val in pc_kwargs.items(): v1 = val[0] same = True for v in val: same &= v == v1 if same: pc_kwargs[key] = v1 # Plot Circles and Ellipses ax = plt.gca() w = np.array(ellipse_data['w']) h = np.array(ellipse_data['h']) a = np.array(ellipse_data['a']) xy = np.array(ellipse_data['xy']) ec = collections.EllipseCollection(w, h, a, units='x', offsets=xy, transOffset=ax.transData, **ec_kwargs) ax.add_collection(ec) # Plot Rectangles rects = [ Rectangle(xy=xyi, width=wi, height=hi, angle=ai) for xyi, wi, hi, ai in zip(rect_data['xy'], rect_data['w'], rect_data['h'], rect_data['angle']) ] rc = collections.PatchCollection(rects, False, **rect_kwargs) ax.add_collection(rc) # Plot Polygons pc = collections.PolyCollection(pc_verts, **pc_kwargs) ax.add_collection(pc) ax.autoscale_view() # Add legend if material: p_kwargs = [{'label': m} for m in material] if index_by == 'seed': for seed_kwargs, seed in zip(seed_args, self): p = seed.phase p_kwargs[p].update(seed_kwargs) else: for key, val in kwargs.items(): if type(val) in (list, np.array): for i, elem in enumerate(val): p_kwargs[i][key] = elem else: for i in range(len(p_kwargs)): p_kwargs[i][key] = val # Replace plural keywords for p_kw in p_kwargs: for kw in _misc.mpl_plural_kwargs: if kw in p_kw: p_kw[kw[:-1]] = p_kw[kw] del p_kw[kw] handles = [patches.Patch(**p_kw) for p_kw in p_kwargs] ax.legend(handles=handles, loc=loc) # Adjust Axes seed_lims = [np.array(s.geometry.limits).flatten() for s in self] mins = np.array(seed_lims)[:, 0::2].min(axis=0) maxs = np.array(seed_lims)[:, 1::2].max(axis=0) xlim = (min(xlim[0], mins[0]), max(xlim[1], maxs[0])) ylim = (min(ylim[0], mins[1]), max(ylim[1], maxs[1])) if n == 2: plt.axis('square') ax.set_xlim(xlim) ax.set_ylim(ylim) if n == 3: zlim = (min(zlim[0], mins[2]), max(zlim[1], maxs[2])) ax.set_xlim(xlim) ax.set_ylim(ylim) ax.set_zlim(zlim) _misc.axisEqual3D(ax)
def _plot_2d(ax, seeds, seed_args): ellipse_data = {'w': [], 'h': [], 'a': [], 'xy': []} ec_kwargs = {} rect_data = [] rect_kwargs = {} for seed, args in zip(seeds, seed_args): geom_name = type(seed.geometry).__name__.lower().strip() if geom_name == 'ellipse': ellipse_data['w'].append(2 * seed.geometry.a) ellipse_data['h'].append(2 * seed.geometry.b) ellipse_data['a'].append(seed.geometry.angle_deg) ellipse_data['xy'].append(np.array(seed.position)) for key, val in args.items(): val_list = ec_kwargs.get(key, []) + [val] ec_kwargs[key] = val_list elif geom_name == 'circle': ellipse_data['w'].append(seed.geometry.diameter) ellipse_data['h'].append(seed.geometry.diameter) ellipse_data['a'].append(0) ellipse_data['xy'].append(np.array(seed.position)) for key, val in args.items(): val_list = ec_kwargs.get(key, []) + [val] ec_kwargs[key] = val_list elif geom_name in ['rectangle', 'square']: w, h = seed.geometry.side_lengths corner = seed.geometry.corner t = seed.geometry.angle_deg rect_inputs = {'width': w, 'height': h, 'angle': t, 'xy': corner} rect_data.append(rect_inputs) for key, val in args.items(): val_list = rect_kwargs.get(key, []) + [val] rect_kwargs[key] = val_list elif geom_name == 'nonetype': pass else: e_str = 'Cannot plot groups of ' + geom_name + ' yet.' raise NotImplementedError(e_str) # abbreviate kwargs if all the same for key, val in ec_kwargs.items(): v1 = val[0] same = True for v in val: same &= v == v1 if same: ec_kwargs[key] = v1 # Plot Circles and Ellipses w = np.array(ellipse_data['w']) h = np.array(ellipse_data['h']) a = np.array(ellipse_data['a']) xy = np.array(ellipse_data['xy']) ec = collections.EllipseCollection(w, h, a, units='x', offsets=xy, transOffset=ax.transData, **ec_kwargs) ax.add_collection(ec) # Plot Rectangles rects = [Rectangle(**rect_inps) for rect_inps in rect_data] rc = collections.PatchCollection(rects, False, **rect_kwargs) ax.add_collection(rc) ax.autoscale_view()
ax.add_patch(P.Circle((xc, yc), rout, **patch_args)) ax.subfigure_label('(a)') fig.tight_layout() fig.nice_colorbar(im, ticks=[]) ax = fig.add_subplot(1, 2, 2, polar=True, frame_on=False) ellipse_args = { 'units': 'xy', 'facecolors': 'none', 'linewidth': 0.5, 'transOffset': ax.transData } ec_RHCP = collections.EllipseCollection( widths=widths[rhcp_mask], heights=heights[rhcp_mask], angles=angles[rhcp_mask], edgecolors=tango.skyblue3, offsets=offsets[rhcp_mask], **ellipse_args) ec_LHCP = collections.EllipseCollection( widths=widths[lhcp_mask], heights=heights[lhcp_mask], angles=angles[lhcp_mask], edgecolors=tango.scarletred3, offsets=offsets[lhcp_mask], **ellipse_args) ec_lin = collections.EllipseCollection( widths=widths[lin_mask], heights=heights[lin_mask], angles=angles[lin_mask], edgecolors='black',
def __init__(self,x,y, s=None, linewidths=None, values=None, cmap=None, norm=None, colors=None, edgecolors=None, alphas=1.0, zorder=None, transform=mtransforms.IdentityTransform(), dpi=72): assert len(x.shape) == 1 assert len(y.shape) == 1 assert x.shape == y.shape self._npts = x.shape[0] ref_colour_nitems = None # == colour composition or colour mapping == # if values is None or cmap is None: # use colors as facecolors - check sizes if colors is not None: assert isinstance(colors, np.ndarray) if len(colors.shape) > 1: assert x.shape[0] == colors.shape[0] color_dims = colors.shape[0] if len(colors.shape) == 1 else colors.shape[1] if color_dims == 3: # RGB - check and merge with alpha(s) if len(colors.shape) > 1: alpha_array = alphas if isinstance(alphas, np.ndarray) else np.ones(colors.shape[0], dtype=np.float32) * alphas colours = np.column_stack([colors, alpha_array]) else: colours = np.concatenate([colors, alphas]) else: assert color_dims == 4 colours = colors ref_colour_nitems = -1 if len(colours.shape) > 1: ref_colour_nitems = colours.shape[0] self._facecolors = colours if edgecolors is not None: assert isinstance(edgecolors, np.ndarray) if len(edgecolors.shape) > 1: assert x.shape[0] == edgecolors.shape[0] color_dims = edgecolors.shape[0] if len(edgecolors.shape) == 1 else edgecolors.shape[1] if color_dims == 3: # RGB - check and merge with alpha(s) if len(edgecolors.shape) > 1: ecolours = np.column_stack([edgecolors, np.ones(colors.shape[0], dtype=np.float32)]) else: ecolours = np.concatenate([edgecolors, alphas]) else: assert color_dims == 4 ecolours = edgecolors self._edgecolors = ecolours assert self._facecolors.shape == self._edgecolors.shape else: raise ValueError("No color information available for scatter plot circles.") else: # use the colour mapping - no face or edge colors self._facecolors = None self._edgecolors = None if edgecolors is not None: self._edgecolors = edgecolors self._cmap = cmap if norm is not None and not isinstance(norm, mcolors.Normalize): msg = "'norm' must be an instance of 'mcolors.Normalize'" raise ValueError(msg) self._norm = norm self._values = values assert x.shape[0] == values.shape[0] ref_colour_nitems = self._values.shape[0] if alphas not in [None, False] and type(alphas) not in [list, np.ndarray]: self._uniform_alpha = alphas self._linewidths = linewidths self._create_path_collection() if ref_colour_nitems > 0 and (isinstance(self._linewidths, np.ndarray) or type(self._linewidths) in [list, ]): assert self._linewidths.shape[0] == ref_colour_nitems else: assert type(self._linewidths) in [np.float, np.float32, np.float64, float] self._markersizes = s if s is not None else 1.0 if ref_colour_nitems > 0 and (isinstance(self._markersizes, np.ndarray) or type(self._markersizes) in [list, ]): assert self._markersizes.shape[0] == ref_colour_nitems else: assert type(self._markersizes) in [np.float, np.float32, np.float64, float] self._markersizes = np.ones(self._npts, dtype=np.float32) * self._markersizes # self._offsets = np.c_[x, y].reshape(-1, 1, 2) self._offsets = np.column_stack((x, y)) # .reshape(-1, 1, 2) self._plotorder = np.arange(0, x.shape[0]) # self._offsets = np.concatenate((x, y), axis=1).reshape(-1, 1, 2) self._zorder = zorder self._transform = transform self._dpi = dpi # self._collection = mcoll.PathCollection( # (self._markerpath,), self._markersizes, # cmap=self._cmap, # norm=self._norm, # facecolors=self._facecolors, # edgecolors=self._edgecolors, # linewidths=self._linewidths, # offsets=self._offsets, # transOffset=self._transform, # alpha=self._uniform_alpha, # zorder=self._zorder # ) # sizes = np.array([self._markersizes,] * self._npts) sizes = self._markersizes self._collection = mcoll.EllipseCollection( widths=sizes, heights=sizes, angles=np.zeros(self._npts), units='x', cmap=self._cmap, norm=self._norm, facecolors=self._facecolors, edgecolors=self._edgecolors, linewidths=self._linewidths, offsets=self._offsets, transOffset=self._transform, alpha=self._uniform_alpha, zorder=self._zorder ) print(self._collection.get_offsets()) if self._facecolors is None and self._values is not None and self._cmap is not None and self._norm is not None: self.update_plotorder() self._collection.set_array(np.asarray(self._values))
def __init__(self, fig, ax, landmarksTruePositions, robot, ekfSlam, extendPlot, controller): self.fig = fig self.ax = ax[0] self.landmarks = landmarksTruePositions self.robot = robot self.ekfSlam = ekfSlam ax[0].set(title = "EKF State", xlabel = "$x$, meters", ylabel = "$y$, meters") ax[1].set(title = "Covariance matrix", xlabel = "$\Sigma$ column $j$", ylabel = "$\Sigma$ row $i$") self.ax.set(aspect = "equal", xlim = (landmarksTruePositions['x'].min() - extendPlot, landmarksTruePositions['x'].max() + extendPlot), ylim = (landmarksTruePositions['y'].min() - extendPlot, landmarksTruePositions['y'].max() + extendPlot) ) self.landmarksTruePositionsScatter = self.ax.scatter( landmarksTruePositions['x'], landmarksTruePositions['y'], color='black', marker='x', label = 'true landmark positions', zorder=3, s=150) self.positionTrue = self.ax.quiver([0.0], [0.0], [1.0], [0.0], pivot='mid', color='black', units='inches', scale=4.0, label='True position') self.positionEst = self.ax.quiver([0.0], [0.0], [1.0], [0.0], pivot='mid', color='violet', units='inches', scale=4.0, label='Estimated position') self.distanceAndBearingLines = self.ax.add_collection( mc.LineCollection([[(0.0, 0.0), (0.0, 0.0)]], linewidths=2, alpha=0.7, linestyles='dashed', colors = cm.jet(np.linspace(0, 1, len(self.landmarks))))) self.landmarksEstimatedPositionsScatter = self.ax.scatter( [], [], color='magenta', marker='o', label = 'estimated landmark positions') data = np.zeros(1000) data[:] = np.nan self.landmarkMeasurementsScatter = self.ax.scatter( data, data, marker='.', alpha = 0.4, zorder=0, s=20.0, edgecolor='none', c=np.zeros((1000, 4)), ) self.ax.legend(loc = 'upper left') self.im = ax[1].imshow(self.ekfSlam.Sigma, interpolation='none', vmin=-1, vmax=1, cmap=plt.cm.PiYG) fig.colorbar(self.im, ax=ax[1], shrink=0.7) self.localcm = cm.jet(np.linspace(0, 1, len(self.landmarks))) self.localcm[:,3] = 0.4 self.text = self.ax.text(self.ax.get_xlim()[0] + (self.ax.get_xlim()[0] + self.ax.get_xlim()[1]) * 0.5, self.ax.get_ylim()[0] + (self.ax.get_ylim()[0] + self.ax.get_ylim()[1]) * 0.05, "", fontsize=15) ellipses = mc.EllipseCollection(self.landmarks['x'], self.landmarks['y'], [0.0] * len(self.landmarks), offsets = np.vstack((self.landmarks['x'], self.landmarks['y'])).T, transOffset = self.ax.transData, units='xy', facecolors='none', edgecolors=cm.jet(np.linspace(0, 1, len(self.landmarks))), alpha=0.95) self.confidenceEllipses = self.ax.add_collection(ellipses) self.confidenceEllipses.set_facecolor('none') self.positionConfidenceEllipse = Ellipse((0.0, 0.0), 0.0, 0.0, 0.0, edgecolor='magenta', facecolor='none') self.ax.add_patch(self.positionConfidenceEllipse) self.i = 0 self.controller = controller plt.tight_layout()
def run_script(args): matplotlib.interactive(False) args.plot_type = 'dashboard' Rprop0 = args.Rock0 Rprop1 = args.Rock1 theta = np.arange(0, 90) vp0, vs0, rho0 = make_normal_dist(Rprop0, args.iterations) vp1, vs1, rho1 = make_normal_dist(Rprop1, args.iterations) reflect = [] hist_titles = [[r'$V_\mathrm{P}$', r'$m/s$'], [r'$V_\mathrm{S}$', r'$m/s$'], [r'$\rho$', r'$kg / m^3$']] nbins = 15 vp_lim = (np.amin( (Rprop1.vp - (3. * Rprop1.vp_sig), Rprop0.vp - (3. * Rprop1.vp_sig))), np.amax((Rprop1.vp + (3. * Rprop1.vp_sig), Rprop0.vp + (3. * Rprop1.vp_sig)))) vs_lim = (np.amin( (Rprop1.vs - (3. * Rprop1.vs_sig), Rprop0.vs - (3. * Rprop1.vs_sig))), np.amax((Rprop1.vs + (3. * Rprop1.vs_sig), Rprop0.vs + (3. * Rprop1.vs_sig)))) rho_lim = (np.amin((Rprop1.rho - (3. * Rprop1.rho_sig), Rprop0.rho - (3. * Rprop1.rho_sig))), np.amax((Rprop1.rho + (3. * Rprop1.rho_sig), Rprop0.rho + (3. * Rprop1.rho_sig)))) limits = np.array([[vp_lim, vs_lim, rho_lim], [vp_lim, vs_lim, rho_lim]]) for i in range(args.iterations): reflect.append( args.reflectivity_method(vp0[i], vs0[i], rho0[i], vp1[i], vs1[i], rho1[i], theta)) reflect = np.array(reflect) reflect = np.nan_to_num(reflect) #temp = np.concatenate( (vp0, rho0, vs0, vp1, rho1, vs1,), axis=0) temp = np.concatenate((vp0, vs0, rho0, vp1, vs1, rho1), axis=0) prop_samples = np.reshape(temp, (6, args.iterations)) ave_reflect = np.mean(reflect, axis=0) nbins = 15 # DO PLOTTING plt.figure(figsize=(5, 13)) plt.subplots_adjust(bottom=0.1, left=0.1, top=1, right=0.9) plt.hold(True) if args.plot_type == 'dashboard': G = matplotlib.gridspec.GridSpec(9, 2, hspace=0.5) shift = 3 else: G = matplotlib.gridspec.GridSpec(2, 6) shift = 0 # histogram plots (ax_3, ax_4, ax_5, ax_6, ax_7, ax_8) hist_max = 0 for k in np.arange(len(prop_samples)): # find the max bar height of the histogram for scaling the plots hist_max = max(hist_max, max(np.histogram(prop_samples[k], density=True)[0])) for j in np.arange(2): upper_color = 'blue' #color of upper histogram lower_color = 'green' #color of lower histogram for i in np.arange(3): plt.subplot(G[3 + i + shift, 0]) plt.hist(prop_samples[i], nbins, facecolor=upper_color, histtype='stepfilled', alpha=0.25, normed=True) plt.hist(prop_samples[i + (3)], nbins, facecolor=lower_color, histtype='stepfilled', alpha=0.25, normed=True) temp = plt.gca() # Annotation and making it look nice plt.axis( [limits[j][i][0], limits[j][i][1], temp.axis()[2], hist_max]) plt.yticks([]) plt.xticks(rotation=90, horizontalalignment='left') ax = plt.gca() ax.spines['right'].set_color('none') ax.spines['left'].set_color('none') ax.spines['top'].set_color('none') ax.spines['bottom'].set_alpha(0.5) for label in ax.get_xticklabels() + ax.get_yticklabels(): label.set_fontsize(6) label.set_alpha(0.5) for tick in ax.xaxis.get_major_ticks(): tick.tick1On = True tick.tick2On = False # upper text label mean_props = [[Rprop0.vp, Rprop1.vp], [Rprop0.vs, Rprop1.vs], [Rprop0.rho, Rprop1.rho]] which_label = ['upper', 'lower'] # Main label ax.text(x=limits[0, i, 1], y=hist_max * 0.5, s=hist_titles[i][0], color='black', fontsize=14, alpha=0.75, horizontalalignment='left', verticalalignment='center') # Label for units ax.text(x=limits[0, i, 1], y=hist_max * 0.25, s=hist_titles[i][1], color='black', fontsize=10, alpha=0.75, horizontalalignment='left', verticalalignment='center') ax.text( x=float(mean_props[i][0]), y=hist_max / 5.0, s=which_label[0], alpha=0.75, color=upper_color, fontsize='9', horizontalalignment='center', verticalalignment='center', ) #lower text label ax.text(x=float(mean_props[i][1]), y=hist_max / 5.0, s=which_label[1], alpha=0.75, color=lower_color, fontsize='9', horizontalalignment='center', verticalalignment='center') # # ax_1 the AVO plot # plt.subplot(G[0:3, :]) plt.hold(True) critical_angles = [] for i in range(args.iterations - 1): # Do the AVO template as an underlay # HERE # Do the plots --> This step might not need to be in a loop plt.plot(theta, reflect[i], color='grey', lw=1.0, alpha=np.min((30. / args.iterations, 0.08))) if vp1[i] > vp0[i]: theta_crit = arcsin(vp0[i] / vp1[i]) * 180 / np.pi plt.axvline(x=theta_crit, color='black', lw=1.0, alpha=np.min((30. / args.iterations, 0.5))) critical_angles.append(theta_crit) if len(critical_angles) > 0: critical_angle = np.mean(critical_angles) else: critical_angle = 'N/A' plt.plot(theta, ave_reflect, color='black', alpha=0.5, lw=1.5) plt.grid() # Annotation and making it look nice ax = plt.gca() ax.spines['right'].set_color('none') ax.spines['top'].set_color('none') ax.xaxis.set_ticks_position('bottom') ax.spines['bottom'].set_position(('data', 0)) ax.spines['bottom'].set_alpha(0.5) ax.yaxis.set_ticks_position('left') ax.spines['left'].set_position(('data', 0)) ax.spines['left'].set_alpha(0.5) for label in ax.get_xticklabels() + ax.get_yticklabels(): label.set_fontsize(6) label.set_alpha(0.5) plt.grid() plt.ylim((-.5, .5)) ax.text(0.95, 0.45, 'angle', verticalalignment='top', horizontalalignment='right', transform=ax.transAxes, color='black', fontsize=8, alpha=0.5) ax.text(0.05, 0.95, 'amplitude', verticalalignment='top', horizontalalignment='right', transform=ax.transAxes, rotation=90, color='black', fontsize=8, alpha=0.5) # # Patches for the Background template AVO plot STARTS here # a1 = 0.10 # transparency for AVO background template patches rangex = 90 band = 0.04 # thickness of Class 2 band # CLASS 1 Path1 = mpath.Path path_data1 = [ (Path1.MOVETO, (rangex * 0, 0.04)), (Path1.CURVE4, (rangex * 0.4, 0.05)), (Path1.CURVE4, (rangex * 0.6, -0.015)), (Path1.CURVE4, (rangex * 1.0, -band)), (Path1.LINETO, (rangex * 1.0, 1.0)), (Path1.LINETO, (rangex * 0.0, 1.0)), (Path1.CLOSEPOLY, (rangex * 0.0, 1.0)), ] codes1, verts1 = zip(*path_data1) path1 = mpath.Path(verts1, codes1) patch1 = mpatches.PathPatch(path1, facecolor='r', alpha=a1, ec='none') ax.add_patch(patch1) # plot control points and connecting lines x1, y1 = zip(*path1.vertices) #line1, = ax.plot(x1, y1, 'go-') # CLASS 2p Path2P = mpath.Path path_data2P = [ (Path2P.MOVETO, (rangex * 0, band)), (Path2P.CURVE4, (rangex * 0.4, 0.05)), (Path2P.CURVE4, (rangex * 0.6, -0.015)), (Path2P.CURVE4, (rangex * 1.0, -band)), (Path2P.LINETO, (rangex * 1.0, -(band + band))), (Path2P.CURVE4, (rangex * 0.6, -(0.015 + band))), (Path2P.CURVE4, (rangex * 0.4, 0.05 - band)), (Path2P.CURVE4, (rangex * 0.0, 0.0)), (Path2P.CLOSEPOLY, (rangex * 0.0, 0.0)), ] codes2P, verts2P = zip(*path_data2P) path2P = mpath.Path(verts2P, codes2P) patch2P = mpatches.PathPatch(path2P, facecolor='yellow', alpha=a1, ec='none') ax.add_patch(patch2P) # plot control points and connecting lines x2, y2 = zip(*path2P.vertices) #line2, = ax.plot(x2, y2, 'ro-') # CLASS 2 Path2 = mpath.Path path_data2 = [ (Path2.MOVETO, (rangex * 0.0, 0.0)), (Path2.CURVE4, (rangex * 0.4, 0.05 - band)), (Path2.CURVE4, (rangex * 0.6, -(0.015 + band))), (Path2.CURVE4, (rangex * 1.0, -(band + band))), (Path2.LINETO, (rangex * 1.0, -(3 * band))), (Path2.CURVE4, (rangex * 0.6, -(0.015 + 2 * band))), (Path2.CURVE4, (rangex * 0.4, 0.05 - (0.0 + 2 * band))), (Path2.CURVE4, (rangex * 0.0, -band)), (Path2.CLOSEPOLY, (rangex * 0.0, 0.0)), ] codes2, verts2 = zip(*path_data2) path2 = mpath.Path(verts2, codes2) patch2 = mpatches.PathPatch(path2, facecolor='green', alpha=a1, ec='none') ax.add_patch(patch2) # plot control points and connecting lines x2, y2 = zip(*path2.vertices) #line2, = ax.plot(x2, y2, 'ro-') # CLASS 3 Path3 = mpath.Path path_data3 = [ (Path3.MOVETO, (rangex * 0.0, -band)), (Path3.CURVE4, (rangex * 0.4, 0.05 - (0.0 + 2 * band))), (Path3.CURVE4, (rangex * 0.6, -(0.015 + 2 * band))), (Path3.CURVE4, (rangex * 1.0, -(3 * band))), (Path3.LINETO, (rangex * 1.0, -1.0)), (Path3.LINETO, (rangex * 0.0, -1.0)), (Path3.CLOSEPOLY, (rangex * 0.0, -1.0)), ] codes3, verts3 = zip(*path_data3) path3 = mpath.Path(verts3, codes3) patch3 = mpatches.PathPatch(path3, facecolor='blue', alpha=a1, ec='none') ax.add_patch(patch3) # plot control points and connecting lines x3, y3 = zip(*path3.vertices) #line2, = ax.plot(x2, y2, 'ro-') ax.grid() ax.text(0.98, 0.98, 'Amplitude vs angle', verticalalignment='top', horizontalalignment='right', transform=ax.transAxes, color='black', fontsize=9, fontweight='bold', alpha=0.5) ax.spines['right'].set_color('none') ax.spines['top'].set_color('none') ax.xaxis.set_ticks_position('bottom') ax.spines['bottom'].set_position(('data', 0)) ax.yaxis.set_ticks_position('left') ax.spines['left'].set_position(('data', 0)) for label in ax.get_xticklabels() + ax.get_yticklabels(): label.set_fontsize(8) label.set_alpha(0.5) plt.grid() # Class 1 label # y-values for class labels 1,2p, 2, 3, and 4 respectively ylabelcntrs = [0.35, 0.025, -0.025, -0.4, -0.2] xctrs = 10 fs = 10 # fontsize a2 = 0.4 # transparency value for Gradient vs Intercept text ax.text(xctrs, ylabelcntrs[0], 'CLASS 1', verticalalignment='center', horizontalalignment='left', color='red', fontsize=fs, fontweight='bold', alpha=a2) # Class 2p label ax.text(xctrs, ylabelcntrs[1], 'CLASS 2p', verticalalignment='center', horizontalalignment='left', rotation=-3, color='#EEC900', fontsize=fs, fontweight='bold', alpha=a2 * 1.5) # Class 2 label ax.text(xctrs, ylabelcntrs[2], 'CLASS 2', verticalalignment='center', horizontalalignment='left', rotation=-3, color='green', fontsize=fs, fontweight='bold', alpha=a2) # Class 3 label ax.text(xctrs, ylabelcntrs[3], 'CLASS 3', verticalalignment='center', horizontalalignment='left', rotation=-15, color='blue', fontsize=fs, fontweight='bold', alpha=a2) # Class 4 label ax.text(xctrs, ylabelcntrs[4], 'CLASS 4', verticalalignment='center', horizontalalignment='left', rotation=15, color='#B048B5', fontsize=fs, fontweight='bold', alpha=a2) # # Patches for background template AVO plot ENDS here # # ax_2 the AB plot plt.subplot(G[0 + shift:3 + shift, :]) plt.hold(True) max_ang = args.max_angle # Max ang for computing gradient for i in range(args.iterations - 1): plt.scatter(reflect[i, 0], (reflect[i, max_ang] - reflect[i, 0]), color='grey', s=20, alpha=np.max((30. / args.iterations, 0.2))) # Plot the average of the dots plt.scatter(ave_reflect[0], ave_reflect[max_ang] - ave_reflect[0], color='black', s=40, alpha=0.5) # Annotation and making it nice plt.xticks([]), plt.yticks([]) ax = plt.gca() ax.spines['right'].set_color('none') ax.spines['top'].set_color('none') ax.xaxis.set_ticks_position('bottom') ax.spines['bottom'].set_position(('data', 0)) ax.spines['bottom'].set_alpha(0.5) ax.yaxis.set_ticks_position('left') ax.spines['left'].set_position(('data', 0)) ax.spines['left'].set_alpha(0.5) plt.grid() # axis limits ylimits = (np.amin((-.3, np.nanmin(reflect[:, 50] - reflect[:, 0]))), np.amax((.3, np.nanmax(reflect[:, 50] - reflect[:, 0])))) xlimits = (np.amin( (-.3, np.nanmin(reflect[:, 0]))), np.amax((.3, np.nanmax(reflect[:, 0])))) plt.ylim(ylimits) plt.xlim(xlimits) # axis labels ax.text( xlimits[1] - 0.05, 0.025, 'intercept', verticalalignment='center', horizontalalignment='right', #transform=ax.transAxes, color='black', fontsize=8, alpha=0.5) ax.text( 0.025, ylimits[1] - 0.05, 'gradient', rotation=90, verticalalignment='top', horizontalalignment='center', #transform=ax.transAxes, color='black', fontsize=8, alpha=0.5) # # Patches for background Gradient vs Intercept template STARTS here # x = np.arange(xlimits[0], xlimits[1], 0.01) y = np.arange(ylimits[0], ylimits[1], 0.01) s0 = -x shift2 = 0.04 #width for class2 width in plot height_ellipse = 0.05 width_ellipse = 3.0 # Plot background trend (diagonal line) ax.plot(x, s0, color='black', alpha=a1) # add a rectangle for class 2 neg lowleft2 = (-shift2, -1.0) class2neg = mpatches.Rectangle(lowleft2, width=abs(lowleft2[0]), height=1.0, color='green', alpha=a1, ec="white", lw=4) ax.add_patch(class2neg) # add a rectange for class 2 pos lowleft2pos = (0.0, 0.0) class2pos = mpatches.Rectangle(lowleft2pos, width=abs(lowleft2[0]), height=1.0, color='green', alpha=a1, ec="white", lw=4) ax.add_patch(class2pos) # add a rectange for class 2p pos lowleft2Ppos = (-shift2, 0.0) class2Ppos = mpatches.Rectangle(lowleft2Ppos, width=abs(lowleft2[0]), height=1.0, color='yellow', alpha=a1, ec="white", lw=4) ax.add_patch(class2Ppos) # add a rectange for class 2p neg lowleft2Pneg = (0.0, -1.0) class2Ppos = mpatches.Rectangle(lowleft2Pneg, width=abs(lowleft2[0]), height=1.0, color='yellow', alpha=a1, ec="white", lw=4) ax.add_patch(class2Ppos) # add rectangle for lower left quadrant class 3 lowleft3neg = (-1.0, -1.0) class3neg = mpatches.Rectangle(lowleft3neg, width=1.0 + lowleft2[0], height=1.0, color='blue', alpha=a1, ec='none') ax.add_patch(class3neg) # add rectange for upper right quadrant class 3 lowleft3pos = (shift2, 0) class3pos = mpatches.Rectangle(lowleft3pos, width=1.0 + lowleft2[0], height=1.0, color='blue', alpha=a1, ec='none') ax.add_patch(class3pos) # add a Polygon for Class 4 upper left quadrant # add a path patch Path4u = mpath.Path path_data4u = [(Path4u.MOVETO, [-1.0, 0.0]), (Path4u.LINETO, [-1.0, 1.0]), (Path4u.LINETO, [-shift2, shift2]), (Path4u.LINETO, [-shift2, 0]), (Path4u.CLOSEPOLY, [-shift2, 0.0])] codes4u, verts4u = zip(*path_data4u) path4u = mpath.Path(verts4u, codes4u) patch4u = mpatches.PathPatch( path4u, facecolor='#B048B5', # purple alpha=a1, ec='none') ax.add_patch(patch4u) # add a Polygon for Class 4 lower right quadrant Path4l = mpath.Path path_data4l = [(Path4l.MOVETO, [shift2, 0.0]), (Path4l.LINETO, [1.0, 0.0]), (Path4l.LINETO, [1.0, -1.0]), (Path4l.LINETO, [shift2, -shift2]), (Path4l.CLOSEPOLY, [shift2, -shift2])] codes4l, verts4l = zip(*path_data4l) path4l = mpath.Path(verts4l, codes4l) patch4l = mpatches.PathPatch( path4l, facecolor='#B048B5', # purple alpha=a1, ec='none') ax.add_patch(patch4l) # Add a Polygon for the Class 1 upper right quadrant Path1u = mpath.Path path_data1u = [(Path1u.MOVETO, [-shift2, shift2]), (Path1u.LINETO, [-1.0, 1.0]), (Path1u.LINETO, [-shift2, 1.0]), (Path1u.CLOSEPOLY, [-shift2, shift2])] codes1u, verts1u = zip(*path_data1u) path1u = mpath.Path(verts1u, codes1u) patch1u = mpatches.PathPatch(path1u, facecolor='red', alpha=a1, ec='none') ax.add_patch(patch1u) # Add a Polygone for the Class 1 lower left quadrant Path1l = mpath.Path path_data1l = [(Path1l.MOVETO, [shift2, -shift2]), (Path1l.LINETO, [shift2, -1.0]), (Path1l.LINETO, [1.0, -1.0]), (Path1l.CLOSEPOLY, [shift2, -shift2])] codes1l, verts1l = zip(*path_data1l) path1l = mpath.Path(verts1l, codes1l) patch1l = mpatches.PathPatch(path1l, facecolor='red', alpha=a1, ec='none') ax.add_patch(patch1l) # Draw ellipse xy = np.hstack((0, 0)) bkgd = collections.EllipseCollection(widths=width_ellipse, heights=height_ellipse, angles=135, units='xy', offsets=xy, transOffset=ax.transData, facecolor='grey', edgecolor='none', alpha=0.15) ax.add_collection(bkgd) #Get rid of axes spines ax.spines['right'].set_color('none') ax.spines['top'].set_color('none') ax.xaxis.set_ticks_position('bottom') ax.spines['bottom'].set_position(('data', 0)) ax.spines['bottom'].set_alpha(0.5) ax.yaxis.set_ticks_position('left') ax.spines['left'].set_position(('data', 0)) ax.spines['left'].set_alpha(0.5) # Annotation for Gradient vs Intercept annotation ax.text(0.98, 0.98, 'Gradient vs intercept', verticalalignment='top', horizontalalignment='right', transform=ax.transAxes, color='black', fontsize=9, fontweight='bold', alpha=0.50) for label in ax.get_xticklabels() + ax.get_yticklabels(): label.set_fontsize(8) label.set_alpha(0.5) # Class 1 label ax.text(3 * shift2, ylimits[0] + shift2, 'CLASS 1', verticalalignment='center', horizontalalignment='center', color='red', fontsize=fs, fontweight='bold', alpha=a2) # Class 2 label ax.text(-0.5 * shift2, ylimits[0] + 0.5 * shift2, 'CLASS 2', verticalalignment='bottom', horizontalalignment='center', rotation=90, color='green', fontsize=fs, fontweight='bold', alpha=a2) # Class 2P label ax.text(0.5 * shift2, ylimits[0] + 0.5 * shift2, 'CLASS 2p', verticalalignment='bottom', horizontalalignment='center', rotation=90, color='#EEC900', fontsize=fs, fontweight='bold', alpha=a2 * 1.5) # Class 3 label ax.text(-3 * shift2, ylimits[0] + shift2, 'CLASS 3', verticalalignment='center', horizontalalignment='right', color='blue', fontsize=fs, fontweight='bold', alpha=a2) # Class 4 label ax.text(-3 * shift2, shift2, 'CLASS 4', verticalalignment='center', horizontalalignment='right', color='#B048B5', fontsize=fs, fontweight='bold', alpha=a2) # Background label angle = -45 ax.text(0.1 * height_ellipse, 0.1 * height_ellipse, 'background', verticalalignment='center', horizontalalignment='center', rotation=angle, transform=ax.transData, color='black', fontsize=fs, fontweight='bold', alpha=a2 / 2.0) return get_figure_data(), {"mean critical angle": critical_angle}