milind upadhyay | 96016ca | 2021-02-20 15:28:50 -0800 | [diff] [blame] | 1 | #!/usr/bin/python3 |
| 2 | |
| 3 | from rect import Rect |
| 4 | import ball_detection |
| 5 | |
| 6 | # Creates a UI for a user to select the regions in a camera image where the balls could be placed. |
| 7 | # After the balls have been placed on the field and they submit the regions, |
| 8 | # it will take another picture and based on the yellow regions in that picture it will determine where the |
| 9 | # balls are. This tells us which path the current field is. It then sends the Alliance and Letter of the path |
| 10 | # with aos_send to the /camera channel for the robot to excecute the spline for that path. |
| 11 | |
| 12 | from enum import Enum |
| 13 | import glog |
| 14 | import json |
| 15 | import matplotlib.patches as patches |
| 16 | import matplotlib.pyplot as plt |
| 17 | from matplotlib.widgets import Button |
| 18 | import numpy as np |
| 19 | import os |
| 20 | |
| 21 | class Alliance(Enum): |
| 22 | kRed = "red" |
| 23 | kBlue = "blue" |
| 24 | kUnknown = None |
| 25 | |
| 26 | class Letter(Enum): |
| 27 | kA = "A" |
| 28 | kB = "B" |
| 29 | |
| 30 | |
| 31 | NUM_RECTS = 4 |
| 32 | AOS_SEND_PATH = "bazel-bin/aos/aos_send" |
| 33 | |
| 34 | # The minimum percentage of yellow for a region of a image to |
| 35 | # be considered to have a ball |
| 36 | BALL_PCT_THRESHOLD = 50 |
| 37 | |
| 38 | rects = [Rect(None, None, None, None)] |
| 39 | |
| 40 | # current index in rects list |
| 41 | rect_index = 0 |
| 42 | |
| 43 | fig, img_ax = plt.subplots() |
| 44 | |
| 45 | txt = img_ax.text(0, 0, "", size = 10, backgroundcolor = "white") |
| 46 | |
| 47 | confirm = Button(plt.axes([0.7, 0.05, 0.1, 0.075]), "Confirm") |
| 48 | cancel = Button(plt.axes([0.81, 0.05, 0.1, 0.075]), "Cancel") |
| 49 | submit = Button(plt.axes([0.4, 0.4, 0.1, 0.1]), "Submit") |
| 50 | |
| 51 | def draw_txt(): |
| 52 | alliance = (Alliance.kRed if rect_index % 2 == 0 else Alliance.kBlue) |
| 53 | letter = (Letter.kA if rect_index < (NUM_RECTS / 2) else Letter.kB) |
| 54 | txt.set_text("Click on top left point and bottom right point for " + |
| 55 | alliance.value + ", path " + letter.value) |
| 56 | txt.set_color(alliance.value) |
| 57 | |
| 58 | |
| 59 | def on_confirm(event): |
| 60 | global rect_index |
| 61 | if rects[rect_index].x1 != None and rects[rect_index].x2 != None: |
| 62 | confirm.ax.set_visible(False) |
| 63 | cancel.ax.set_visible(False) |
| 64 | rect_index += 1 |
| 65 | clear_rect() |
| 66 | if rect_index == NUM_RECTS: |
| 67 | submit.ax.set_visible(True) |
| 68 | else: |
| 69 | draw_txt() |
| 70 | rects.append(Rect(None, None, None, None)) |
| 71 | plt.show() |
| 72 | |
| 73 | def on_cancel(event): |
| 74 | global rect_index |
| 75 | if rect_index < NUM_RECTS: |
| 76 | confirm.ax.set_visible(False) |
| 77 | cancel.ax.set_visible(False) |
| 78 | clear_rect() |
| 79 | rects[rect_index].x1 = None |
| 80 | rects[rect_index].y1 = None |
| 81 | rects[rect_index].x2 = None |
| 82 | rects[rect_index].y2 = None |
| 83 | plt.show() |
| 84 | |
| 85 | def on_submit(event): |
| 86 | plt.close("all") |
| 87 | pcts = ball_detection.pct_yellow(ball_detection.capture_img(), rects) |
| 88 | if len(pcts) == len(rects): |
| 89 | paths = [] |
| 90 | for i in range(len(pcts)): |
| 91 | alliance = (Alliance.kRed if i % 2 == 0 else Alliance.kBlue) |
| 92 | letter = (Letter.kA if i < NUM_RECTS / 2 else Letter.kB) |
| 93 | paths.append({"alliance" : alliance.name, "letter" : letter.name}) |
| 94 | max_index = np.argmax(pcts) |
| 95 | path = paths[max_index] |
| 96 | # Make sure that exactly one percentage is >= the threshold |
| 97 | rects_with_balls = np.where(pcts >= BALL_PCT_THRESHOLD)[0].size |
| 98 | glog.info("rects_with_balls: %s" % rects_with_balls) |
| 99 | if rects_with_balls != 1: |
| 100 | path["alliance"] = Alliance.kUnknown.name |
| 101 | glog.warn("More than one ball found, path is unknown" if rects_with_balls > 1 else |
| 102 | "No balls found") |
| 103 | glog.info("Path is %s" % path) |
| 104 | os.system(AOS_SEND_PATH + " --config bazel-bin/y2020/config.json " + |
| 105 | "/pi1/camera y2020.vision.GalacticSearchPath '" + json.dumps(path) + "'") |
| 106 | |
| 107 | for j in range(len(pcts)): |
| 108 | glog.info("%s: %s%% yellow" % (rects[j], pcts[j])) |
| 109 | else: |
| 110 | glog.error("Error: len of pcts (%u) != len of rects: (%u)" % (len(pcts), len(rects))) |
| 111 | |
| 112 | # Clears rect on screen |
| 113 | def clear_rect(): |
| 114 | if len(img_ax.patches) == 0: |
| 115 | glog.error("There were no patches found in img_ax") |
| 116 | else: |
| 117 | img_ax.patches[-1].remove() |
| 118 | |
| 119 | def on_click(event): |
| 120 | # This will get called when user clicks on Submit button, don't want to override the points on |
| 121 | # the last rect. Additionally, the event xdata or ydata will be None if the user clicks out of |
| 122 | # the bounds of the axis |
| 123 | if rect_index < NUM_RECTS and event.xdata != None and event.ydata != None: |
| 124 | if rects[rect_index].x1 == None: |
| 125 | rects[rect_index].x1, rects[rect_index].y1 = int(event.xdata), int(event.ydata) |
| 126 | elif rects[rect_index].x2 == None: |
| 127 | rects[rect_index].x2, rects[rect_index].y2 = int(event.xdata), int(event.ydata) |
| 128 | if rects[rect_index].x2 < rects[rect_index].x1: |
| 129 | rects[rect_index].x2 = rects[rect_index].x1 + (rects[rect_index].x1 - rects[rect_index].x2) |
| 130 | if rects[rect_index].y2 < rects[rect_index].y1: |
| 131 | rects[rect_index].y2 = rects[rect_index].y1 + (rects[rect_index].y1 - rects[rect_index].y2) |
| 132 | |
| 133 | img_ax.add_patch(patches.Rectangle((rects[rect_index].x1, rects[rect_index].y1), |
| 134 | rects[rect_index].x2 - rects[rect_index].x1, rects[rect_index].y2 - rects[rect_index].y1, |
| 135 | edgecolor = 'r', linewidth = 1, facecolor="none")) |
| 136 | confirm.ax.set_visible(True) |
| 137 | cancel.ax.set_visible(True) |
| 138 | plt.show() |
| 139 | else: |
| 140 | glog.info("Either submitted or user pressed out of the bounds of the axis") |
| 141 | |
| 142 | def setup_button(button, on_clicked): |
| 143 | button.on_clicked(on_clicked) |
| 144 | button.ax.set_visible(False) |
| 145 | |
| 146 | def main(): |
| 147 | glog.setLevel("INFO") |
| 148 | |
| 149 | img_ax.imshow(ball_detection.capture_img()) |
| 150 | |
| 151 | fig.canvas.mpl_connect("button_press_event", on_click) |
| 152 | setup_button(confirm, on_confirm) |
| 153 | setup_button(cancel, on_cancel) |
| 154 | setup_button(submit, on_submit) |
| 155 | draw_txt() |
| 156 | plt.show() |
| 157 | |
| 158 | if __name__ == "__main__": |
| 159 | main() |