blob: e7f8402c7ba4b54e939373b8121682c9efdcc72e [file] [log] [blame]
milind upadhyay96016ca2021-02-20 15:28:50 -08001#!/usr/bin/python3
2
3from rect import Rect
4import 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
12from enum import Enum
13import glog
14import json
15import matplotlib.patches as patches
16import matplotlib.pyplot as plt
17from matplotlib.widgets import Button
18import numpy as np
19import os
20
21class Alliance(Enum):
22 kRed = "red"
23 kBlue = "blue"
24 kUnknown = None
25
26class Letter(Enum):
27 kA = "A"
28 kB = "B"
29
30
31NUM_RECTS = 4
32AOS_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
36BALL_PCT_THRESHOLD = 50
37
38rects = [Rect(None, None, None, None)]
39
40# current index in rects list
41rect_index = 0
42
43fig, img_ax = plt.subplots()
44
45txt = img_ax.text(0, 0, "", size = 10, backgroundcolor = "white")
46
47confirm = Button(plt.axes([0.7, 0.05, 0.1, 0.075]), "Confirm")
48cancel = Button(plt.axes([0.81, 0.05, 0.1, 0.075]), "Cancel")
49submit = Button(plt.axes([0.4, 0.4, 0.1, 0.1]), "Submit")
50
51def 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
59def 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
73def 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
85def 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
113def 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
119def 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
142def setup_button(button, on_clicked):
143 button.on_clicked(on_clicked)
144 button.ax.set_visible(False)
145
146def 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
158if __name__ == "__main__":
159 main()