rxbag: draw active messages

This commit is contained in:
Tim Field 2010-05-07 20:00:58 +00:00
parent 1324c724cd
commit 099b77f9be
7 changed files with 92 additions and 64 deletions

View File

@ -35,17 +35,22 @@
PKG = 'rxbag' PKG = 'rxbag'
import roslib; roslib.load_manifest(PKG) import roslib; roslib.load_manifest(PKG)
import rospy import rospy
import bisect
import time import time
import wx import wx
from util.layer import Layer from util.layer import Layer
## A widget that can render an interval of time of a topic as a rectangle on the timeline
class TimelineRenderer: class TimelineRenderer:
"""
A widget that can render an interval of time of a topic as a rectangle on the timeline.
@param msg_combine_px: don't draw discrete messages if they're less than this many pixels separated [optional]
@type msg_combine_px: float
"""
def __init__(self, timeline, msg_combine_px=1.5): def __init__(self, timeline, msg_combine_px=1.5):
self.timeline = timeline self.timeline = timeline
self.msg_combine_px = msg_combine_px # don't draw discrete messages if they're less than this many pixels separated self.msg_combine_px = msg_combine_px
def get_segment_height(self, topic): def get_segment_height(self, topic):
return None return None
@ -55,13 +60,13 @@ class TimelineRenderer:
class MessageView(Layer): class MessageView(Layer):
""" """
A widget that can display message details A widget that can display message details.
""" """
name = 'Untitled' name = 'Untitled'
def __init__(self, timeline, parent, title, x, y, width, height, max_repaint=None): def __init__(self, timeline, parent, title, x, y, width, height):
Layer.__init__(self, parent, title, x, y, width, height, max_repaint) Layer.__init__(self, parent, title, x, y, width, height)
self.timeline = timeline self.timeline = timeline
self.border = False self.border = False
@ -76,55 +81,60 @@ class MessageView(Layer):
pass pass
class TopicMessageView(MessageView): class TopicMessageView(MessageView):
def __init__(self, timeline, parent, title, x, y, width, height, max_repaint=None): def __init__(self, timeline, parent, title, x, y, width, height):
MessageView.__init__(self, timeline, parent, title, x, y, width, height, max_repaint) MessageView.__init__(self, timeline, parent, title, x, y, width, height)
self.topic = None self.topic = None
self.msg_index = None self.stamp = None
self.entry = None
self._create_toolbar() self._create_toolbar()
def message_viewed(self, bag, msg_details): def message_viewed(self, bag, msg_details):
topic, msg, t = msg_details topic, msg, t = msg_details
self.topic = topic self.topic = topic
#self.msg_index = msg_index self.stamp = t
def message_cleared(self): def message_cleared(self):
pass pass
#self.msg_index = None
def on_close(self, event): def on_close(self, event):
self.timeline.remove_view(self.topic, self) self.timeline.remove_view(self.topic, self)
def navigate_first(self): def navigate_first(self):
# todo: fix if not self.topic:
if self.topic and self.msg_index is not None: return
topic_positions = self.timeline.bag_index.msg_positions[self.topic]
if len(topic_positions) > 0:
self.timeline.set_playhead(topic_positions[0][0])
def navigate_previous(self): for entry in self.timeline.bag_file._get_entries(self.timeline.bag_file._get_connections(self.topic)):
# todo: fix self.timeline.set_playhead(entry.time.to_sec())
if self.topic and self.msg_index is not None: break
new_msg_index = self.msg_index - 1
if new_msg_index >= 0:
self.timeline.set_playhead(self.timeline.bag_index.msg_positions[self.topic][new_msg_index][0])
def navigate_next(self): # def navigate_previous(self):
# todo: fix # if not self.topic:
if self.topic and self.msg_index is not None: # return
new_msg_index = self.msg_index + 1 #
topic_positions = self.timeline.bag_index.msg_positions[self.topic] # index = bisect.bisect_right(self.timeline.message_history_cache[self.topic], self.stamp.to_sec()) - 1
if new_msg_index < len(topic_positions): # if index > 0:
self.timeline.set_playhead(topic_positions[new_msg_index][0]) # self.stamp = self.timeline.message_history_cache[self.topic][index - 1]
# self.timeline.set_playhead(self.stamp)
#
# def navigate_next(self):
# if not self.topic:
# return
#
# index = bisect.bisect_right(self.timeline.message_history_cache[self.topic], self.stamp.to_sec()) - 1
# if index < len(self.timeline.message_history_cache[self.topic]) - 1:
# self.stamp = self.timeline.message_history_cache[self.topic][index + 1]
# self.timeline.set_playhead(self.stamp)
def navigate_last(self): def navigate_last(self):
# todo: fix if not self.topic:
if self.topic and self.msg_index is not None: return
topic_positions = self.timeline.bag_index.msg_positions[self.topic]
if len(topic_positions) > 0: for entry in self.timeline.bag_file._get_entries_reverse(self.timeline.bag_file._get_connections(self.topic)):
self.timeline.set_playhead(topic_positions[-1][0]) self.timeline.set_playhead(entry.time.to_sec())
break
@property @property
def frame(self): def frame(self):
@ -135,7 +145,7 @@ class TopicMessageView(MessageView):
tb = self.frame.CreateToolBar() tb = self.frame.CreateToolBar()
tb.Bind(wx.EVT_TOOL, lambda e: self.navigate_first(), tb.AddLabelTool(wx.ID_ANY, '', wx.Bitmap(icons_dir + 'resultset_first.png'))) tb.Bind(wx.EVT_TOOL, lambda e: self.navigate_first(), tb.AddLabelTool(wx.ID_ANY, '', wx.Bitmap(icons_dir + 'resultset_first.png')))
tb.Bind(wx.EVT_TOOL, lambda e: self.navigate_previous(), tb.AddLabelTool(wx.ID_ANY, '', wx.Bitmap(icons_dir + 'resultset_previous.png'))) #tb.Bind(wx.EVT_TOOL, lambda e: self.navigate_previous(), tb.AddLabelTool(wx.ID_ANY, '', wx.Bitmap(icons_dir + 'resultset_previous.png')))
tb.Bind(wx.EVT_TOOL, lambda e: self.navigate_next(), tb.AddLabelTool(wx.ID_ANY, '', wx.Bitmap(icons_dir + 'resultset_next.png'))) #tb.Bind(wx.EVT_TOOL, lambda e: self.navigate_next(), tb.AddLabelTool(wx.ID_ANY, '', wx.Bitmap(icons_dir + 'resultset_next.png')))
tb.Bind(wx.EVT_TOOL, lambda e: self.navigate_last(), tb.AddLabelTool(wx.ID_ANY, '', wx.Bitmap(icons_dir + 'resultset_last.png'))) tb.Bind(wx.EVT_TOOL, lambda e: self.navigate_last(), tb.AddLabelTool(wx.ID_ANY, '', wx.Bitmap(icons_dir + 'resultset_last.png')))
tb.Realize() tb.Realize()

View File

@ -40,8 +40,8 @@ import wx
from util.layer import Layer from util.layer import Layer
class PlayheadLayer(Layer): class PlayheadLayer(Layer):
def __init__(self, parent, title, timeline, x, y, width, height, max_repaint=None): def __init__(self, parent, title, timeline, x, y, width, height):
Layer.__init__(self, parent, title, x, y, width, height, max_repaint) Layer.__init__(self, parent, title, x, y, width, height)
self.timeline = timeline self.timeline = timeline

View File

@ -47,8 +47,8 @@ from message_view import TopicMessageView
class RawView(TopicMessageView): class RawView(TopicMessageView):
name = 'Raw' name = 'Raw'
def __init__(self, timeline, parent, title, x, y, width, height, max_repaint=0.1): def __init__(self, timeline, parent, title, x, y, width, height):
TopicMessageView.__init__(self, timeline, parent, title, x, y, width, height, max_repaint) TopicMessageView.__init__(self, timeline, parent, title, x, y, width, height)
self._font = wx.Font(9, wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL) self._font = wx.Font(9, wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)
@ -96,6 +96,7 @@ class RawView(TopicMessageView):
self.msg_tree.SetSize((size[0], size[1] - self.msg_tree.GetPosition()[1])) self.msg_tree.SetSize((size[0], size[1] - self.msg_tree.GetPosition()[1]))
def clear(self): def clear(self):
self.msg_title.SetValue('')
self.msg_incoming = None self.msg_incoming = None
self.invalidate() self.invalidate()

View File

@ -87,8 +87,8 @@ class RxBagApp(wx.App):
frame.Bind(wx.EVT_CLOSE, lambda e: wx.Exit()) frame.Bind(wx.EVT_CLOSE, lambda e: wx.Exit())
except Exception, ex: except Exception, ex:
rospy.logerr('Error initializing application: %s' % str(ex)) print >> sys.stderr, 'Error initializing application:', ex
raise return False
return True return True

View File

@ -41,8 +41,8 @@ from util.layer import Layer
from bag_helper import BagHelper from bag_helper import BagHelper
class StatusLayer(Layer): class StatusLayer(Layer):
def __init__(self, parent, title, timeline, x, y, width, height, max_repaint=None): def __init__(self, parent, title, timeline, x, y, width, height):
Layer.__init__(self, parent, title, x, y, width, height, max_repaint) Layer.__init__(self, parent, title, x, y, width, height)
self.timeline = timeline self.timeline = timeline

View File

@ -92,7 +92,7 @@ class TimelinePanel(LayerPanel):
x, y = 5, 19 x, y = 5, 19
self.timeline = Timeline(self, 'Timeline', x, y, width - x, height - y, max_repaint=1.0) self.timeline = Timeline(self, 'Timeline', x, y, width - x, height - y)
self.timeline.set_bag_files(self.bag_files) self.timeline.set_bag_files(self.bag_files)
self.status = status.StatusLayer(self, 'Status', self.timeline, self.timeline.x, 4, 300, 16) self.status = status.StatusLayer(self, 'Status', self.timeline, self.timeline.x, 4, 300, 16)
@ -142,8 +142,8 @@ class TimelinePanel(LayerPanel):
class Timeline(Layer): class Timeline(Layer):
name = 'Timeline' name = 'Timeline'
def __init__(self, parent, title, x, y, width, height, max_repaint=None): def __init__(self, parent, title, x, y, width, height):
Layer.__init__(self, parent, title, x, y, width, height, max_repaint) Layer.__init__(self, parent, title, x, y, width, height)
self.bag_files = {} self.bag_files = {}
self.bag_file = None self.bag_file = None
@ -390,6 +390,7 @@ class Timeline(Layer):
self.listeners.setdefault(topic, []).append(listener) self.listeners.setdefault(topic, []).append(listener)
self.update_message_view() self.update_message_view()
self.invalidate()
def remove_listener(self, topic, listener): def remove_listener(self, topic, listener):
topic_listeners = self.listeners.get(topic) topic_listeners = self.listeners.get(topic)
@ -397,6 +398,7 @@ class Timeline(Layer):
topic_listeners.remove(listener) topic_listeners.remove(listener)
if len(topic_listeners) == 0: if len(topic_listeners) == 0:
del self.listeners[topic] del self.listeners[topic]
self.invalidate()
@property @property
def history_bottom(self): def history_bottom(self):
@ -604,7 +606,9 @@ class Timeline(Layer):
row += 1 row += 1
def _draw_time_indicators(self, dc): def _draw_time_indicators(self, dc):
"""Draw time indicators on the timeline""" """
Draw vertical grid-lines showing major and minor time divisions.
"""
x_per_sec = self.map_dstamp_to_dx(1.0) x_per_sec = self.map_dstamp_to_dx(1.0)
@ -632,7 +636,7 @@ class Timeline(Layer):
dc.set_line_width(1) dc.set_line_width(1)
for stamp in stamps: for stamp in stamps:
x = self.map_stamp_to_x(stamp, False) x = self.map_stamp_to_x(stamp, False)
label = self._get_label(division, stamp - start_stamp) label = self._get_label(division, stamp - start_stamp)
label_x = x + 3 label_x = x + 3
@ -646,9 +650,9 @@ class Timeline(Layer):
dc.set_source_rgba(0.25, 0.25, 0.25, 0.3) dc.set_source_rgba(0.25, 0.25, 0.25, 0.3)
dc.move_to(x, label_y + 1) dc.move_to(x, label_y + 1)
dc.line_to(x, self.history_bottom) dc.line_to(x, self.history_bottom)
dc.stroke()
dc.stroke()
def _draw_minor_divisions(self, dc, stamps, start_stamp, division): def _draw_minor_divisions(self, dc, stamps, start_stamp, division):
xs = [self.map_stamp_to_x(stamp) for stamp in stamps] xs = [self.map_stamp_to_x(stamp) for stamp in stamps]
@ -708,7 +712,7 @@ class Timeline(Layer):
break break
else: else:
self._draw_topic_history(dc, topic) self._draw_topic_history(dc, topic)
def _draw_topic_history(self, dc, topic): def _draw_topic_history(self, dc, topic):
""" """
Draw boxes to show message regions on timelines. Draw boxes to show message regions on timelines.
@ -733,9 +737,6 @@ class Timeline(Layer):
if msg_combine_interval is None: if msg_combine_interval is None:
msg_combine_interval = self.map_dx_to_dstamp(self.default_msg_combine_px) msg_combine_interval = self.map_dx_to_dstamp(self.default_msg_combine_px)
start_time = roslib.rostime.Time.from_sec(max(0.0, self.stamp_left))
end_time = roslib.rostime.Time.from_sec(max(0.0, self.stamp_right))
if topic not in self.message_history_cache: if topic not in self.message_history_cache:
start_time = roslib.rostime.Time.from_sec(max(0.0, self.start_stamp)) start_time = roslib.rostime.Time.from_sec(max(0.0, self.start_stamp))
end_time = roslib.rostime.Time.from_sec(max(0.0, self.end_stamp)) end_time = roslib.rostime.Time.from_sec(max(0.0, self.end_stamp))
@ -744,17 +745,19 @@ class Timeline(Layer):
self.message_history_cache[topic] = all_stamps self.message_history_cache[topic] = all_stamps
else: else:
all_stamps = self.message_history_cache[topic] all_stamps = self.message_history_cache[topic]
start_index = bisect.bisect_left(all_stamps, self.stamp_left) start_index = bisect.bisect_left(all_stamps, self.stamp_left)
end_index = bisect.bisect_left(all_stamps, self.stamp_right) end_index = bisect.bisect_left(all_stamps, self.stamp_right)
# Set pen based on datatype # Set pen based on datatype
datatype_color = self.datatype_colors.get(datatype, self.default_datatype_color) datatype_color = self.datatype_colors.get(datatype, self.default_datatype_color)
dc.set_source_rgb(datatype_color.red / 255.0, datatype_color.green / 255.0, datatype_color.blue / 255.0) datatype_rgb = datatype_color.red / 255.0, datatype_color.green / 255.0, datatype_color.blue / 255.0
# Iterate through regions of connected messages # Iterate through regions of connected messages
width_interval = self.history_width / (self.stamp_right - self.stamp_left) width_interval = self.history_width / (self.stamp_right - self.stamp_left)
dc.set_line_width(1)
dc.set_source_rgb(*datatype_rgb)
for (stamp_start, stamp_end) in self._find_regions(all_stamps[start_index:end_index], self.map_dx_to_dstamp(self.default_msg_combine_px)): for (stamp_start, stamp_end) in self._find_regions(all_stamps[start_index:end_index], self.map_dx_to_dstamp(self.default_msg_combine_px)):
region_x_start = self.history_left + (stamp_start - self.stamp_left) * width_interval region_x_start = self.history_left + (stamp_start - self.stamp_left) * width_interval
region_x_end = self.history_left + (stamp_end - self.stamp_left) * width_interval region_x_end = self.history_left + (stamp_end - self.stamp_left) * width_interval
@ -764,6 +767,19 @@ class Timeline(Layer):
dc.fill() dc.fill()
# Draw active message
if topic in self.listeners:
dc.set_line_width(3)
playhead_stamp = None
playhead_index = bisect.bisect_right(all_stamps, self.playhead) - 1
if playhead_index >= 0:
playhead_stamp = all_stamps[playhead_index]
if playhead_stamp > self.stamp_left and playhead_stamp < self.stamp_right:
playhead_x = self.history_left + (all_stamps[playhead_index] - self.stamp_left) * width_interval
dc.move_to(playhead_x, msg_y)
dc.line_to(playhead_x, msg_y + msg_height)
dc.stroke()
# Custom renderer # Custom renderer
if renderer: if renderer:
# Iterate through regions of connected messages # Iterate through regions of connected messages
@ -956,11 +972,12 @@ class Timeline(Layer):
msgs[topic] = None msgs[topic] = None
# Inform the listeners # Inform the listeners
for topic, msg_data in msgs.items(): for topic in self.topics:
topic_listeners = self.listeners.get(topic) topic_listeners = self.listeners.get(topic)
if not topic_listeners: if not topic_listeners:
continue continue
msg_data = msgs.get(topic)
if msg_data: if msg_data:
for listener in topic_listeners: for listener in topic_listeners:
listener.message_viewed(self.bag_file, msg_data) listener.message_viewed(self.bag_file, msg_data)

View File

@ -40,7 +40,7 @@ import wx.lib.wxcairo
class Layer: class Layer:
name = 'Untitled' name = 'Untitled'
def __init__(self, parent, title, x, y, width, height, max_repaint=None): def __init__(self, parent, title, x, y, width, height):
self._x = x self._x = x
self._y = y self._y = y
self._width = width self._width = width