Skip to content

Commit 6b046fa

Browse files
committed
add slam files
1 parent bf25dfa commit 6b046fa

File tree

4 files changed

+681
-0
lines changed

4 files changed

+681
-0
lines changed

__init__.py

+212
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
'''
2+
3+
This file is a modification of the file below to enable map save
4+
https://github.com/simondlevy/PyRoboViz/blob/master/roboviz/__init__.py
5+
6+
roboviz.py - Python classes for displaying maps and robots
7+
8+
Requires: numpy, matplotlib
9+
10+
Copyright (C) 2018 Simon D. Levy
11+
12+
This file is part of PyRoboViz.
13+
14+
PyRoboViz is free software: you can redistribute it and/or modify
15+
it under the terms of the GNU Lesser General Public License as
16+
published by the Free Software Foundation, either version 3 of the
17+
License, or (at your option) any later version.
18+
19+
PyRoboViz is distributed in the hope that it will be useful,
20+
but WITHOUT ANY WARRANTY without even the implied warranty of
21+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22+
GNU General Public License for more details.
23+
24+
'''
25+
# Essential imports
26+
import matplotlib.pyplot as plt
27+
import matplotlib.cm as colormap
28+
import matplotlib.lines as mlines
29+
from mpl_toolkits.mplot3d import Axes3D
30+
import numpy as np
31+
import datetime
32+
33+
# This helps with Raspberry Pi
34+
import matplotlib
35+
matplotlib.use('TkAgg')
36+
37+
class Visualizer(object):
38+
39+
# Robot display params
40+
ROBOT_HEIGHT_M = 0.5
41+
ROBOT_WIDTH_M = 0.3
42+
43+
def __init__(self, map_size_pixels, map_size_meters, title, show_trajectory=False, zero_angle=0):
44+
45+
# Put origin in center
46+
self._init(map_size_pixels, map_size_meters, title, -map_size_pixels / 2, show_trajectory, zero_angle)
47+
48+
def display(self, x_m, y_m, theta_deg):
49+
50+
self._setPose(x_m, y_m, theta_deg)
51+
52+
return self._refresh()
53+
54+
def _init(self, map_size_pixels, map_size_meters, title, shift, show_trajectory=False, zero_angle=0):
55+
56+
# Store constants for update
57+
map_size_meters = map_size_meters
58+
self.map_size_pixels = map_size_pixels
59+
self.map_scale_meters_per_pixel = map_size_meters / float(map_size_pixels)
60+
61+
# Create a byte array to display the map with a color overlay
62+
self.bgrbytes = bytearray(map_size_pixels * map_size_pixels * 3)
63+
64+
# Make a nice big (10"x10") figure
65+
fig = plt.figure(figsize=(10,10), facecolor="white")
66+
fig.set_facecolor("white")
67+
# Added this line to make sure the map background is white
68+
plt.rcParams['figure.facecolor'] = 'white'
69+
70+
# Store Python ID of figure to detect window close
71+
self.figid = id(fig)
72+
73+
fig.canvas.set_window_title('SLAM')
74+
plt.title(title)
75+
76+
# Use an "artist" to speed up map drawing
77+
self.img_artist = None
78+
79+
# No vehicle to show yet
80+
self.vehicle = None
81+
82+
# Create axes
83+
self.ax = fig.gca()
84+
self.ax.set_xlabel('X (m)')
85+
self.ax.set_ylabel('Y (m)')
86+
# self.ax.grid(False)
87+
88+
# Hence we must relabel the axis ticks to show millimeters
89+
ticks = np.arange(shift,self.map_size_pixels+shift+100,100)
90+
labels = [str(self.map_scale_meters_per_pixel * tick) for tick in ticks]
91+
self.ax.set_xticklabels(labels)
92+
self.ax.set_yticklabels(labels)
93+
94+
self.ax.set_facecolor('w')
95+
96+
# Store previous position for trajectory
97+
self.prevpos = None
98+
self.showtraj = show_trajectory
99+
100+
# We base the axis on pixels, to support displaying the map
101+
self.ax.set_xlim([shift, self.map_size_pixels+shift])
102+
self.ax.set_ylim([shift, self.map_size_pixels+shift])
103+
104+
# Set up default shift for centering at origin
105+
shift = -self.map_size_pixels / 2
106+
# print("shift = " + str(shift))
107+
108+
self.zero_angle = zero_angle
109+
self.start_angle = None
110+
self.rotate_angle = 0
111+
112+
def _setPose(self, x_m, y_m, theta_deg):
113+
'''
114+
Sets vehicle pose:
115+
X: left/right (m)
116+
Y: forward/back (m)
117+
theta: rotation (degrees)
118+
'''
119+
120+
# If zero-angle was indicated, grab first angle to compute rotation
121+
if self.start_angle is None and self.zero_angle != 0:
122+
self.start_angle = theta_deg
123+
self.rotate_angle = self.zero_angle - self.start_angle
124+
125+
# Rotate by computed angle, or zero if no zero-angle indicated
126+
d = self.rotate_angle
127+
a = np.radians(d)
128+
c = np.cos(a)
129+
s = np.sin(a)
130+
x_m,y_m = x_m*c-y_m*s, y_m*c+x_m*s
131+
132+
# Erase previous vehicle image after first iteration
133+
if not self.vehicle is None:
134+
self.vehicle.remove()
135+
136+
# Use a very short arrow shaft to orient the head of the arrow
137+
theta_rad = np.radians(theta_deg+d)
138+
c = np.cos(theta_rad)
139+
s = np.sin(theta_rad)
140+
l = 0.1
141+
dx = l * c
142+
dy = l * s
143+
144+
s = self.map_scale_meters_per_pixel
145+
146+
self.vehicle=self.ax.arrow(x_m/s, y_m/s,
147+
dx, dy, head_width=Visualizer.ROBOT_WIDTH_M/s,
148+
head_length=Visualizer.ROBOT_HEIGHT_M/s, fc='r', ec='r')
149+
150+
# Show trajectory if indicated
151+
currpos = self._m2pix(x_m,y_m)
152+
if self.showtraj and not self.prevpos is None:
153+
if (self.prevpos[0] != 0 and self.prevpos[1] != 0):
154+
self.ax.add_line(mlines.Line2D((self.prevpos[0],currpos[0]), (self.prevpos[1],currpos[1])))
155+
self.prevpos = currpos
156+
157+
def _refresh(self):
158+
159+
# If we have a new figure, something went wrong (closing figure failed)
160+
if self.figid != id(plt.gcf()):
161+
return False
162+
163+
# Added this line to make sure the map background is white
164+
plt.rcParams['figure.facecolor'] = 'white'
165+
plt.rcParams['axes.facecolor'] = 'white'
166+
plt.rcParams['savefig.facecolor'] = 'white'
167+
168+
# Redraw current objects without blocking
169+
plt.draw()
170+
now = datetime.datetime.now()
171+
# Create a directory named 'gif' inside the base directory
172+
plt.savefig('gif/slamMap' + '- ' + str(now.hour).zfill(2) + '- ' + str(now.minute).zfill(2) + '- ' + str(now.second).zfill(2) + '.png')
173+
174+
# Refresh display, setting flag on window close or keyboard interrupt
175+
try:
176+
plt.pause(.01) # Arbitrary pause to force redraw
177+
return True
178+
except:
179+
return False
180+
181+
return True
182+
183+
def _m2pix(self, x_m, y_m):
184+
185+
s = self.map_scale_meters_per_pixel
186+
187+
return x_m/s, y_m/s
188+
189+
class MapVisualizer(Visualizer):
190+
191+
def __init__(self, map_size_pixels, map_size_meters, title='MapVisualizer', show_trajectory=False):
192+
193+
# Put origin in lower left; disallow zero-angle setting
194+
Visualizer._init(self, map_size_pixels, map_size_meters, title, 0, show_trajectory, 0)
195+
196+
def display(self, x_m, y_m, theta_deg, mapbytes):
197+
198+
self._setPose(x_m, y_m, theta_deg)
199+
200+
mapimg = np.reshape(np.frombuffer(mapbytes, dtype=np.uint8), (self.map_size_pixels, self.map_size_pixels))
201+
202+
# Pause to allow display to refresh
203+
plt.pause(.001)
204+
205+
if self.img_artist is None:
206+
self.img_artist = self.ax.imshow(mapimg, cmap=colormap.gray)
207+
208+
else:
209+
self.img_artist.set_data(mapimg)
210+
211+
return self._refresh()
212+

mqtt-slamviz.py

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#!/usr/bin/env python
2+
import time
3+
from sys import exit
4+
from roboviz import MapVisualizer
5+
import numpy as np
6+
7+
8+
9+
try:
10+
import paho.mqtt.client as mqtt
11+
except ImportError:
12+
exit("This example requires the paho-mqtt module\nInstall with: sudo pip install paho-mqtt")
13+
14+
15+
MQTT_SERVER = "test.mosquitto.org"
16+
MQTT_PORT = 1883
17+
MQTT_TOPIC = "safetycam/topic/slamviz"
18+
19+
# Set these to use authorisation
20+
MQTT_USER = None
21+
MQTT_PASS = None
22+
23+
24+
MAP_SIZE_PIXELS = 200
25+
MAP_SIZE_METERS = 10
26+
# Set up a SLAM display
27+
viz = MapVisualizer(MAP_SIZE_PIXELS, MAP_SIZE_METERS, 'SLAM', show_trajectory=True)
28+
29+
print("""
30+
Public MQTT messages from {server} on port {port} to visualize SLAM!
31+
32+
It will monitor the {topic} topic by default, and decodes the bytearray
33+
""".format(
34+
server=MQTT_SERVER,
35+
port=MQTT_PORT,
36+
topic=MQTT_TOPIC
37+
))
38+
39+
def on_connect(client, userdata, flags, rc):
40+
print("Connected with result code "+str(rc))
41+
42+
client.subscribe(MQTT_TOPIC)
43+
44+
def on_message(client, userdata, msg):
45+
46+
robotPos_bytes = msg.payload[:24]
47+
map_bytes = msg.payload[24:]
48+
49+
robotPos = np.frombuffer(robotPos_bytes, dtype='float64')
50+
robotPos = np.array(robotPos)
51+
52+
x, y, theta = robotPos
53+
viz.display(x / 1000., y / 1000., theta, map_bytes)
54+
55+
return
56+
57+
58+
59+
client = mqtt.Client()
60+
client.on_connect = on_connect
61+
client.on_message = on_message
62+
63+
if MQTT_USER is not None and MQTT_PASS is not None:
64+
print("Using username: {un} and password: {pw}".format(un=MQTT_USER, pw="*" * len(MQTT_PASS)))
65+
client.username_pw_set(username=MQTT_USER, password=MQTT_PASS)
66+
67+
client.connect(MQTT_SERVER, MQTT_PORT, 60)
68+
69+
client.loop_forever()

0 commit comments

Comments
 (0)