Skip to content

Commit 00b1507

Browse files
Merge branch 's2025_teamB' into s2025_groupB_state_est
2 parents c75e655 + b4ba3bb commit 00b1507

42 files changed

Lines changed: 2210 additions & 107 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/python-app.yml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# This workflow will install Python dependencies, run tests and lint with a single version of Python
2+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
3+
4+
name: Python application
5+
6+
on:
7+
push:
8+
branches:
9+
- '**'
10+
11+
12+
permissions:
13+
contents: read
14+
15+
jobs:
16+
PEP-Guidelines:
17+
18+
runs-on: ubuntu-latest
19+
20+
steps:
21+
- uses: actions/checkout@v3
22+
- name: Set up Python 3.10
23+
uses: actions/setup-python@v3
24+
with:
25+
python-version: "3.10"
26+
- name: Install dependencies
27+
run: |
28+
python -m pip install --upgrade pip
29+
pip install flake8 flake8-docstrings pep8-naming
30+
- name: Lint with flake8
31+
run: |
32+
# stop the build if there are Python syntax errors or undefined names
33+
flake8 ./GEMstack --count --select=E9,F63,F7,F82 --show-source --statistics --exclude=__init__.py || exit 1
34+
# to enable more advanced checks on the repo, uncomment the lines below (There are around 3000 violations)
35+
# flake8 ./GEMstack --ignore=D,C901,E402,E231 --count --max-complexity=10 --max-line-length=127 --statistics --exclude=__init__.py || exit 1
36+
# if we want to enable documentation checks, uncomment the line below
37+
# flake8 ./GEMstack --ignore=E128,E402,E501,F401 --docstring-convention pep257 --max-line-length=120 --exclude=__init__.py || exit 1
38+
continue-on-error: false
39+
40+
Documentation:
41+
42+
runs-on: ubuntu-latest
43+
44+
steps:
45+
- uses: actions/checkout@v3
46+
- name: Set up Python 3.10
47+
uses: actions/setup-python@v3
48+
with:
49+
python-version: "3.10"
50+
- name: Install dependencies
51+
run: |
52+
python -m pip install --upgrade pip
53+
pip install sphinx sphinx-rtd-theme
54+
- name: Generate Documentation
55+
run: |
56+
# stop the build if there are Python syntax errors or undefined names
57+
sphinx-build -b html docs docs/build
58+
- name: Save Documentation as Artifact
59+
uses: actions/upload-artifact@v4
60+
with:
61+
name: documentation
62+
path: docs/build

.gitignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,15 @@ cython_debug/
165165
# and can be added to the global gitignore or merged into this file. For a more nuclear
166166
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
167167
#.idea/
168+
169+
# ZED run files
170+
**/*.run
171+
.vscode/
172+
setup/zed_sdk.run
173+
cuda/
174+
homework/yolov8n.pt
175+
homework/yolo11n.pt
176+
GEMstack/knowledge/detection/yolov8n.pt
177+
GEMstack/knowledge/detection/yolo11n.pt
178+
yolov8n.pt
179+
yolo11n.pt
850 Bytes
Binary file not shown.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
calibration_date: "2024-03-05" # Date of calibration YYYY-MM-DD
22
reference: rear_axle_center # rear axle center
33
rear_axle_height: 0.33 # height of rear axle center above flat ground
4-
gnss_location: [1.10,0.1.62] # meters, taken from https://github.com/hangcui1201/POLARIS_GEM_e2_Real/blob/main/vehicle_drivers/gem_gnss_control/scripts/gem_gnss_tracker_stanley_rtk.py. Note conflict with pure pursuit location?
4+
gnss_location: [1.10,0,1.62] # meters, taken from https://github.com/hangcui1201/POLARIS_GEM_e2_Real/blob/main/vehicle_drivers/gem_gnss_control/scripts/gem_gnss_tracker_stanley_rtk.py. Note conflict with pure pursuit location?
55
gnss_yaw: 0.0 # radians
66
top_lidar: !include "gem_e4_ouster.yaml"
77
front_camera: !include "gem_e4_oak.yaml"
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"fx": 684.8333129882812,
3+
"fy": 684.6096801757812,
4+
"cx": 573.37109375,
5+
"cy": 363.700927734375
6+
}
7+

GEMstack/mathutils/dubins.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def derivative(self,x,u):
4444
right = [-fwd[1],fwd[0]]
4545
phi = u[1]
4646
d = u[0]
47-
return np.array([fwd[0]*d,fwd[1]*d,phi])
47+
return np.array([fwd[0]*d,fwd[1]*d,phi*d])
4848

4949

5050
class DubinsCarIntegrator(ControlSpace):

GEMstack/mathutils/transforms.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ def vector_dist(v1, v2) -> float:
3636
"""Euclidean distance between two vectors"""
3737
return vo.distance(v1,v2)
3838

39+
def vector_dot(v1, v2) -> float:
40+
"""Dot product between two vectors"""
41+
return vo.dot(v1,v2)
42+
43+
def vector_cross(v1, v2) -> float:
44+
"""Cross product between two 2D vectors"""
45+
return vo.cross(v1,v2)
46+
3947
def vector2_angle(v1, v2 = None) -> float:
4048
"""find the ccw angle bewtween two 2d vectors"""
4149
if v2 is None:
@@ -123,3 +131,16 @@ def xy_to_lat_lon(x_east : float, y_north : float, lat_reference : float, lon_re
123131
# convert GNSS waypoints into local fixed frame reprented in x and y
124132
lat, lon = axy.xy2ll(x_east, y_north, lat_reference, lon_reference)
125133
return lat, lon
134+
135+
def quaternion_to_euler(x : float, y : float, z : float, w : float):
136+
t0 = +2.0 * (w * x + y * z)
137+
t1 = +1.0 - 2.0 * (x * x + y * y)
138+
roll = np.arctan2(t0, t1)
139+
t2 = +2.0 * (w * y - z * x)
140+
t2 = +1.0 if t2 > +1.0 else t2
141+
t2 = -1.0 if t2 < -1.0 else t2
142+
pitch = np.arcsin(t2)
143+
t3 = +2.0 * (w * z + x * y)
144+
t4 = +1.0 - 2.0 * (y * y + z * z)
145+
yaw = np.arctan2(t3, t4)
146+
return [roll, pitch, yaw]

GEMstack/onboard/interface/gem_hardware.py

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from .gem import *
22
from ...utils import settings
33
import math
4+
import time
45

56
# ROS Headers
67
import rospy
78
from std_msgs.msg import String, Bool, Float32, Float64
89
from sensor_msgs.msg import Image,PointCloud2
10+
import message_filters
911
try:
1012
from novatel_gps_msgs.msg import NovatelPosition, NovatelXYZ, Inspva
1113
except ImportError:
@@ -55,6 +57,7 @@ def __init__(self):
5557
self.front_depth_sub = None
5658
self.top_lidar_sub = None
5759
self.stereo_sub = None
60+
self.sync = None
5861
self.faults = []
5962

6063
# -------------------- PACMod setup --------------------
@@ -151,6 +154,7 @@ def subscribe_sensor(self, name, callback, type = None):
151154
if name == 'gnss':
152155
topic = self.ros_sensor_topics[name]
153156
if topic.endswith('inspva'):
157+
#GEM e2 uses Novatel GNSS
154158
if type is not None and (type is not Inspva and type is not GNSSReading):
155159
raise ValueError("GEMHardwareInterface GEM e2 only supports Inspva/GNSSReading for GNSS")
156160
if type is Inspva:
@@ -169,24 +173,25 @@ def callback_with_gnss_reading(inspva_msg: Inspva):
169173
callback(GNSSReading(pose,speed,inspva_msg.status))
170174
self.gnss_sub = rospy.Subscriber(topic, Inspva, callback_with_gnss_reading)
171175
else:
172-
#assume it's septentrio
176+
#assume it's septentrio on GEM e4
173177
if type is not None and (type is not INSNavGeod and type is not GNSSReading):
174178
raise ValueError("GEMHardwareInterface GEM e4 only supports INSNavGeod/GNSSReading for GNSS")
175179
if type is INSNavGeod:
176180
self.gnss_sub = rospy.Subscriber(topic, INSNavGeod, callback)
177181
else:
178182
def callback_with_gnss_reading(msg: INSNavGeod):
179183
pose = ObjectPose(ObjectFrameEnum.GLOBAL,
180-
x=msg.longitude,
181-
y=msg.latitude,
184+
t=self.time(),
185+
x=math.degrees(msg.longitude), #Septentrio GNSS uses radians rather than degrees
186+
y=math.degrees(msg.latitude),
182187
z=msg.height,
183188
yaw=math.radians(msg.heading), #heading from north in degrees (TODO: maybe?? check this)
184189
roll=math.radians(msg.roll),
185190
pitch=math.radians(msg.pitch),
186191
)
187192
speed = np.sqrt(msg.ve**2 + msg.vn**2)
188193
callback(GNSSReading(pose,speed,('error' if msg.error else 'ok')))
189-
self.gnss_sub = rospy.Subscriber(topic, Inspva, callback_with_gnss_reading)
194+
self.gnss_sub = rospy.Subscriber(topic, INSNavGeod, callback_with_gnss_reading)
190195
elif name == 'top_lidar':
191196
topic = self.ros_sensor_topics[name]
192197
if type is not None and (type is not PointCloud2 and type is not np.ndarray):
@@ -227,6 +232,40 @@ def callback_with_cv2(msg : Image):
227232
cv_image = conversions.ros_Image_to_cv2(msg, desired_encoding="passthrough")
228233
callback(cv_image)
229234
self.front_depth_sub = rospy.Subscriber(topic, Image, callback_with_cv2)
235+
elif name == 'sensor_fusion_Lidar_Camera_GNSS':
236+
def callback_with_cv2_numpy_gnss(rgb_image_msg: Image, lidar_pc2_msg: PointCloud2, gnss_msg: INSNavGeod):
237+
points = conversions.ros_PointCloud2_to_numpy(lidar_pc2_msg, want_rgb=False)
238+
cv_image = conversions.ros_Image_to_cv2(rgb_image_msg, desired_encoding="bgr8")
239+
pose = ObjectPose(ObjectFrameEnum.GLOBAL,
240+
t = 0,
241+
x=gnss_msg.longitude,
242+
y=gnss_msg.latitude,
243+
z=gnss_msg.height,
244+
yaw=math.radians(gnss_msg.heading), #heading from north in degrees (TODO: maybe?? check this)
245+
roll=math.radians(gnss_msg.roll),
246+
pitch=math.radians(gnss_msg.pitch),
247+
)
248+
speed = np.sqrt(gnss_msg.ve**2 + gnss_msg.vn**2)
249+
callback(cv_image,points,GNSSReading(pose,speed,('error' if gnss_msg.error else 'ok')))
250+
topic_camera = self.ros_sensor_topics['front_camera']
251+
topic_lidar = self.ros_sensor_topics['top_lidar']
252+
topic_gnss = self.ros_sensor_topics['gnss']
253+
self.front_camera_sub = message_filters.Subscriber(topic_camera, Image)
254+
self.top_lidar_sub = message_filters.Subscriber(topic_lidar, PointCloud2)
255+
self.gnss_sub = message_filters.Subscriber(topic_gnss, INSNavGeod)
256+
self.sync = message_filters.ApproximateTimeSynchronizer([self.front_camera_sub, self.top_lidar_sub,self.gnss_sub], queue_size=10, slop=0.1)
257+
self.sync.registerCallback(callback_with_cv2_numpy_gnss)
258+
elif name == 'sensor_fusion_Lidar_Camera':
259+
def callback_with_cv2_numpy(rgb_image_msg: Image, lidar_pc2_msg: PointCloud2):
260+
points = conversions.ros_PointCloud2_to_numpy(lidar_pc2_msg, want_rgb=False)
261+
cv_image = conversions.ros_Image_to_cv2(rgb_image_msg, desired_encoding="bgr8")
262+
callback(cv_image,points)
263+
topic_camera = self.ros_sensor_topics['front_camera']
264+
topic_lidar = self.ros_sensor_topics['top_lidar']
265+
self.front_camera_sub = message_filters.Subscriber(topic_camera, Image)
266+
self.top_lidar_sub = message_filters.Subscriber(topic_lidar, PointCloud2)
267+
self.sync = message_filters.ApproximateTimeSynchronizer([self.front_camera_sub, self.top_lidar_sub], queue_size=10, slop=0.1)
268+
self.sync.registerCallback(callback_with_cv2_numpy)
230269

231270

232271
# PACMod enable callback function
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
class IdTracker():
2+
"""Abstracts out id tracking
3+
"""
4+
def __init__(self):
5+
self.__id = 0
6+
7+
def get_new_id(self) -> int:
8+
"""Returns a unique agent id
9+
"""
10+
assigned_id = self.__id
11+
self.__id += 1 # id will intentionally overflow to get back to 0
12+
return assigned_id
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"fx": 684.8333129882812,
3+
"fy": 684.6096801757812,
4+
"cx": 573.37109375,
5+
"cy": 363.700927734375
6+
}
7+

0 commit comments

Comments
 (0)