-
-
Notifications
You must be signed in to change notification settings - Fork 104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Binary Occupancy Grid Implementation #25
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After the binary occupancy grid map class was implemented, the mapper module class need to be implemented by referring to the following ndt global mapper class. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
""" | ||
binary_occupancy_grid.py | ||
|
||
Author: Shantanu Parab | ||
""" | ||
|
||
import numpy as np | ||
import sys | ||
from pathlib import Path | ||
from collections import defaultdict | ||
import matplotlib.pyplot as plt | ||
from matplotlib.colors import ListedColormap | ||
|
||
|
||
abs_dir_path = str(Path(__file__).absolute().parent) | ||
relative_path = "/../../../components/" | ||
|
||
|
||
sys.path.append(abs_dir_path + relative_path + "visualization") | ||
sys.path.append(abs_dir_path + relative_path + "obstacle") | ||
sys.path.append(abs_dir_path + relative_path + "state") | ||
|
||
|
||
from min_max import MinMax | ||
from obstacle_list import ObstacleList | ||
from obstacle import Obstacle | ||
from state import State | ||
import json | ||
|
||
|
||
# Define RGB colors for each grid value | ||
# Colors in the format [R, G, B], where values are in the range [0, 1] | ||
colors = [ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This constant value should be defined as a constant member value of the class and be named as COLORS if it is not changed. |
||
[1.0, 1.0, 1.0], # Free space (white) | ||
[0.4, 0.8, 1.0], # Explored nodes (light blue) | ||
[0.0, 1.0, 0.0], # Path (green) | ||
[0.5, 0.5, 0.5], # Clearance space (yellow-orange) | ||
[0.0, 0.0, 0.0], # Obstacles (red) | ||
] | ||
|
||
# Create a colormap | ||
custom_cmap = ListedColormap(colors) | ||
|
||
class BinaryOccupancyGrid: | ||
def __init__(self, x_lim , y_lim, resolution, clearance, map_path): | ||
|
||
self.x_min, self.x_max = x_lim.min_value(), x_lim.max_value() | ||
self.y_min, self.y_max = y_lim.min_value(), y_lim.max_value() | ||
self.resolution = resolution | ||
self.clearance = clearance | ||
|
||
self.map, self.x_range, self.y_range = self.create_grid() | ||
self.map_path = map_path | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function to save map should be defined as changeable for switching, saving or not saving the map data. |
||
|
||
|
||
def create_grid(self): | ||
"""Create a grid based on the specified or derived limits.""" | ||
|
||
x_range = np.arange(self.x_min, self.x_max, self.resolution) | ||
y_range = np.arange(self.y_min, self.y_max, self.resolution) | ||
|
||
map = np.zeros((len(y_range), len(x_range))) # Initialize map as free space | ||
|
||
return map, x_range, y_range | ||
|
||
def add_object(self, obtacle_list: ObstacleList): | ||
"""Mark obstacles and their clearance on the map, considering rotation (yaw).""" | ||
for obs in obtacle_list.list: | ||
# Get obstacle parameters | ||
x_c = obs.state.x_m | ||
y_c = obs.state.y_m | ||
yaw = obs.state.yaw_rad | ||
length = obs.length_m | ||
width = obs.width_m | ||
|
||
# Calculate the clearance dimensions | ||
clearance_length = length + self.clearance | ||
clearance_width = width + self.clearance | ||
|
||
# Define corners for the clearance area | ||
clearance_corners = np.array([ | ||
[-clearance_length, -clearance_width], | ||
[-clearance_length, clearance_width], | ||
[clearance_length, clearance_width], | ||
[clearance_length, -clearance_width] | ||
]) | ||
|
||
# Define corners for the actual obstacle | ||
obstacle_corners = np.array([ | ||
[-length, -width], | ||
[-length, width], | ||
[length, width], | ||
[length, -width] | ||
]) | ||
|
||
# Apply rotation to both obstacle and clearance corners | ||
rotation_matrix = np.array([ | ||
[np.cos(yaw), -np.sin(yaw)], | ||
[np.sin(yaw), np.cos(yaw)] | ||
]) | ||
rotated_clearance_corners = np.dot(clearance_corners, rotation_matrix.T) + np.array([x_c, y_c]) | ||
rotated_obstacle_corners = np.dot(obstacle_corners, rotation_matrix.T) + np.array([x_c, y_c]) | ||
|
||
# Mark the clearance area | ||
self._mark_area(rotated_clearance_corners, value=0.75) # 0.5 for clearance | ||
|
||
# Mark the actual obstacle area | ||
self._mark_area(rotated_obstacle_corners, value=1.0) # 1.0 for obstacles | ||
|
||
def _point_in_polygon(self, x, y, corners): | ||
""" | ||
Check if a point (x, y) is inside a polygon defined by corners. | ||
Args: | ||
x: X-coordinate of the point. | ||
y: Y-coordinate of the point. | ||
corners: Array of polygon corners in global coordinates. | ||
Returns: | ||
True if the point is inside the polygon, False otherwise. | ||
""" | ||
n = len(corners) | ||
inside = False | ||
px, py = x, y | ||
for i in range(n): | ||
x1, y1 = corners[i] | ||
x2, y2 = corners[(i + 1) % n] | ||
if ((y1 > py) != (y2 > py)) and \ | ||
(px < (x2 - x1) * (py - y1) / (y2 - y1 + 1e-6) + x1): | ||
inside = not inside | ||
return inside | ||
|
||
|
||
def _mark_area(self, corners, value): | ||
""" | ||
Mark a rectangular area on the map based on the given rotated corners. | ||
Args: | ||
corners: The rotated corners of the area in global coordinates. | ||
value: The value to mark in the map (e.g., 0.5 for clearance, 1.0 for obstacles). | ||
""" | ||
# Get the bounding box of the corners | ||
x_min = max(0, int((min(corners[:, 0]) - self.x_range[0]) / self.resolution)) | ||
x_max = min(self.map.shape[1], int((max(corners[:, 0]) - self.x_range[0]) / self.resolution)) | ||
y_min = max(0, int((min(corners[:, 1]) - self.y_range[0]) / self.resolution)) | ||
y_max = min(self.map.shape[0], int((max(corners[:, 1]) - self.y_range[0]) / self.resolution)) | ||
|
||
# Iterate through the map cells in the bounding box | ||
for x in range(x_min, x_max): | ||
for y in range(y_min, y_max): | ||
# Get the center of the current cell | ||
cell_x = self.x_range[0] + x * self.resolution + self.resolution / 2 | ||
cell_y = self.y_range[0] + y * self.resolution + self.resolution / 2 | ||
|
||
# Check if the cell center is inside the rotated polygon | ||
if self._point_in_polygon(cell_x, cell_y, corners): | ||
self.map[y, x] = max(self.map[y, x], value) # Mark the cell | ||
|
||
# Save the map to a file as an image/json | ||
def save_map(self): | ||
""" | ||
Save the map to a file. | ||
""" | ||
|
||
if self.map_path.endswith('.npy'): | ||
np.save(self.map_path, self.map) | ||
elif self.map_path.endswith('.png'): | ||
plt.imsave(self.map_path, self.map, cmap=custom_cmap, origin='lower') | ||
elif self.map_path.endswith('.json'): | ||
map_list = self.map.tolist() | ||
with open(self.map_path, 'w') as f: | ||
json.dump(map_list, f) | ||
else: | ||
raise ValueError("Unsupported file format. Use .npy, .png, or .json") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please check that this exception handling doesn’t stop running the unit test suites unexpectedly. |
||
|
||
|
||
|
||
if __name__ == "__main__": | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove this main process from this file and implement in the simulation I mentioned in this comment. https://github.com/ShisatoYano/AutonomousVehicleControlBeginnersGuide/pull/25/files#r1938217039 |
||
|
||
obst_list = ObstacleList() | ||
obst_list.add_obstacle(Obstacle(State(x_m=10.0, y_m=15.0), length_m=10, width_m=8)) | ||
obst_list.add_obstacle(Obstacle(State(x_m=40.0, y_m=0.0), length_m=2, width_m=10)) | ||
obst_list.add_obstacle(Obstacle(State(x_m=10.0, y_m=-10.0, yaw_rad=np.rad2deg(45)), length_m=5, width_m=5)) | ||
obst_list.add_obstacle(Obstacle(State(x_m=30.0, y_m=15.0, yaw_rad=np.rad2deg(10)), length_m=5, width_m=2)) | ||
|
||
bin_occ_grid = BinaryOccupancyGrid(MinMax(-5, 55), MinMax(-20, 25), 0.5, 1.5) | ||
bin_occ_grid.add_object(obst_list) | ||
|
||
bin_occ_grid.save_map("map.json") | ||
|
||
plt.figure(figsize=(10, 8)) | ||
plt.imshow(bin_occ_grid.map, extent=[bin_occ_grid.x_range[0], bin_occ_grid.x_range[-1], bin_occ_grid.y_range[0], bin_occ_grid.y_range[-1]], | ||
origin='lower', cmap=custom_cmap) | ||
|
||
plt.legend() | ||
plt.show() |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add a simulation of binary occupancy grid map construction by referring to the following ndt map construction simulation. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
""" | ||
Test of Binary Map Construction | ||
|
||
Author: Shantanu Parab | ||
""" | ||
|
||
from pathlib import Path | ||
import sys | ||
import pytest | ||
|
||
sys.path.append(str(Path(__file__).absolute().parent) + "/../src/components/mapping/grid") | ||
import binary_occupancy_grid | ||
|
||
|
||
def test_simulation(): | ||
binary_occupancy_grid.show_plot = False | ||
binary_occupancy_grid.main() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please refer to the following ndt_map.py.
https://github.com/ShisatoYano/AutonomousVehicleControlBeginnersGuide/blob/main/src/components/mapping/ndt/ndt_map.py
The binary occupancy grid class you added should be implemented by inheriting this grid map base class.
https://github.com/ShisatoYano/AutonomousVehicleControlBeginnersGuide/blob/main/src/components/mapping/grid/grid_map.py