00001
00002 from rqt_gui_py.plugin import Plugin
00003 from python_qt_binding import loadUi
00004 from python_qt_binding.QtCore import Qt, QTimer, qWarning, Slot
00005 from python_qt_binding.QtGui import QAction, QIcon, QMenu, QWidget
00006 from python_qt_binding.QtGui import QWidget, QVBoxLayout, QSizePolicy, QColor
00007 from rqt_py_common.topic_completer import TopicCompleter
00008 from matplotlib.colors import colorConverter
00009 from rqt_py_common.topic_helpers import is_slot_numeric
00010 from rqt_plot.rosplot import ROSData as _ROSData
00011 from rqt_plot.rosplot import RosPlotException
00012 from matplotlib.collections import (PolyCollection,
00013 PathCollection, LineCollection)
00014 import matplotlib
00015 import matplotlib.patches as mpatches
00016 import rospkg
00017 import rospy
00018 from cStringIO import StringIO
00019 import cv2
00020 from cv_bridge import CvBridge
00021 from sensor_msgs.msg import Image
00022 from jsk_recognition_msgs.msg import PlotData
00023 import numpy as np
00024 import os, sys
00025 import argparse
00026
00027 try:
00028 from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
00029 except ImportError:
00030
00031 import sys
00032 import thread
00033 sys.modules['_thread'] = thread
00034 from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
00035 from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
00036 from matplotlib.figure import Figure
00037
00038 import numpy as np
00039 import matplotlib.pyplot as plt
00040
00041 class ROSData(_ROSData):
00042 def _get_data(self, msg):
00043 val = msg
00044 try:
00045 if not self.field_evals:
00046 return val
00047 for f in self.field_evals:
00048 val = f(val)
00049 return val
00050 except IndexError:
00051 self.error = RosPlotException("[%s] index error for: %s" % (self.name, str(val).replace('\n', ', ')))
00052 except TypeError:
00053 self.error = RosPlotException("[%s] value was not numeric: %s" % (self.name, val))
00054
00055
00056
00057 class Plot2D(Plugin):
00058 def __init__(self, context):
00059 super(Plot2D, self).__init__(context)
00060 self.setObjectName('Plot2D')
00061 self._args = self._parse_args(context.argv())
00062 self._widget = Plot2DWidget(self._args.topics)
00063 self._widget.is_line = self._args.line
00064 self._widget.fit_line = self._args.fit_line
00065 self._widget.xtitle = self._args.xtitle
00066 self._widget.ytitle = self._args.ytitle
00067 self._widget.no_legend = self._args.no_legend
00068 self._widget.sort_x = self._args.sort_x
00069 context.add_widget(self._widget)
00070 def _parse_args(self, argv):
00071 parser = argparse.ArgumentParser(prog='rqt_histogram_plot', add_help=False)
00072 Plot2D.add_arguments(parser)
00073 args = parser.parse_args(argv)
00074 return args
00075 @staticmethod
00076 def add_arguments(parser):
00077 group = parser.add_argument_group('Options for rqt_histogram plugin')
00078 group.add_argument('topics', nargs='?', default=[], help='Topics to plot')
00079 group.add_argument('--line', action="store_true", help="Plot with lines instead of scatter")
00080 group.add_argument('--fit-line', action="store_true", help="Plot line with least-square fitting")
00081 group.add_argument('--xtitle', help="Title in X axis")
00082 group.add_argument('--ytitle', help="Title in Y axis")
00083 group.add_argument('--no-legend', action="store_true")
00084 group.add_argument('--sort-x', action="store_true")
00085 class Plot2DWidget(QWidget):
00086 _redraw_interval = 40
00087 def __init__(self, topics):
00088 super(Plot2DWidget, self).__init__()
00089 self.setObjectName('Plot2DWidget')
00090 rp = rospkg.RosPack()
00091 ui_file = os.path.join(rp.get_path('jsk_rqt_plugins'),
00092 'resource', 'plot_histogram.ui')
00093 loadUi(ui_file, self)
00094 self.cv_bridge = CvBridge()
00095 self.subscribe_topic_button.setIcon(QIcon.fromTheme('add'))
00096 self.pause_button.setIcon(QIcon.fromTheme('media-playback-pause'))
00097 self.clear_button.setIcon(QIcon.fromTheme('edit-clear'))
00098 self.data_plot = MatPlot2D(self)
00099 self.data_plot_layout.addWidget(self.data_plot)
00100 self._topic_completer = TopicCompleter(self.topic_edit)
00101 self._topic_completer.update_topics()
00102 self.topic_edit.setCompleter(self._topic_completer)
00103 self.data_plot.dropEvent = self.dropEvent
00104 self.data_plot.dragEnterEvent = self.dragEnterEvent
00105 self._start_time = rospy.get_time()
00106 self._rosdata = None
00107 if len(topics) != 0:
00108 self.subscribe_topic(topics)
00109 self._update_plot_timer = QTimer(self)
00110 self._update_plot_timer.timeout.connect(self.update_plot)
00111 self._update_plot_timer.start(self._redraw_interval)
00112 @Slot('QDropEvent*')
00113 def dropEvent(self, event):
00114 if event.mimeData().hasText():
00115 topic_name = str(event.mimeData().text())
00116 else:
00117 droped_item = event.source().selectedItems()[0]
00118 topic_name = str(droped_item.data(0, Qt.UserRole))
00119 self.subscribe_topic(topic_name)
00120 @Slot()
00121 def on_topic_edit_returnPressed(self):
00122 if self.subscribe_topic_button.isEnabled():
00123 self.subscribe_topic(str(self.topic_edit.text()))
00124 @Slot()
00125 def on_subscribe_topic_button_clicked(self):
00126 self.subscribe_topic(str(self.topic_edit.text()))
00127
00128 def subscribe_topic(self, topic_name):
00129 self.topic_with_field_name = topic_name
00130 self.pub_image = rospy.Publisher(topic_name + "/histogram_image", Image)
00131 if not self._rosdata:
00132 self._rosdata = ROSData(topic_name, self._start_time)
00133 else:
00134 if self._rosdata != topic_name:
00135 self._rosdata.close()
00136 self.data_plot.clear()
00137 self._rosdata = ROSData(topic_name, self._start_time)
00138 else:
00139 rospy.logwarn("%s is already subscribed", topic_name)
00140
00141 def enable_timer(self, enabled=True):
00142 if enabled:
00143 self._update_plot_timer.start(self._redraw_interval)
00144 else:
00145 self._update_plot_timer.stop()
00146 @Slot()
00147 def on_clear_button_clicked(self):
00148 self.data_plot.clear()
00149
00150 @Slot(bool)
00151 def on_pause_button_clicked(self, checked):
00152 self.enable_timer(not checked)
00153
00154 def update_plot(self):
00155 if not self._rosdata:
00156 return
00157 data_x, data_y = self._rosdata.next()
00158
00159 if len(data_y) == 0:
00160 return
00161 axes = self.data_plot._canvas.axes
00162 axes.cla()
00163
00164
00165 concatenated_data = zip(data_y[-1].xs, data_y[-1].ys)
00166 if self.sort_x:
00167 concatenated_data.sort(key=lambda x: x[0])
00168 xs = [d[0] for d in concatenated_data]
00169 ys = [d[1] for d in concatenated_data]
00170 if self.is_line:
00171 axes.plot(xs, ys)
00172 else:
00173 axes.scatter(xs, ys)
00174
00175 axes.set_xlim(min(xs), max(xs))
00176 axes.set_ylim(min(ys), max(ys))
00177
00178 if self.fit_line:
00179 X = np.array(data_y[-1].xs)
00180 Y = np.array(data_y[-1].ys)
00181 A = np.array([X,np.ones(len(X))])
00182 A = A.T
00183 a,b = np.linalg.lstsq(A,Y)[0]
00184 axes.plot(X,(a*X+b),"g--")
00185
00186 axes.grid()
00187 if not self.no_legend:
00188 axes.legend([self.topic_with_field_name], prop={'size': '8'})
00189 if self.xtitle:
00190 axes.set_xlabel(self.xtitle)
00191 if self.ytitle:
00192 axes.set_ylabel(self.ytitle)
00193 self.data_plot._canvas.draw()
00194 buffer = StringIO()
00195 self.data_plot._canvas.figure.savefig(buffer, format="png")
00196 buffer.seek(0)
00197 img_array = np.asarray(bytearray(buffer.read()), dtype=np.uint8)
00198 img = cv2.imdecode(img_array, cv2.CV_LOAD_IMAGE_COLOR)
00199 self.pub_image.publish(self.cv_bridge.cv2_to_imgmsg(img, "bgr8"))
00200
00201 class MatPlot2D(QWidget):
00202 class Canvas(FigureCanvas):
00203 def __init__(self, parent=None):
00204 super(MatPlot2D.Canvas, self).__init__(Figure())
00205 self.axes = self.figure.add_subplot(111)
00206 self.figure.tight_layout()
00207 self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
00208 self.updateGeometry()
00209 def resizeEvent(self, event):
00210 super(MatPlot2D.Canvas, self).resizeEvent(event)
00211 self.figure.tight_layout()
00212 def __init__(self, parent=None):
00213 super(MatPlot2D, self).__init__(parent)
00214 self._canvas = MatPlot2D.Canvas()
00215 self._toolbar = NavigationToolbar(self._canvas, self._canvas)
00216 vbox = QVBoxLayout()
00217 vbox.addWidget(self._toolbar)
00218 vbox.addWidget(self._canvas)
00219 self.setLayout(vbox)
00220 def redraw(self):
00221 pass
00222 def clear(self):
00223 self._canvas.axes.cla()
00224 self._canvas.draw()