00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033 from __future__ import print_function
00034 import rospy
00035
00036
00037 import sys
00038 from python_qt_binding import QT_BINDING_MODULES
00039 if (
00040 not QT_BINDING_MODULES['QtCore'].__name__.startswith('PyQt5') and
00041 'PyQt5' in sys.modules
00042 ):
00043 sys.modules['PyQt5'] = None
00044 from PIL import Image
00045 from PIL.ImageQt import ImageQt
00046
00047 from rqt_bag import TimelineCache, TimelineRenderer
00048
00049 from rqt_bag_plugins import image_helper
00050
00051 from python_qt_binding.QtCore import Qt
00052 from python_qt_binding.QtGui import QBrush, QPen, QPixmap
00053
00054
00055 class ImageTimelineRenderer(TimelineRenderer):
00056 """
00057 Draws thumbnails of sensor_msgs/Image or sensor_msgs/CompressedImage in the timeline.
00058 """
00059 def __init__(self, timeline, thumbnail_height=160):
00060 super(ImageTimelineRenderer, self).__init__(timeline, msg_combine_px=40.0)
00061
00062 self.thumbnail_height = thumbnail_height
00063
00064 self.thumbnail_combine_px = 20.0
00065 self.min_thumbnail_width = 8
00066 self.quality = Image.NEAREST
00067
00068 self.thumbnail_cache = TimelineCache(self._load_thumbnail, lambda topic, msg_stamp, thumbnail: self.timeline.scene().update())
00069
00070
00071 def get_segment_height(self, topic):
00072 return self.thumbnail_height
00073
00074 def draw_timeline_segment(self, painter, topic, stamp_start, stamp_end, x, y, width, height):
00075 """
00076 draws a stream of images for the topic
00077 :param painter: painter object, ''QPainter''
00078 :param topic: topic to draw, ''str''
00079 :param stamp_start: stamp to start drawing, ''rospy.Time''
00080 :param stamp_end: stamp to end drawing, ''rospy.Time''
00081 :param x: x to draw images at, ''int''
00082 :param y: y to draw images at, ''int''
00083 :param width: width in pixels of the timeline area, ''int''
00084 :param height: height in pixels of the timeline area, ''int''
00085 """
00086 if x < self.timeline._history_left:
00087 width += x - self.timeline._history_left
00088 x = self.timeline._history_left
00089 max_interval_thumbnail = self.timeline.map_dx_to_dstamp(self.thumbnail_combine_px)
00090 max_interval_thumbnail = max(0.1, max_interval_thumbnail)
00091 thumbnail_gap = 6
00092 thumbnail_x, thumbnail_y, thumbnail_height = x + 1, y + 1, height - 2 - thumbnail_gap
00093
00094
00095 painter.setBrush(QBrush(Qt.white))
00096 painter.drawRect(x, y, width, height - thumbnail_gap)
00097 thumbnail_width = None
00098
00099 while True:
00100 available_width = (x + width) - thumbnail_x
00101
00102
00103 if width > 1 and available_width < self.min_thumbnail_width:
00104 break
00105
00106
00107 if not thumbnail_width or thumbnail_x + thumbnail_width >= self.timeline._history_left:
00108 stamp = self.timeline.map_x_to_stamp(thumbnail_x, clamp_to_visible=False)
00109 thumbnail_bitmap = self.thumbnail_cache.get_item(topic, stamp, max_interval_thumbnail)
00110
00111
00112 if not thumbnail_bitmap:
00113 thumbnail_details = (thumbnail_height,)
00114 self.thumbnail_cache.enqueue((topic, stamp, max_interval_thumbnail, thumbnail_details))
00115 if not thumbnail_width:
00116 break
00117 else:
00118 thumbnail_width, _ = thumbnail_bitmap.size
00119
00120 if width > 1:
00121 if available_width < thumbnail_width:
00122 thumbnail_width = available_width - 1
00123 QtImage = ImageQt(thumbnail_bitmap)
00124 pixmap = QPixmap.fromImage(QtImage)
00125 painter.drawPixmap(thumbnail_x, thumbnail_y, thumbnail_width, thumbnail_height, pixmap)
00126 thumbnail_x += thumbnail_width
00127
00128 if width == 1:
00129 break
00130
00131 painter.setPen(QPen(Qt.black))
00132 painter.setBrush(QBrush(Qt.transparent))
00133 if width == 1:
00134 painter.drawRect(x, y, thumbnail_x - x, height - thumbnail_gap - 1)
00135 else:
00136 painter.drawRect(x, y, width, height - thumbnail_gap - 1)
00137 return True
00138
00139 def close(self):
00140 if self.thumbnail_cache:
00141 self.thumbnail_cache.stop()
00142 self.thumbnail_cache.join()
00143
00144 def _load_thumbnail(self, topic, stamp, thumbnail_details):
00145 """
00146 Loads the thumbnail from the bag
00147 """
00148 (thumbnail_height,) = thumbnail_details
00149
00150
00151 t = rospy.Time.from_sec(stamp)
00152 bag, entry = self.timeline.scene().get_entry(t, topic)
00153 if not entry:
00154 return None, None
00155 pos = entry.position
00156
00157
00158
00159 with self.timeline.scene()._bag_lock:
00160 msg_topic, msg, msg_stamp = bag._read_message(pos)
00161
00162
00163 try:
00164 pil_image = image_helper.imgmsg_to_pil(msg)
00165 except Exception as ex:
00166 print('Error loading image on topic %s: %s' % (topic, str(ex)), file=sys.stderr)
00167 pil_image = None
00168
00169 if not pil_image:
00170 print('Disabling renderer on %s' % topic, file=sys.stderr)
00171 self.timeline.set_renderer_active(topic, False)
00172 return None, None
00173
00174
00175 try:
00176 pil_image_size = pil_image.size
00177 thumbnail_width = int(round(thumbnail_height * (float(pil_image_size[0]) / pil_image_size[1])))
00178
00179 thumbnail = pil_image.resize((thumbnail_width, thumbnail_height), self.quality)
00180
00181 return msg_stamp, thumbnail
00182
00183 except Exception as ex:
00184 print('Error loading image on topic %s: %s' % (topic, str(ex)), file=sys.stderr)
00185 raise
00186 return None, None