Skip to content

Commit 18a42a8

Browse files
authored
Merge pull request #3211 from JdeRobot/end_to_end_visual_control_exercise
[New Exercise] RoboticsAcademy: End-to-End Visual Control
2 parents a403070 + c5cc857 commit 18a42a8

File tree

19 files changed

+635
-4
lines changed

19 files changed

+635
-4
lines changed

database/exercises/db.sql

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ COPY public.exercises (id, exercise_id, name, description, tags, status, url) FR
134134
19 human_detection Human Detection Deep learning-based human detection. ["Computer Vision","Deep Learning"] ACTIVE https://jderobot.github.io/RoboticsAcademy/exercises/ComputerVision/human_detection
135135
20 drone_gymkhana Drone Gymkhana Drone Gymkhana ["ROS2","Drones"] ACTIVE https://jderobot.github.io/RoboticsAcademy/exercises/Drones/drone_gymkhana
136136
21 power_tower_inspection Power Tower Inspection Power Tower inspection using drones ["ROS2","Drones"] ACTIVE https://jderobot.github.io/RoboticsAcademy/exercises/Drones/power_tower_inspection
137+
22 end_to_end_visual_control End to End Visual Control End to end visual control using deep learning. ["Computer Vision","Deep Learning","AUTONOMOUS DRIVING"] PROTOTYPE https://jderobot.github.io/RoboticsAcademy/exercises/AutonomousCars/end_to_end_visual_control
137138
\.
138139

139140
--
@@ -174,8 +175,12 @@ COPY public.exercises_universes (id, exercise_id, universe_id) FROM stdin;
174175
30 16 32
175176
31 14 33
176177
32 17 34
177-
33 20 36
178-
34 21 37
178+
33 22 5
179+
34 22 6
180+
35 22 20
181+
36 22 21
182+
37 20 36
183+
38 21 37
179184
\.
180185
-- 30 16 3
181186

@@ -246,6 +251,9 @@ COPY public.exercises_tools (id, exercise_id, tool_id) FROM stdin;
246251
60 21 web_gui
247252
61 21 console
248253
62 21 simulator
254+
63 22 console
255+
64 22 simulator
256+
65 22 web_gui
249257
\.
250258

251259
--
@@ -385,4 +393,4 @@ ADD CONSTRAINT exercises_w_exercise_id_e4e984bc_fk_exercises FOREIGN KEY (exerci
385393

386394
--
387395
-- PostgreSQL database dump complete
388-
--
396+
--
1.66 MB
Loading
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import time
2+
from datetime import datetime
3+
4+
start_time_internal_freq_control = None
5+
6+
7+
def tick(ideal_cycle: int = 50):
8+
global start_time_internal_freq_control
9+
finish_time = datetime.now()
10+
ideal_ms = 1000 / ideal_cycle
11+
12+
if start_time_internal_freq_control == None:
13+
start_time_internal_freq_control = finish_time
14+
return
15+
16+
dt = finish_time - start_time_internal_freq_control
17+
ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0
18+
19+
if ms < ideal_ms:
20+
time.sleep((ideal_ms - ms) / 1000.0)
21+
22+
start_time_internal_freq_control = finish_time
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import rclpy
2+
import sys
3+
import threading
4+
import time
5+
6+
from hal_interfaces.general.motors import MotorsNode
7+
from hal_interfaces.general.camera import CameraNode
8+
9+
10+
IMG_WIDTH = 320
11+
IMG_HEIGHT = 240
12+
13+
freq = 90.0 # Less than this wont work
14+
15+
16+
# Mutes exceptions
17+
def custom_thread_excepthook(args):
18+
if "spin" in args.thread.name:
19+
return
20+
sys.__excepthook__(args.exc_type, args.exc_value, args.exc_traceback)
21+
22+
23+
threading.excepthook = custom_thread_excepthook
24+
25+
26+
def __auto_spin() -> None:
27+
while rclpy.ok():
28+
try:
29+
executor.spin_once(timeout_sec=0)
30+
except Exception:
31+
pass
32+
time.sleep(1 / freq)
33+
34+
35+
# ROS2 init
36+
if not rclpy.ok():
37+
rclpy.init(args=sys.argv)
38+
39+
# ROS2 Topics
40+
motor_node = MotorsNode("/cmd_vel", 4, 0.3)
41+
camera_node = CameraNode("/cam_f1_left/image_raw")
42+
43+
# Spin nodes so that subscription callbacks load topic data
44+
executor = rclpy.executors.MultiThreadedExecutor()
45+
executor.add_node(camera_node)
46+
executor_thread = threading.Thread(target=__auto_spin, daemon=True)
47+
executor_thread.start()
48+
49+
50+
# Get Image from ROS Driver Camera
51+
def getImage():
52+
image = camera_node.getImage()
53+
while image == None:
54+
image = camera_node.getImage()
55+
return image.data
56+
57+
58+
# Set the velocity
59+
def setV(velocity):
60+
motor_node.sendV(float(velocity))
61+
62+
63+
# Set the angular velocity
64+
def setW(velocity):
65+
motor_node.sendW(float(velocity))
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import cv2
2+
import base64
3+
import json
4+
import threading
5+
import rclpy
6+
from gui_interfaces.general.measuring_threading_gui import MeasuringThreadingGUI
7+
from console_interfaces.general.console import start_console
8+
from hal_interfaces.general.odometry import OdometryNode
9+
from lap import Lap
10+
11+
import sys
12+
13+
sys.path.insert(0, "/RoboticsApplicationManager")
14+
15+
from manager.ram_logging.log_manager import LogManager
16+
17+
# Graphical User Interface Class
18+
19+
20+
class WebGUI(MeasuringThreadingGUI):
21+
def __init__(self, host="ws://127.0.0.1:2303"):
22+
super().__init__(host)
23+
24+
self.image_to_be_shown = None
25+
self.image_to_be_shown_updated = False
26+
self.image_show_lock = threading.Lock()
27+
28+
# Payload vars
29+
self.payload = {"image": "", "lap": "", "map": ""}
30+
# TODO: maybe move this to HAL and have it be hybrid
31+
self.pose3d_object = OdometryNode("/odom")
32+
executor = rclpy.executors.MultiThreadedExecutor()
33+
executor.add_node(self.pose3d_object)
34+
executor_thread = threading.Thread(target=executor.spin, daemon=True)
35+
executor_thread.start()
36+
self.lap = Lap(self.pose3d_object)
37+
38+
self.start()
39+
40+
# Process incoming messages to the GUI
41+
def gui_in_thread(self, ws, message):
42+
43+
# In this case, incoming msgs can only be acks
44+
if "ack" in message:
45+
with self.ack_lock:
46+
self.ack = True
47+
self.ack_frontend = True
48+
elif "start" in message:
49+
self.lap.unpause()
50+
elif "pause" in message:
51+
self.lap.pause()
52+
else:
53+
LogManager.logger.error("Unsupported msg")
54+
55+
# Prepares and sends a map to the websocket server
56+
def update_gui(self):
57+
58+
payload = self.payloadImage()
59+
self.payload["image"] = json.dumps(payload)
60+
61+
# Payload Lap Message
62+
lapped = self.lap.check_threshold()
63+
self.payload["lap"] = ""
64+
if lapped != None:
65+
self.payload["lap"] = str(lapped)
66+
67+
# Payload Map Message
68+
pose = self.pose3d_object.getPose3d()
69+
pos_message = str((pose.x, pose.y))
70+
self.payload["map"] = pos_message
71+
72+
message = json.dumps(self.payload)
73+
self.send_to_client(message)
74+
75+
# Function to prepare image payload
76+
# Encodes the image as a JSON string and sends through the WS
77+
def payloadImage(self):
78+
with self.image_show_lock:
79+
image_to_be_shown_updated = self.image_to_be_shown_updated
80+
image_to_be_shown = self.image_to_be_shown
81+
82+
image = image_to_be_shown
83+
payload = {"image": "", "shape": ""}
84+
85+
if not image_to_be_shown_updated:
86+
return payload
87+
88+
shape = image.shape
89+
frame = cv2.imencode(".JPEG", image)[1]
90+
encoded_image = base64.b64encode(frame)
91+
92+
payload["image"] = encoded_image.decode("utf-8")
93+
payload["shape"] = shape
94+
95+
with self.image_show_lock:
96+
self.image_to_be_shown_updated = False
97+
98+
return payload
99+
100+
# Function for student to call
101+
def showImage(self, image):
102+
with self.image_show_lock:
103+
self.image_to_be_shown = image
104+
self.image_to_be_shown_updated = True
105+
106+
107+
host = "ws://127.0.0.1:2303"
108+
gui = WebGUI(host)
109+
110+
# Redirect the console
111+
start_console()
112+
113+
114+
# Expose to the user
115+
def showImage(image):
116+
gui.showImage(image)
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
from datetime import datetime
2+
3+
4+
class Lap:
5+
def __init__(self, pose3d):
6+
self.pose3d = pose3d
7+
self.reset()
8+
9+
# Function to check for threshold
10+
# And incrementing the running time
11+
def check_threshold(self):
12+
pose3d = self.pose3d.getPose3d()
13+
14+
# Variable for pause
15+
if self.pause_condition == False:
16+
# Running condition to calculate the current time
17+
# Time calculated by adding increments from each iteration
18+
if self.start_time != 0 and self.lap_rest == False:
19+
if self.lap_time == 0:
20+
self.lap_time = datetime.now() - self.start_time
21+
else:
22+
self.lap_time += datetime.now() - self.start_time
23+
24+
self.start_time = datetime.now()
25+
26+
# Condition when the time starts running
27+
if self.start_time == 0 and self.lap_rest == True:
28+
self.start_time = datetime.now()
29+
self.lap_rest = False
30+
31+
return self.lap_time
32+
33+
# Function to return lap time
34+
def return_lap_time(self):
35+
return self.lap_time
36+
37+
# Function to reset
38+
def reset(self):
39+
# Reset the initial variables
40+
self.start_time = 0
41+
self.lap_time = 0
42+
43+
self.lap_rest = True
44+
self.buffer = False
45+
46+
self.pause_condition = False
47+
48+
# Function to pause
49+
def pause(self):
50+
self.pause_condition = True
51+
52+
# Function to unpause
53+
def unpause(self):
54+
# To enable unpause button to be used again and again
55+
if self.pause_condition == True:
56+
# Next time the time will be incremented accordingly
57+
self.start_time = datetime.now()
58+
59+
self.pause_condition = False
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
model_dir_path = "/workspace/code/model.onnx"
2+
3+
4+
# This file is part of the Human Detection ROS2 package.
5+
def model_path_func() -> str:
6+
return model_dir_path
7+
8+
9+
model_path = model_path_func()
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import * as React from "react";
2+
import {Fragment} from "react";
3+
4+
import "./css/FollowLineRR.css";
5+
6+
const FollowLineRR = (props) => {
7+
return (
8+
<Fragment>
9+
{props.children}
10+
</Fragment>
11+
);
12+
};
13+
14+
export default FollowLineRR;

0 commit comments

Comments
 (0)