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 HistogramWithRange, HistogramWithRangeBin
00023
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 HistogramPlot(Plugin):
00058 def __init__(self, context):
00059 super(HistogramPlot, self).__init__(context)
00060 self.setObjectName('HistogramPlot')
00061 self._args = self._parse_args(context.argv())
00062 self._widget = HistogramPlotWidget(self._args.topics)
00063 context.add_widget(self._widget)
00064 def _parse_args(self, argv):
00065 parser = argparse.ArgumentParser(prog='rqt_histogram_plot', add_help=False)
00066 HistogramPlot.add_arguments(parser)
00067 args = parser.parse_args(argv)
00068 return args
00069 @staticmethod
00070 def add_arguments(parser):
00071 group = parser.add_argument_group('Options for rqt_histogram plugin')
00072 group.add_argument('topics', nargs='?', default=[], help='Topics to plot')
00073
00074 class HistogramPlotWidget(QWidget):
00075 _redraw_interval = 40
00076 def __init__(self, topics):
00077 super(HistogramPlotWidget, self).__init__()
00078 self.setObjectName('HistogramPlotWidget')
00079 rp = rospkg.RosPack()
00080 ui_file = os.path.join(rp.get_path('jsk_rqt_plugins'),
00081 'resource', 'plot_histogram.ui')
00082 loadUi(ui_file, self)
00083 self.cv_bridge = CvBridge()
00084 self.subscribe_topic_button.setIcon(QIcon.fromTheme('add'))
00085 self.pause_button.setIcon(QIcon.fromTheme('media-playback-pause'))
00086 self.clear_button.setIcon(QIcon.fromTheme('edit-clear'))
00087 self.data_plot = MatHistogramPlot(self)
00088 self.data_plot_layout.addWidget(self.data_plot)
00089 self._topic_completer = TopicCompleter(self.topic_edit)
00090 self._topic_completer.update_topics()
00091 self.topic_edit.setCompleter(self._topic_completer)
00092 self.data_plot.dropEvent = self.dropEvent
00093 self.data_plot.dragEnterEvent = self.dragEnterEvent
00094 self._start_time = rospy.get_time()
00095 self._rosdata = None
00096 if len(topics) != 0:
00097 self.subscribe_topic(topics)
00098 self._update_plot_timer = QTimer(self)
00099 self._update_plot_timer.timeout.connect(self.update_plot)
00100 self._update_plot_timer.start(self._redraw_interval)
00101 @Slot('QDropEvent*')
00102 def dropEvent(self, event):
00103 if event.mimeData().hasText():
00104 topic_name = str(event.mimeData().text())
00105 else:
00106 droped_item = event.source().selectedItems()[0]
00107 topic_name = str(droped_item.data(0, Qt.UserRole))
00108 self.subscribe_topic(topic_name)
00109 @Slot()
00110 def on_topic_edit_returnPressed(self):
00111 if self.subscribe_topic_button.isEnabled():
00112 self.subscribe_topic(str(self.topic_edit.text()))
00113 @Slot()
00114 def on_subscribe_topic_button_clicked(self):
00115 self.subscribe_topic(str(self.topic_edit.text()))
00116
00117 def subscribe_topic(self, topic_name):
00118 self.topic_with_field_name = topic_name
00119 self.pub_image = rospy.Publisher(topic_name + "/histogram_image", Image)
00120 if not self._rosdata:
00121 self._rosdata = ROSData(topic_name, self._start_time)
00122 else:
00123 if self._rosdata != topic_name:
00124 self._rosdata.close()
00125 self.data_plot.clear()
00126 self._rosdata = ROSData(topic_name, self._start_time)
00127 else:
00128 rospy.logwarn("%s is already subscribed", topic_name)
00129
00130 def enable_timer(self, enabled=True):
00131 if enabled:
00132 self._update_plot_timer.start(self._redraw_interval)
00133 else:
00134 self._update_plot_timer.stop()
00135 @Slot()
00136 def on_clear_button_clicked(self):
00137 self.data_plot.clear()
00138
00139 @Slot(bool)
00140 def on_pause_button_clicked(self, checked):
00141 self.enable_timer(not checked)
00142
00143 def update_plot(self):
00144 if not self._rosdata:
00145 return
00146 data_x, data_y = self._rosdata.next()
00147
00148 if len(data_y) == 0:
00149 return
00150 axes = self.data_plot._canvas.axes
00151 axes.cla()
00152 if self._rosdata.sub.data_class is HistogramWithRange:
00153 xs = [y.count for y in data_y[-1].bins]
00154 pos = [y.min_value for y in data_y[-1].bins]
00155 widths = [y.max_value - y.min_value for y in data_y[-1].bins]
00156 axes.set_xlim(xmin=pos[0], xmax=pos[-1] + widths[-1])
00157 else:
00158 xs = data_y[-1]
00159 pos = np.arange(len(xs))
00160 widths = [1] * len(xs)
00161 axes.set_xlim(xmin=0, xmax=len(xs))
00162
00163 for p, x, w in zip(pos, xs, widths):
00164 axes.bar(p, x, color='r', align='center', width=w)
00165 axes.legend([self.topic_with_field_name], prop={'size': '8'})
00166 self.data_plot._canvas.draw()
00167 buffer = StringIO()
00168 self.data_plot._canvas.figure.savefig(buffer, format="png")
00169 buffer.seek(0)
00170 img_array = np.asarray(bytearray(buffer.read()), dtype=np.uint8)
00171 img = cv2.imdecode(img_array, cv2.CV_LOAD_IMAGE_COLOR)
00172 self.pub_image.publish(self.cv_bridge.cv2_to_imgmsg(img, "bgr8"))
00173 class MatHistogramPlot(QWidget):
00174 class Canvas(FigureCanvas):
00175 def __init__(self, parent=None):
00176 super(MatHistogramPlot.Canvas, self).__init__(Figure())
00177 self.axes = self.figure.add_subplot(111)
00178 self.figure.tight_layout()
00179 self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
00180 self.updateGeometry()
00181 def resizeEvent(self, event):
00182 super(MatHistogramPlot.Canvas, self).resizeEvent(event)
00183 self.figure.tight_layout()
00184 def __init__(self, parent=None):
00185 super(MatHistogramPlot, self).__init__(parent)
00186 self._canvas = MatHistogramPlot.Canvas()
00187 self._toolbar = NavigationToolbar(self._canvas, self._canvas)
00188 vbox = QVBoxLayout()
00189 vbox.addWidget(self._toolbar)
00190 vbox.addWidget(self._canvas)
00191 self.setLayout(vbox)
00192 def redraw(self):
00193 pass
00194 def clear(self):
00195 self._canvas.axes.cla()
00196 self._canvas.draw()