def draw_bbox( ax: matplotlib.axes.Axes, bbox: List, color: Tuple[int], text: str = None, alpha: float = 0.2, ) -> None: """ Draw a bounding box on the matplotlib axes object. """ # TODO: fix the bordering in matplotlib so that the pixels # line up appropriately bounding boxes are [x, y, w, h] log.debug(f'Drawing bbox {bbox} {color}') r = Rectangle((bbox[0], bbox[1]), (bbox[2]), (bbox[3]), linewidth=3, facecolor=color, edgecolor=color, alpha=alpha, ) # Add text above box if text is not None: ax.text( x=bbox[0], y=bbox[1], s=text, color=color, weight='bold', fontsize=6, ha='left', va='bottom', ) ax.add_patch(r)
def draw_elements( ax: mpl.axes.Axes, lattice: Lattice, *, labels: bool = True, location: str = "top", ): """Draw the elements of a lattice onto a matplotlib axes.""" x_min, x_max = ax.get_xlim() y_min, y_max = ax.get_ylim() rect_height = 0.05 * (y_max - y_min) if location == "top": y0 = y_max = y_max + rect_height else: y0 = y_min - rect_height y_min -= 3 * rect_height plt.hlines(y0, x_min, x_max, color="black", linewidth=1) ax.set_ylim(y_min, y_max) sign = -1 start = end = 0 for element, group in groupby(lattice.sequence): start = end end += element.length * sum(1 for _ in group) if end <= x_min: continue elif start >= x_max: break try: color = ELEMENT_COLOR[type(element)] except KeyError: continue y0_local = y0 if isinstance(element, Dipole) and element.angle < 0: y0_local += rect_height / 4 ax.add_patch( plt.Rectangle( (max(start, x_min), y0_local - 0.5 * rect_height), min(end, x_max) - max(start, x_min), rect_height, facecolor=color, clip_on=False, zorder=10, )) if labels and type(element) in {Dipole, Quadrupole}: sign = -sign ax.annotate( element.name, xy=(0.5 * (start + end), y0 + sign * rect_height), fontsize=FONT_SIZE, ha="center", va="bottom" if sign > 0 else "top", annotation_clip=False, zorder=11, )
def draw_segmentation( ax: matplotlib.axes.Axes, segmentation: List, color: Tuple[int], alpha: float = 0.6, ) -> None: """ Draw a segmentation polygon on the matplotlib axes object. """ log.debug(f'Drawing segmentation {segmentation} {color}') for seg in segmentation: p = Polygon(np.array(seg).reshape((int(len(seg) / 2), 2)), linewidth=3, color=color, alpha=alpha) ax.add_patch(p)
def draw_poly(ax: mpl.axes.Axes, left: np.ndarray, bottom: np.ndarray, top: np.ndarray, right: np.ndarray, facecolor: str, edgecolor: str, zorder: int) -> None: '''Draw a set of polygrams given parrallel numpy arrays of left, bottom, top, right points''' XY = np.array([[left, left, right, right], [bottom, top, top, bottom]]).T barpath = path.Path.make_compound_path_from_polys(XY) # Clean path to get rid of 0, 0 points. Seems to be a matplotlib bug. If we don't ylim lower bound is set to 0 v = [] c = [] for seg in barpath.iter_segments(): vertices, command = seg if not (vertices[0] == 0. and vertices[1] == 0.): v.append(vertices) c.append(command) cleaned_path = path.Path(v, c) patch = mptch.PathPatch(cleaned_path, facecolor=facecolor, edgecolor=edgecolor, zorder=zorder) ax.add_patch(patch)
def draw_keypoints( ax: matplotlib.axes.Axes, keypoints: List, skeleton: Dict, color: Tuple[int], alpha: float = 0.8, ) -> None: """ Draws keypoints of an instance and follows the rules for keypoint connections to draw lines between appropriate keypoints. "keypoints": [x1,y1,v1,...,xk,yk,vk] - Keypoint coordinates are floats measured from the top left image corner (and are 0-indexed). - We recommend rounding coordinates to the nearest pixel to reduce file size. - v indicates visibility v=0: not labeled (in which case x=y=0) v=1: labeled but not visible v=2: labeled and visible """ log.debug("Drawing keypoints") for k1, k2 in skeleton: # HACK: 0 indexed versus 1 indexed skeleton if min(min(skeleton)) == 1: k1 -= 1 k2 -= 1 k1_x = keypoints[3 * k1 + 0] k1_y = keypoints[3 * k1 + 1] k1_v = keypoints[3 * k1 + 2] k2_x = keypoints[3 * k2 + 0] k2_y = keypoints[3 * k2 + 1] k2_v = keypoints[3 * k2 + 2] if k1_v == 1: circle = Circle((k1_x, k1_y), radius=5, edgecolor=color, facecolor="w", alpha=alpha) ax.add_patch(circle) if k1_v == 2: circle = Circle((k1_x, k1_y), radius=5, edgecolor=color, facecolor=color, alpha=alpha) ax.add_patch(circle) if k2_v == 1: circle = Circle((k2_x, k2_y), radius=5, edgecolor=color, facecolor="w", alpha=alpha) ax.add_patch(circle) if k2_v == 2: circle = Circle((k2_x, k2_y), radius=5, edgecolor=color, facecolor=color, alpha=alpha) ax.add_patch(circle) if k1_v != 0 and k2_v != 0: line = Arrow(k1_x, k1_y, k2_x - k1_x, k2_y - k1_y, color=color, alpha=alpha) ax.add_patch(line)
def floor_plan( ax: mpl.axes.Axes, lattice: Lattice, *, start_angle: float = 0, labels: bool = True, ): ax.set_aspect("equal") codes = Path.MOVETO, Path.LINETO current_angle = start_angle start = np.zeros(2) end = np.zeros(2) x_min = y_min = 0 x_max = y_max = 0 sign = 1 for element, group in groupby(lattice.sequence): start = end.copy() length = element.length * sum(1 for _ in group) if isinstance(element, Drift): color = Color.BLACK line_width = 1 else: color = ELEMENT_COLOR[type(element)] line_width = 6 # TODO: refactor current angle angle = 0 if isinstance(element, Dipole): angle = element.k0 * length radius = length / angle vec = radius * np.array([np.sin(angle), 1 - np.cos(angle)]) sin = np.sin(current_angle) cos = np.cos(current_angle) rot = np.array([[cos, -sin], [sin, cos]]) end += rot @ vec angle_center = current_angle + 0.5 * np.pi center = start + radius * np.array( [np.cos(angle_center), np.sin(angle_center)]) diameter = 2 * radius arc_angle = -90 theta1 = current_angle * 180 / np.pi theta2 = (current_angle + angle) * 180 / np.pi if angle < 0: theta1, theta2 = theta2, theta1 line = patches.Arc( center, width=diameter, height=diameter, angle=arc_angle, theta1=theta1, theta2=theta2, color=color, linewidth=line_width, ) current_angle += angle else: end += length * np.array( [np.cos(current_angle), np.sin(current_angle)]) line = patches.PathPatch(Path((start, end), codes), color=color, linewidth=line_width) x_min = min(x_min, end[0]) y_min = min(y_min, end[1]) x_max = max(x_max, end[0]) y_max = max(y_max, end[1]) ax.add_patch(line) # TODO: currently splitted elements get drawn twice if labels and isinstance(element, (Dipole, Quadrupole)): angle_center = (current_angle - 0.5 * angle) + 0.5 * np.pi sign = -sign center = 0.5 * (start + end) + 0.5 * sign * np.array( [np.cos(angle_center), np.sin(angle_center)]) ax.annotate( element.name, xy=center, fontsize=6, ha="center", va="center", # rotation=(current_angle * 180 / np.pi -90) % 180, annotation_clip=False, zorder=11, ) margin = 0.01 * max((x_max - x_min), (y_max - y_min)) ax.set_xlim(x_min - margin, x_max + margin) ax.set_ylim(y_min - margin, y_max + margin) return ax
def plot_reachable_zone( ax: matplotlib.axes.Axes, mount_model: MountModel, axis_0_west_limit: float = 110, axis_0_east_limit: float = 110, ) -> None: """Plot area(s) of sky reachable by the mount. This only accounts for what area of the sky is reachable. It does not take into account whether the mount can keep up with a specific target's trajectory. Some trajectories, especially those that pass near the mount pole, may require slew rates that exceed what the mount is capable of. This function assumes an equatorial mount with limits on the right ascension axis. Args: ax: Axes object this function will plot on. This should be generated by `make_sky_plot()`. mount_model: Mount model from which this plot will be generated. axis_0_west_limit: Western limit on axis 0 in degrees from the meridian. axis_0_east_limit: Eastern limit on axis 0 in degrees from the meridian. """ if axis_0_west_limit < 90 or axis_0_east_limit < 90: # current logic would not shade the correct regions of the polar plot raise ValueError('Axis limits less than 90 degrees from meridian are not supported') # convert from arg values to encoder position angles axis_0_west_limit = 180 - axis_0_west_limit axis_0_east_limit = 180 + axis_0_east_limit # place a dot at the position of the mount pole mount_pole_topo = mount_model.spherical_to_topocentric( UnitSphericalRepresentation( lon=0*u.deg, lat=90*u.deg, ) ) ax.plot(np.radians(mount_pole_topo.az.deg), 90.0 - mount_pole_topo.alt.deg, 'k.') for meridian_side in MeridianSide: if meridian_side == MeridianSide.EAST: color = 'blue' legend_label = 'east of mount meridian' axis_1_range = np.linspace(0, 180, 100) + mount_model.model_params.axis_1_offset.deg az = np.linspace(mount_pole_topo.az.deg, mount_pole_topo.az.deg + 180, 100) else: axis_1_range = np.linspace(180, 360, 100) + mount_model.model_params.axis_1_offset.deg color = 'red' legend_label = 'west of mount meridian' az = np.linspace(mount_pole_topo.az.deg - 180, mount_pole_topo.az.deg, 100) # add a circle patch outside the visible area of the plot purely for the purpose of # generating an entry in the legend for this region ax.add_patch(Circle((0, 100), radius=0, color=color, alpha=0.2, label=legend_label)) alt = 90*np.ones_like(az) fill_to_horizon(ax, az, alt, color=color) for axis_0 in (axis_0_west_limit, axis_0_east_limit): az = [] alt = [] for axis_1 in axis_1_range: topo = mount_model.encoders_to_topocentric( MountEncoderPositions( Longitude(axis_0*u.deg), Longitude(axis_1*u.deg), ) ) az.append(topo.az.deg) alt.append(topo.alt.deg) az = np.array(az) alt = np.array(alt) ax.plot(np.radians(az), 90.0 - alt, ':', color=color) if axis_0 == axis_0_east_limit and meridian_side == MeridianSide.EAST: fill_to_horizon(ax, az, alt, color=color) elif axis_0 == axis_0_west_limit and meridian_side == MeridianSide.EAST: fill_to_zenith(ax, az, alt, color=color) elif axis_0 == axis_0_east_limit and meridian_side == MeridianSide.WEST: fill_to_zenith(ax, az, alt, color=color) elif axis_0 == axis_0_west_limit and meridian_side == MeridianSide.WEST: fill_to_horizon(ax, az, alt, color=color)