00001
00002
00003 import argparse
00004 import collections
00005 import os
00006 import sys
00007
00008 from cStringIO import StringIO
00009 import cv2
00010 from cv_bridge import CvBridge
00011 from distutils.version import LooseVersion
00012 from matplotlib.figure import Figure
00013 import numpy as np
00014 import python_qt_binding
00015 from python_qt_binding import loadUi
00016 from python_qt_binding.QtCore import Qt
00017 from python_qt_binding.QtCore import QTimer
00018 from python_qt_binding.QtCore import Slot
00019 from python_qt_binding.QtGui import QIcon
00020 import rospkg
00021 import rospy
00022 from rqt_gui_py.plugin import Plugin
00023 from rqt_plot.rosplot import ROSData as _ROSData
00024 from rqt_plot.rosplot import RosPlotException
00025 from rqt_py_common.topic_completer import TopicCompleter
00026 from sensor_msgs.msg import Image
00027
00028 from jsk_recognition_msgs.msg import HistogramWithRange
00029
00030
00031 if LooseVersion(python_qt_binding.QT_BINDING_VERSION).version[0] >= 5:
00032 from python_qt_binding.QtWidgets import QSizePolicy
00033 from python_qt_binding.QtWidgets import QVBoxLayout
00034 from python_qt_binding.QtWidgets import QWidget
00035 try:
00036 from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg \
00037 as FigureCanvas
00038 except ImportError:
00039
00040 import thread
00041 sys.modules['_thread'] = thread
00042 from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg \
00043 as FigureCanvas
00044 try:
00045 from matplotlib.backends.backend_qt5agg import NavigationToolbar2QTAgg \
00046 as NavigationToolbar
00047 except ImportError:
00048 from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT \
00049 as NavigationToolbar
00050 else:
00051 from python_qt_binding.QtGui import QSizePolicy
00052 from python_qt_binding.QtGui import QVBoxLayout
00053 from python_qt_binding.QtGui import QWidget
00054 try:
00055 from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg \
00056 as FigureCanvas
00057 except ImportError:
00058
00059 import thread
00060 sys.modules['_thread'] = thread
00061 from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg \
00062 as FigureCanvas
00063 try:
00064 from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg \
00065 as NavigationToolbar
00066 except ImportError:
00067 from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT \
00068 as NavigationToolbar
00069
00070
00071 class ROSData(_ROSData):
00072 def _get_data(self, msg):
00073 val = msg
00074 try:
00075 if not self.field_evals:
00076 return val
00077 for f in self.field_evals:
00078 val = f(val)
00079 return val
00080 except IndexError:
00081 self.error = RosPlotException(
00082 "{0} index error for: {1}".format(
00083 self.name, str(val).replace('\n', ', ')))
00084 except TypeError:
00085 self.error = RosPlotException(
00086 "{0} value was not numeric: {1}".format(
00087 self.name, val))
00088
00089
00090 class HistogramPlot(Plugin):
00091 def __init__(self, context):
00092 super(HistogramPlot, self).__init__(context)
00093 self.setObjectName('HistogramPlot')
00094 self._args = self._parse_args(context.argv())
00095 self._widget = HistogramPlotWidget(self._args.topics)
00096 context.add_widget(self._widget)
00097
00098 def _parse_args(self, argv):
00099 parser = argparse.ArgumentParser(
00100 prog='rqt_histogram_plot', add_help=False)
00101 HistogramPlot.add_arguments(parser)
00102 args = parser.parse_args(argv)
00103 return args
00104
00105 @staticmethod
00106 def add_arguments(parser):
00107 group = parser.add_argument_group('Options for rqt_histogram plugin')
00108 group.add_argument(
00109 'topics', nargs='?', default=[], help='Topics to plot')
00110
00111
00112 class HistogramPlotWidget(QWidget):
00113 _redraw_interval = 40
00114
00115 def __init__(self, topics):
00116 super(HistogramPlotWidget, self).__init__()
00117 self.setObjectName('HistogramPlotWidget')
00118 rp = rospkg.RosPack()
00119 ui_file = os.path.join(rp.get_path('jsk_rqt_plugins'),
00120 'resource', 'plot_histogram.ui')
00121 loadUi(ui_file, self)
00122 self.cv_bridge = CvBridge()
00123 self.subscribe_topic_button.setIcon(QIcon.fromTheme('add'))
00124 self.pause_button.setIcon(QIcon.fromTheme('media-playback-pause'))
00125 self.clear_button.setIcon(QIcon.fromTheme('edit-clear'))
00126 self.data_plot = MatHistogramPlot(self)
00127 self.data_plot_layout.addWidget(self.data_plot)
00128 self._topic_completer = TopicCompleter(self.topic_edit)
00129 self._topic_completer.update_topics()
00130 self.topic_edit.setCompleter(self._topic_completer)
00131 self.data_plot.dropEvent = self.dropEvent
00132 self.data_plot.dragEnterEvent = self.dragEnterEvent
00133 self._start_time = rospy.get_time()
00134 self._rosdata = None
00135 if len(topics) != 0:
00136 self.subscribe_topic(topics)
00137 self._update_plot_timer = QTimer(self)
00138 self._update_plot_timer.timeout.connect(self.update_plot)
00139 self._update_plot_timer.start(self._redraw_interval)
00140
00141 @Slot('QDropEvent*')
00142 def dropEvent(self, event):
00143 if event.mimeData().hasText():
00144 topic_name = str(event.mimeData().text())
00145 else:
00146 droped_item = event.source().selectedItems()[0]
00147 topic_name = str(droped_item.data(0, Qt.UserRole))
00148 self.subscribe_topic(topic_name)
00149
00150 @Slot()
00151 def on_topic_edit_returnPressed(self):
00152 if self.subscribe_topic_button.isEnabled():
00153 self.subscribe_topic(str(self.topic_edit.text()))
00154
00155 @Slot()
00156 def on_subscribe_topic_button_clicked(self):
00157 self.subscribe_topic(str(self.topic_edit.text()))
00158
00159 def subscribe_topic(self, topic_name):
00160 self.topic_with_field_name = topic_name
00161 self.pub_image = rospy.Publisher(
00162 topic_name + "/histogram_image", Image, queue_size=1)
00163 if not self._rosdata:
00164 self._rosdata = ROSData(topic_name, self._start_time)
00165 else:
00166 if self._rosdata != topic_name:
00167 self._rosdata.close()
00168 self.data_plot.clear()
00169 self._rosdata = ROSData(topic_name, self._start_time)
00170 else:
00171 rospy.logwarn("%s is already subscribed", topic_name)
00172
00173 def enable_timer(self, enabled=True):
00174 if enabled:
00175 self._update_plot_timer.start(self._redraw_interval)
00176 else:
00177 self._update_plot_timer.stop()
00178
00179 @Slot()
00180 def on_clear_button_clicked(self):
00181 self.data_plot.clear()
00182
00183 @Slot(bool)
00184 def on_pause_button_clicked(self, checked):
00185 self.enable_timer(not checked)
00186
00187 def update_plot(self):
00188 if not self._rosdata:
00189 return
00190 data_x, data_y = self._rosdata.next()
00191
00192 if len(data_y) == 0:
00193 return
00194 axes = self.data_plot._canvas.axes
00195 axes.cla()
00196 if isinstance(data_y[-1], HistogramWithRange):
00197 xs = [y.count for y in data_y[-1].bins]
00198 pos = [y.min_value for y in data_y[-1].bins]
00199 widths = [y.max_value - y.min_value for y in data_y[-1].bins]
00200 axes.set_xlim(xmin=pos[0], xmax=pos[-1] + widths[-1])
00201 elif isinstance(data_y[-1], collections.Sequence):
00202 xs = data_y[-1]
00203 pos = np.arange(len(xs))
00204 widths = [1] * len(xs)
00205 axes.set_xlim(xmin=0, xmax=len(xs))
00206 else:
00207 rospy.logerr(
00208 "Topic/Field name '%s' has unsupported '%s' type."
00209 "List of float values and "
00210 "jsk_recognition_msgs/HistogramWithRange are supported."
00211 % (self.topic_with_field_name,
00212 self._rosdata.sub.data_class))
00213 return
00214
00215 for p, x, w in zip(pos, xs, widths):
00216 axes.bar(p, x, color='r', align='center', width=w)
00217 axes.legend([self.topic_with_field_name], prop={'size': '8'})
00218 self.data_plot._canvas.draw()
00219 buffer = StringIO()
00220 self.data_plot._canvas.figure.savefig(buffer, format="png")
00221 buffer.seek(0)
00222 img_array = np.asarray(bytearray(buffer.read()), dtype=np.uint8)
00223 if LooseVersion(cv2.__version__).version[0] < 3:
00224 iscolor = cv2.CV_LOAD_IMAGE_COLOR
00225 else:
00226 iscolor = cv2.IMREAD_COLOR
00227 img = cv2.imdecode(img_array, iscolor)
00228 self.pub_image.publish(self.cv_bridge.cv2_to_imgmsg(img, "bgr8"))
00229
00230
00231 class MatHistogramPlot(QWidget):
00232 class Canvas(FigureCanvas):
00233 def __init__(self, parent=None):
00234 super(MatHistogramPlot.Canvas, self).__init__(Figure())
00235 self.axes = self.figure.add_subplot(111)
00236 self.figure.tight_layout()
00237 self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
00238 self.updateGeometry()
00239
00240 def resizeEvent(self, event):
00241 super(MatHistogramPlot.Canvas, self).resizeEvent(event)
00242 self.figure.tight_layout()
00243
00244 def __init__(self, parent=None):
00245 super(MatHistogramPlot, self).__init__(parent)
00246 self._canvas = MatHistogramPlot.Canvas()
00247 self._toolbar = NavigationToolbar(self._canvas, self._canvas)
00248 vbox = QVBoxLayout()
00249 vbox.addWidget(self._toolbar)
00250 vbox.addWidget(self._canvas)
00251 self.setLayout(vbox)
00252
00253 def redraw(self):
00254 pass
00255
00256 def clear(self):
00257 self._canvas.axes.cla()
00258 self._canvas.draw()