Skip to content

Instantly share code, notes, and snippets.

@superjax
Last active November 18, 2024 01:29
Show Gist options
  • Select an option

  • Save superjax/33151f018407244cb61402e094099c1d to your computer and use it in GitHub Desktop.

Select an option

Save superjax/33151f018407244cb61402e094099c1d to your computer and use it in GitHub Desktop.
An occupancy grid mapping example
# This is an implementation of Occupancy Grid Mapping as Presented
# in Chapter 9 of "Probabilistic Robotics" By Sebastian Thrun et al.
# In particular, this is an implementation of Table 9.1 and 9.2
import scipy.io
import scipy.stats
import numpy as np
import matplotlib.pyplot as plt
from tqdm import tqdm
class Map():
def __init__(self, xsize, ysize, grid_size):
self.xsize = xsize+2 # Add extra cells for the borders
self.ysize = ysize+2
self.grid_size = grid_size # save this off for future use
self.log_prob_map = np.zeros((self.xsize, self.ysize)) # set all to zero
self.alpha = 1.0 # The assumed thickness of obstacles
self.beta = 5.0*np.pi/180.0 # The assumed width of the laser beam
self.z_max = 150.0 # The max reading from the laser
# Pre-allocate the x and y positions of all grid positions into a 3D tensor
# (pre-allocation = faster)
self.grid_position_m = np.array([np.tile(np.arange(0, self.xsize*self.grid_size, self.grid_size)[:,None], (1, self.ysize)),
np.tile(np.arange(0, self.ysize*self.grid_size, self.grid_size)[:,None].T, (self.xsize, 1))])
# Log-Probabilities to add or remove from the map
self.l_occ = np.log(0.65/0.35)
self.l_free = np.log(0.35/0.65)
def update_map(self, pose, z):
dx = self.grid_position_m.copy() # A tensor of coordinates of all cells
dx[0, :, :] -= pose[0] # A matrix of all the x coordinates of the cell
dx[1, :, :] -= pose[1] # A matrix of all the y coordinates of the cell
theta_to_grid = np.arctan2(dx[1, :, :], dx[0, :, :]) - pose[2] # matrix of all bearings from robot to cell
# Wrap to +pi / - pi
theta_to_grid[theta_to_grid > np.pi] -= 2. * np.pi
theta_to_grid[theta_to_grid < -np.pi] += 2. * np.pi
dist_to_grid = scipy.linalg.norm(dx, axis=0) # matrix of L2 distance to all cells from robot
# For each laser beam
for z_i in z:
r = z_i[0] # range measured
b = z_i[1] # bearing measured
# Calculate which cells are measured free or occupied, so we know which cells to update
# Doing it this way is like a billion times faster than looping through each cell (because vectorized numpy is the only way to numpy)
free_mask = (np.abs(theta_to_grid - b) <= self.beta/2.0) & (dist_to_grid < (r - self.alpha/2.0))
occ_mask = (np.abs(theta_to_grid - b) <= self.beta/2.0) & (np.abs(dist_to_grid - r) <= self.alpha/2.0)
# Adjust the cells appropriately
self.log_prob_map[occ_mask] += self.l_occ
self.log_prob_map[free_mask] += self.l_free
if __name__ == '__main__':
# load matlab generated data (located at https://gitlab.magiccvs.byu.edu/superjax/595R/blob/88a577579a75dc744bca7630716211334e04a528/lab6/state_meas_data.mat?)
data = scipy.io.loadmat('state_meas_data.mat')
state = data['X']
meas = data['z']
# Define the parameters for the map. (This is a 100x100m map with grid size 1x1m)
grid_size = 1.0
map = Map(int(100/grid_size), int(100/grid_size), grid_size)
plt.ion() # enable real-time plotting
plt.figure(1) # create a plot
for i in tqdm(range(len(state.T))):
map.update_map(state[:,i], meas[:,:,i].T) # update the map
# Real-Time Plotting
# (comment out these next lines to make it run super fast, matplotlib is painfully slow)
plt.clf()
pose = state[:,i]
circle = plt.Circle((pose[1], pose[0]), radius=3.0, fc='y')
plt.gca().add_patch(circle)
arrow = pose[0:2] + np.array([3.5, 0]).dot(np.array([[np.cos(pose[2]), np.sin(pose[2])], [-np.sin(pose[2]), np.cos(pose[2])]]))
plt.plot([pose[1], arrow[1]], [pose[0], arrow[0]])
plt.imshow(1.0 - 1./(1.+np.exp(map.log_prob_map)), 'Greys')
plt.pause(0.005)
# Final Plotting
plt.ioff()
plt.clf()
plt.imshow(1.0 - 1./(1.+np.exp(map.log_prob_map)), 'Greys') # This is probability
plt.imshow(map.log_prob_map, 'Greys') # log probabilities (looks really cool)
plt.show()
@zarifaziz

Copy link
Copy Markdown

Your matlab generated data link doesn't work anymore :/

@shrinivas96

Copy link
Copy Markdown

Hello. It seems the link you have provided no longer hosts the files you have mentioned. Could you please provide an update link or maybe where I can get such files to run this code.
Thanks very much :)

@shrinivas96

Copy link
Copy Markdown

Could you please provide an update link or maybe where I can get such files to run this code.

Hello. To other people finding the file, I found a link to a gitlab project by the original author.
I hope this was the same file. @superjax, please let me know if this is the same. Thanks

:)

@rajab-m

rajab-m commented Sep 13, 2020

Copy link
Copy Markdown

hello, thanks for the code, could you please explain about the z (measurements) in the .mat file, what do they represent exactly?
thanks

@theColorfulRainbow

Copy link
Copy Markdown

hello, thanks for the code, could you please explain about the z (measurements) in the .mat file, what do they represent exactly?
thanks

they show distance and angle of ray in the format:
[ [distance_1, angle_1],
[distance_2, angle_2],
[ . . . , . . . ],
. . .
]

@anggara70

Copy link
Copy Markdown

this amazing code. Can someone tell me how to get the (x,y) coordinates of an obstacle from the probability that this code gets? is it possible? thanks

@jsjax

jsjax commented Apr 11, 2022

Copy link
Copy Markdown

Hi @anggara70,

First pass would probably be to threshold on some probability to create a binary mask of the map.

i.e.

prob = 1.0 - 1./(1.+np.exp(map.log_prob_map))
obstacles = np.zeros_like(prob)
obstacles[prob > THRESHOLD] = 1.0

and then you could sample from the obstacles array. If it's a 1, then it's likely an obstacle, 0 would likely be free space.

@mte14eha

mte14eha commented Nov 11, 2022

Copy link
Copy Markdown

Hey,

I am getting confused with the conversion from odds to probabilities.

"# Log-Probabilities to add or remove from the map "
self.l_occ = np.log(0.65/0.35)
self.l_free = np.log(0.35/0.65)

These are afaik log-odds and not log probabilities.

If we call P(A) prob. Then P(not A) is 1 - P(A) ie P(not A) = 1-prob
So in the conversion to probabilities the formula should be:

P(not A) = 1.0 - np.exp(map.log_prob_map)*1./(1.+np.exp(map.log_prob_map))

I am getting superconfused and will probably edit this but if you happen to read this I would love some input! :)

@salvex

salvex commented Dec 14, 2022

Copy link
Copy Markdown

Hi

Thanks for your work.I'm currently studying in "Università Degli Studi di Palermo" and i'm attending Robotics class; we're using your code as "study case" for grid mapping. The code it's ok but there's a problem: the file "state_meas_data.mat" and the site provided here it's unreachable. Could you provide a new link?

Best regards

@superjax

Copy link
Copy Markdown
Author

I just updated the link -- should work now.

@fb1812

fb1812 commented Jun 16, 2023

Copy link
Copy Markdown

I have the same issue

@jsjax

jsjax commented Jun 16, 2023

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment