plot_2d.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
3 import argparse
4 import os
5 import sys
6 
7 # https://stackoverflow.com/questions/11914472/stringio-in-python3
8 # https://stackoverflow.com/questions/50797043/string-argument-expected-got-bytes-in-buffer-write
9 try:
10  from cStringIO import StringIO
11 except ImportError:
12  from io import BytesIO as StringIO
13 import cv2
14 from cv_bridge import CvBridge
15 from distutils.version import LooseVersion
16 from matplotlib.figure import Figure
17 import numpy as np
18 import python_qt_binding
19 from python_qt_binding import loadUi
20 from python_qt_binding.QtCore import Qt
21 from python_qt_binding.QtCore import QTimer
22 from python_qt_binding.QtCore import Slot
23 from python_qt_binding.QtGui import QIcon
24 import rospkg
25 import rospy
26 from rqt_gui_py.plugin import Plugin
27 from rqt_plot.rosplot import ROSData as _ROSData
28 from rqt_plot.rosplot import RosPlotException
29 from rqt_py_common.topic_completer import TopicCompleter
30 from sensor_msgs.msg import Image
31 from sklearn import linear_model
32 
33 from jsk_recognition_msgs.msg import PlotData
34 from jsk_recognition_msgs.msg import PlotDataArray
35 
36 # qt5 in kinetic
37 if LooseVersion(python_qt_binding.QT_BINDING_VERSION).version[0] >= 5:
38  from python_qt_binding.QtWidgets import QSizePolicy
39  from python_qt_binding.QtWidgets import QVBoxLayout
40  from python_qt_binding.QtWidgets import QWidget
41  try:
42  from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg \
43  as FigureCanvas
44  except ImportError:
45  # work around bug in dateutil
46  import thread
47  sys.modules['_thread'] = thread
48  from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg \
49  as FigureCanvas
50  try:
51  from matplotlib.backends.backend_qt5agg import NavigationToolbar2QTAgg \
52  as NavigationToolbar
53  except ImportError:
54  from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT \
55  as NavigationToolbar
56 else:
57  from python_qt_binding.QtGui import QSizePolicy
58  from python_qt_binding.QtGui import QVBoxLayout
59  from python_qt_binding.QtGui import QWidget
60  try:
61  from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg \
62  as FigureCanvas
63  except ImportError:
64  # work around bug in dateutil
65  import thread
66  sys.modules['_thread'] = thread
67  from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg \
68  as FigureCanvas
69  try:
70  from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg \
71  as NavigationToolbar
72  except ImportError:
73  from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT \
74  as NavigationToolbar
75 
76 
77 
78 class ROSData(_ROSData):
79  def _get_data(self, msg):
80  val = msg
81  try:
82  if not self.field_evals:
83  return val
84  for f in self.field_evals:
85  val = f(val)
86  return val
87  except IndexError:
89  "{0} index error for: {1}".format(
90  self.name, str(val).replace('\n', ', ')))
91  except TypeError:
92  self.error = RosPlotException(
93  "{0} value was not numeric: {1}".format(
94  self.name, val))
95 
96 
97 def add_list(xss):
98 
99  "xss = [[0, 1, 2, ...], [0, 1, 2, ...], ...]"
100 
101  ret = []
102  for xs in xss:
103  ret.extend(xs)
104  return ret
105 
106 
107 class Plot2D(Plugin):
108  def __init__(self, context):
109  super(Plot2D, self).__init__(context)
110  self.setObjectName('Plot2D')
111  self._args = self._parse_args(context.argv())
112  self._widget = Plot2DWidget(self._args.topics)
113  self._widget.is_line = self._args.line
114  self._widget.fit_line = self._args.fit_line
115  self._widget.fit_line_ransac = self._args.fit_line_ransac
116  outlier = self._args.fit_line_ransac_outlier
117  self._widget.fit_line_ransac_outlier = outlier
118  self._widget.xtitle = self._args.xtitle
119  self._widget.ytitle = self._args.ytitle
120  self._widget.no_legend = self._args.no_legend
121  self._widget.sort_x = self._args.sort_x
122  context.add_widget(self._widget)
123 
124  def _parse_args(self, argv):
125  parser = argparse.ArgumentParser(
126  prog='rqt_histogram_plot', add_help=False)
127  Plot2D.add_arguments(parser)
128  args = parser.parse_args(argv)
129  return args
130 
131  @staticmethod
132  def add_arguments(parser):
133  group = parser.add_argument_group(
134  'Options for rqt_histogram plugin')
135  group.add_argument(
136  'topics', nargs='?', default=[], help='Topics to plot')
137  group.add_argument(
138  '--line', action="store_true",
139  help="Plot with lines instead of scatter")
140  group.add_argument(
141  '--fit-line', action="store_true",
142  help="Plot line with least-square fitting")
143  group.add_argument(
144  '--fit-line-ransac', action="store_true",
145  help="Plot line with RANSAC")
146  group.add_argument(
147  '--fit-line-ransac-outlier', type=float, default=0.1,
148  help="Plot line with RANSAC")
149  group.add_argument('--xtitle', help="Title in X axis")
150  group.add_argument('--ytitle', help="Title in Y axis")
151  group.add_argument('--no-legend', action="store_true")
152  group.add_argument('--sort-x', action="store_true")
153 
154 
155 class Plot2DWidget(QWidget):
156  _redraw_interval = 10
157 
158  def __init__(self, topics):
159  super(Plot2DWidget, self).__init__()
160  self.setObjectName('Plot2DWidget')
161  rp = rospkg.RosPack()
162  ui_file = os.path.join(
163  rp.get_path('jsk_rqt_plugins'), 'resource', 'plot_histogram.ui')
164  loadUi(ui_file, self)
165  self.cv_bridge = CvBridge()
166  self.subscribe_topic_button.setIcon(QIcon.fromTheme('add'))
167  self.pause_button.setIcon(QIcon.fromTheme('media-playback-pause'))
168  self.clear_button.setIcon(QIcon.fromTheme('edit-clear'))
169  self.data_plot = MatPlot2D(self)
170  self.data_plot_layout.addWidget(self.data_plot)
171  self._topic_completer = TopicCompleter(self.topic_edit)
172  self._topic_completer.update_topics()
173  self.topic_edit.setCompleter(self._topic_completer)
174  self.data_plot.dropEvent = self.dropEvent
175  self.data_plot.dragEnterEvent = self.dragEnterEvent
176  self._start_time = rospy.get_time()
177  self._rosdata = None
178  if len(topics) != 0:
179  self.subscribe_topic(topics)
180  self._update_plot_timer = QTimer(self)
181  self._update_plot_timer.timeout.connect(self.update_plot)
182  self._update_plot_timer.start(self._redraw_interval)
183 
184  @Slot('QDropEvent*')
185  def dropEvent(self, event):
186  if event.mimeData().hasText():
187  topic_name = str(event.mimeData().text())
188  else:
189  droped_item = event.source().selectedItems()[0]
190  topic_name = str(droped_item.data(0, Qt.UserRole))
191  self.subscribe_topic(topic_name)
192 
193  @Slot()
195  "callback function when form is entered"
196  if self.subscribe_topic_button.isEnabled():
197  self.subscribe_topic(str(self.topic_edit.text()))
198 
199  @Slot()
201  self.subscribe_topic(str(self.topic_edit.text()))
202 
203  def subscribe_topic(self, topic_name):
204  rospy.loginfo("subscribe topic")
205  self.topic_with_field_name = topic_name
206  self.pub_image = rospy.Publisher(
207  topic_name + "/plot_image", Image, queue_size=1)
208  if not self._rosdata:
209  self._rosdata = ROSData(topic_name, self._start_time)
210  else:
211  if self._rosdata != topic_name:
212  self._rosdata.close()
213  self.data_plot.clear()
214  self._rosdata = ROSData(topic_name, self._start_time)
215  else:
216  rospy.logwarn("%s is already subscribed", topic_name)
217 
218  def enable_timer(self, enabled=True):
219  if enabled:
220  self._update_plot_timer.start(self._redraw_interval)
221  else:
222  self._update_plot_timer.stop()
223 
224  @Slot()
226  self.data_plot.clear()
227 
228  @Slot(bool)
229  def on_pause_button_clicked(self, checked):
230  self.enable_timer(not checked)
231 
232  def plot_one(self, msg, axes):
233  concatenated_data = list(zip(msg.xs, msg.ys))
234  if self.sort_x:
235  concatenated_data.sort(key=lambda x: x[0])
236  xs = [d[0] for d in concatenated_data]
237  ys = [d[1] for d in concatenated_data]
238  if self.is_line or msg.type is PlotData.LINE:
239  axes.plot(xs, ys,
240  label=msg.label or self.topic_with_field_name)
241  else:
242  axes.scatter(xs, ys,
243  label=msg.label or self.topic_with_field_name)
244  if msg.fit_line or self.fit_line:
245  X = np.array(msg.xs)
246  Y = np.array(msg.ys)
247  A = np.array([X, np.ones(len(X))])
248  A = A.T
249  a, b = np.linalg.lstsq(A, Y, rcond=-1)[0]
250  axes.plot(X, (a*X+b), "g--", label="{0} x + {1}".format(a, b))
251  if msg.fit_line_ransac or self.fit_line_ransac:
252  model_ransac = linear_model.RANSACRegressor(
253  linear_model.LinearRegression(), min_samples=2,
254  residual_threshold=self.fit_line_ransac_outlier)
255  X = np.array(msg.xs).reshape((len(msg.xs), 1))
256  Y = np.array(msg.ys)
257  model_ransac.fit(X, Y)
258  line_X = X
259  line_y_ransac = model_ransac.predict(line_X)
260  if len(model_ransac.estimator_.coef_) == 1:
261  coef = model_ransac.estimator_.coef_[0]
262  else:
263  coef = model_ransac.estimator_.coef_[0][0]
264  if not isinstance(model_ransac.estimator_.intercept_, list):
265  intercept = model_ransac.estimator_.intercept_
266  else:
267  intercept = model_ransac.estimator_.intercept_[0]
268  axes.plot(
269  line_X, line_y_ransac, "r--",
270  label="{0} x + {1}".format(coef, intercept))
271 
272  def update_plot(self):
273  if not self._rosdata:
274  return
275  try:
276  data_x, data_y = self._rosdata.next()
277  except RosPlotException as e:
278  rospy.logerr("Exception in subscribing topic")
279  rospy.logerr(e.message)
280  return
281  if len(data_y) == 0:
282  return
283  axes = self.data_plot._canvas.axes
284  axes.cla()
285  # matplotlib
286  # concatenate x and y in order to sort
287  latest_msg = data_y[-1]
288  min_x = None
289  max_x = None
290  min_y = None
291  max_y = None
292  if isinstance(latest_msg, PlotData):
293  data = [latest_msg]
294  legend_size = 8
295  no_legend = False
296  elif isinstance(latest_msg, PlotDataArray):
297  data = latest_msg.data
298  legend_size = latest_msg.legend_font_size or 8
299  no_legend = latest_msg.no_legend
300  if latest_msg.min_x != latest_msg.max_x:
301  min_x = latest_msg.min_x
302  max_x = latest_msg.max_x
303  if latest_msg.min_y != latest_msg.max_y:
304  min_y = latest_msg.min_y
305  max_y = latest_msg.max_y
306  else:
307  rospy.logerr(
308  "Topic should be jsk_recognition_msgs/PlotData",
309  "or jsk_recognition_msgs/PlotDataArray")
310  for d in data:
311  self.plot_one(d, axes)
312  xs = add_list([d.xs for d in data])
313  ys = add_list([d.ys for d in data])
314  if min_x is None:
315  min_x = min(xs)
316  if max_x is None:
317  max_x = max(xs)
318  if min_y is None:
319  min_y = min(ys)
320  if max_y is None:
321  max_y = max(ys)
322  axes.set_xlim(min_x, max_x)
323  axes.set_ylim(min_y, max_y)
324  # line fitting
325  if not no_legend and not self.no_legend:
326  axes.legend(prop={'size': legend_size})
327  axes.grid()
328  if self.xtitle:
329  axes.set_xlabel(self.xtitle)
330  if self.ytitle:
331  axes.set_ylabel(self.ytitle)
332  self.data_plot._canvas.draw()
333  buffer = StringIO()
334  self.data_plot._canvas.figure.savefig(buffer, format="png")
335  buffer.seek(0)
336  img_array = np.asarray(bytearray(buffer.read()), dtype=np.uint8)
337  if LooseVersion(cv2.__version__).version[0] < 2:
338  iscolor = cv2.CV_LOAD_IMAGE_COLOR
339  else:
340  iscolor = cv2.IMREAD_COLOR
341  img = cv2.imdecode(img_array, iscolor)
342  self.pub_image.publish(self.cv_bridge.cv2_to_imgmsg(img, "bgr8"))
343 
344 
345 class MatPlot2D(QWidget):
346 
347  class Canvas(FigureCanvas):
348  def __init__(self, parent=None):
349  super(MatPlot2D.Canvas, self).__init__(Figure())
350  self.axes = self.figure.add_subplot(111)
351  self.figure.tight_layout()
352  self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
353  self.updateGeometry()
354 
355  def resizeEvent(self, event):
356  super(MatPlot2D.Canvas, self).resizeEvent(event)
357  self.figure.tight_layout()
358 
359  def __init__(self, parent=None):
360  super(MatPlot2D, self).__init__(parent)
362  self._toolbar = NavigationToolbar(self._canvas, self._canvas)
363  vbox = QVBoxLayout()
364  vbox.addWidget(self._toolbar)
365  vbox.addWidget(self._canvas)
366  self.setLayout(vbox)
367 
368  def redraw(self):
369  pass
370 
371  def clear(self):
372  self._canvas.axes.cla()
373  self._canvas.draw()
rqt_plot::rosplot::RosPlotException
jsk_rqt_plugins.plot_2d.Plot2DWidget.dropEvent
def dropEvent(self, event)
Definition: plot_2d.py:185
jsk_rqt_plugins.plot_2d.MatPlot2D.Canvas
Definition: plot_2d.py:347
jsk_rqt_plugins.plot_2d.Plot2DWidget._rosdata
_rosdata
Definition: plot_2d.py:177
jsk_rqt_plugins.plot_2d.MatPlot2D.Canvas.resizeEvent
def resizeEvent(self, event)
Definition: plot_2d.py:355
rqt_plot::rosplot::ROSData
rqt_gui_py::plugin::Plugin
jsk_rqt_plugins.plot_2d.Plot2DWidget.__init__
def __init__(self, topics)
Definition: plot_2d.py:158
jsk_rqt_plugins.plot_2d.MatPlot2D
Definition: plot_2d.py:345
jsk_rqt_plugins.plot_2d.Plot2DWidget
Definition: plot_2d.py:155
jsk_rqt_plugins.plot_2d.Plot2D._parse_args
def _parse_args(self, argv)
Definition: plot_2d.py:124
jsk_rqt_plugins.plot_2d.Plot2DWidget._update_plot_timer
_update_plot_timer
Definition: plot_2d.py:180
jsk_rqt_plugins.plot_2d.Plot2DWidget.plot_one
def plot_one(self, msg, axes)
Definition: plot_2d.py:232
jsk_rqt_plugins.plot_2d.Plot2DWidget.update_plot
def update_plot(self)
Definition: plot_2d.py:272
jsk_rqt_plugins.plot_2d.MatPlot2D._canvas
_canvas
Definition: plot_2d.py:361
jsk_rqt_plugins.plot_2d.Plot2DWidget.subscribe_topic
def subscribe_topic(self, topic_name)
Definition: plot_2d.py:203
jsk_rqt_plugins.plot_2d.Plot2DWidget.pub_image
pub_image
Definition: plot_2d.py:206
jsk_rqt_plugins.plot_2d.Plot2DWidget.enable_timer
def enable_timer(self, enabled=True)
Definition: plot_2d.py:218
jsk_rqt_plugins.plot_2d.Plot2DWidget.topic_with_field_name
topic_with_field_name
Definition: plot_2d.py:205
jsk_rqt_plugins.plot_2d.Plot2DWidget._topic_completer
_topic_completer
Definition: plot_2d.py:171
jsk_rqt_plugins.plot_2d.MatPlot2D.clear
def clear(self)
Definition: plot_2d.py:371
jsk_rqt_plugins.plot_2d.Plot2D._widget
_widget
Definition: plot_2d.py:112
jsk_rqt_plugins.plot_2d.Plot2DWidget.on_pause_button_clicked
def on_pause_button_clicked(self, checked)
Definition: plot_2d.py:229
jsk_rqt_plugins.plot_2d.MatPlot2D.Canvas.__init__
def __init__(self, parent=None)
Definition: plot_2d.py:348
jsk_rqt_plugins.plot_2d.Plot2D.add_arguments
def add_arguments(parser)
Definition: plot_2d.py:132
jsk_rqt_plugins.plot_2d.Plot2DWidget.data_plot
data_plot
Definition: plot_2d.py:169
jsk_rqt_plugins.plot_2d.Plot2DWidget._redraw_interval
int _redraw_interval
Definition: plot_2d.py:156
jsk_rqt_plugins.plot_2d.Plot2DWidget.on_clear_button_clicked
def on_clear_button_clicked(self)
Definition: plot_2d.py:225
jsk_rqt_plugins.plot_2d.MatPlot2D.__init__
def __init__(self, parent=None)
Definition: plot_2d.py:359
rqt_plot::rosplot
jsk_rqt_plugins.plot_2d.Plot2DWidget.cv_bridge
cv_bridge
Definition: plot_2d.py:165
rqt_gui_py::plugin
rqt_py_common::topic_completer
jsk_rqt_plugins.plot_2d.MatPlot2D.Canvas.axes
axes
Definition: plot_2d.py:350
jsk_rqt_plugins.plot_2d.Plot2DWidget._start_time
_start_time
Definition: plot_2d.py:176
jsk_rqt_plugins.plot_2d.MatPlot2D.redraw
def redraw(self)
Definition: plot_2d.py:368
jsk_rqt_plugins.plot_2d.Plot2DWidget.on_subscribe_topic_button_clicked
def on_subscribe_topic_button_clicked(self)
Definition: plot_2d.py:200
jsk_rqt_plugins.plot_2d.Plot2D._args
_args
Definition: plot_2d.py:111
jsk_rqt_plugins.plot_2d.ROSData.error
error
Definition: plot_2d.py:88
jsk_rqt_plugins.plot_2d.Plot2D.__init__
def __init__(self, context)
Definition: plot_2d.py:108
jsk_rqt_plugins.plot_2d.MatPlot2D._toolbar
_toolbar
Definition: plot_2d.py:362
rqt_plot::rosplot::ROSData::_get_data
def _get_data(self, msg)
rqt_py_common::topic_completer::TopicCompleter
jsk_rqt_plugins.plot_2d.Plot2DWidget.on_topic_edit_returnPressed
def on_topic_edit_returnPressed(self)
Definition: plot_2d.py:194
jsk_rqt_plugins.plot_2d.add_list
def add_list(xss)
Definition: plot_2d.py:97
jsk_rqt_plugins.plot_2d.Plot2D
Definition: plot_2d.py:107


jsk_rqt_plugins
Author(s):
autogenerated on Fri Dec 13 2024 03:49:48