plot_2d.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
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     # work around bug in dateutil
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         # matplotlib
00164         # concatenate x and y in order to sort
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         # set limit
00175         axes.set_xlim(min(xs), max(xs))
00176         axes.set_ylim(min(ys), max(ys))
00177         # line fitting
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()


jsk_rqt_plugins
Author(s):
autogenerated on Sun Sep 13 2015 22:29:48