Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement a rqt_bag plotting plugin #239

Merged
merged 43 commits into from
Jun 10, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
beea724
Start adding plot plugin
trainman419 May 23, 2014
a0c2254
Add new plot plugin to plugins.xml
trainman419 May 23, 2014
c5a55e0
Add empty but working plot view
trainman419 May 23, 2014
683fce9
Tinkering with plot plugin
trainman419 May 23, 2014
9c18dea
Add topic to plugin view constructor
trainman419 May 27, 2014
2066f84
Initialize plot widget on startup
trainman419 May 28, 2014
9b35022
Pull in adwilson's matplot plugin
trainman419 May 28, 2014
c2cb201
Use thread for loading bag files
trainman419 May 28, 2014
7442316
bag plot mostly not working with rqt_plot backend
trainman419 May 28, 2014
9406b9e
Add vline and limits to matplot and pyqtgraph
trainman419 May 29, 2014
32ea817
Actually use position when drawing vline
trainman419 May 29, 2014
963e76a
Unify autoscaling and autoscrolling
trainman419 May 30, 2014
c7c5a5f
Make vline persist if a curve is removed
trainman419 May 30, 2014
b81f2f2
Actually use new limits methods
trainman419 May 30, 2014
1f6fd4b
Fix import after rebase
trainman419 May 30, 2014
9e672c5
More work on autoscaling
trainman419 May 30, 2014
b2bec47
Fix pyqtgraph get limits
trainman419 May 30, 2014
8f2b893
Preserve limits when switching backends
trainman419 May 30, 2014
56bd55c
Disable autoscrolling
trainman419 May 30, 2014
69e69bb
Extract bag and first message on startup
trainman419 May 31, 2014
9d89750
Remove a few TODOs and write a few more
trainman419 May 31, 2014
79e1300
Bug fixes
trainman419 May 31, 2014
8904efd
Emit region changed signal and use it for plot
trainman419 May 31, 2014
56fa23b
Update maintainer status
trainman419 May 31, 2014
4a126b7
Fix argument parsing
trainman419 May 31, 2014
47b5503
Fix bug in clear_values
trainman419 May 31, 2014
4b9605a
Make resampling work again
trainman419 May 31, 2014
c28a5a9
Fix curve removal bug
trainman419 May 31, 2014
59c5bff
Fix bag image viewer
trainman419 Jun 1, 2014
6612431
Refactor resampling, add more notes
trainman419 Jun 3, 2014
c8223f5
Strip data store out of individual plots
trainman419 Jun 3, 2014
cca7851
Fix limits bug in matplot
trainman419 Jun 3, 2014
3dd9a03
DataPlot emits a limits_changed event
trainman419 Jun 3, 2014
1d45e9e
Add TODO about pyqtgraph limit setting
trainman419 Jun 3, 2014
6816df7
Completely refactor limits and timestep updates
trainman419 Jun 3, 2014
3872fd8
Do add_curve and redraw with signals
trainman419 Jun 3, 2014
45c3da5
Actually do resampling in a thread
trainman419 Jun 3, 2014
120eec6
Update plot on region change, clean up comments
trainman419 Jun 3, 2014
c63631f
Remove verbose warnings
trainman419 Jun 3, 2014
da409ab
Re-add home button
trainman419 Jun 3, 2014
87c2e90
Clean up print statements in qwtplot
trainman419 Jun 3, 2014
3e3dff2
Fix startup error on plotting
trainman419 Jun 3, 2014
abe95a1
Clean up and address minor issues
trainman419 Jun 5, 2014
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions rqt_bag/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<version>0.3.6</version>
<description>rqt_bag provides a GUI plugin for displaying and replaying ROS bag files.</description>
<maintainer email="[email protected]">Aaron Blasdel</maintainer>
<maintainer email="[email protected]">Austin Hendrix</maintainer>

<license>BSD</license>

Expand Down
21 changes: 17 additions & 4 deletions rqt_bag/src/rqt_bag/bag.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,14 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

import os
import argparse
import threading

from qt_gui.plugin import Plugin

from .bag_widget import BagWidget


class Bag(Plugin):
"""
Subclass of Plugin to provide interactive bag visualization, playing(publishing) and recording
Expand All @@ -55,19 +56,31 @@ def __init__(self, context):
self._widget.setWindowTitle(self._widget.windowTitle() + (' (%d)' % context.serial_number()))
context.add_widget(self._widget)

for bagfile in args.bagfiles:
self._widget.load_bag(bagfile)
def load_bags():
for bagfile in args.bagfiles:
self._widget.load_bag(bagfile)

load_thread = threading.Thread(target=load_bags)
load_thread.start()

def _parse_args(self, argv):
parser = argparse.ArgumentParser(prog='rqt_bag', add_help=False)
Bag.add_arguments(parser)
return parser.parse_args(argv)

@staticmethod
def _isfile(parser, arg):
if os.path.isfile(arg):
return arg
else:
parser.error("Bag file %s does not exist" % ( arg ))

@staticmethod
def add_arguments(parser):
group = parser.add_argument_group('Options for rqt_bag plugin')
group.add_argument('--clock', action='store_true', help='publish the clock time')
group.add_argument('bagfiles', type=argparse.FileType('r'), nargs='*', default=[], help='Bagfiles to load')
group.add_argument('bagfiles', type=lambda x: Bag._isfile(parser, x),
nargs='*', default=[], help='Bagfiles to load')

def shutdown_plugin(self):
self._widget.shutdown_all()
Expand Down
1 change: 1 addition & 0 deletions rqt_bag/src/rqt_bag/bag_timeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class BagTimeline(QGraphicsScene):
Also handles events
"""
status_bar_changed_signal = Signal()
selected_region_changed = Signal(rospy.Time, rospy.Time)

def __init__(self, context, publish_clock):
"""
Expand Down
32 changes: 31 additions & 1 deletion rqt_bag/src/rqt_bag/bag_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
import rospkg

from python_qt_binding import loadUi
from python_qt_binding.QtCore import Qt
from python_qt_binding.QtCore import Qt, qWarning, Signal
from python_qt_binding.QtGui import QFileDialog, QGraphicsView, QIcon, QWidget

import rosbag
Expand All @@ -55,6 +55,9 @@ class BagWidget(QWidget):
Widget for use with Bag class to display and replay bag files
Handles all widget callbacks and contains the instance of BagTimeline for storing visualizing bag data
"""

set_status_text = Signal(str)

def __init__(self, context, publish_clock):
"""
:param context: plugin context hook to enable adding widgets as a ROS_GUI pane, ''PluginContext''
Expand Down Expand Up @@ -123,6 +126,7 @@ def __init__(self, context, publish_clock):
self._recording = False

self._timeline.status_bar_changed_signal.connect(self._update_status_bar)
self.set_status_text.connect(self._set_status_text)

def graphics_view_on_key_press(self, event):
key = event.key()
Expand Down Expand Up @@ -236,6 +240,18 @@ def _handle_load_clicked(self):
self.load_bag(filename[0])

def load_bag(self, filename):
qWarning("Loading %s" % filename)

# QProgressBar can EITHER: show text or show a bouncing loading bar,
# but apparently the text is hidden when the bounding loading bar is
# shown
#self.progress_bar.setRange(0, 0)
self.set_status_text.emit("Loading %s" % filename)
#progress_format = self.progress_bar.format()
#progress_text_visible = self.progress_bar.isTextVisible()
#self.progress_bar.setFormat("Loading %s" % filename)
#self.progress_bar.setTextVisible(True)

bag = rosbag.Bag(filename)
self.play_button.setEnabled(True)
self.thumbs_button.setEnabled(True)
Expand All @@ -249,12 +265,26 @@ def load_bag(self, filename):
self.save_button.setEnabled(True)
self.record_button.setEnabled(False)
self._timeline.add_bag(bag)
qWarning("Done loading %s" % filename )
# put the progress bar back the way it was
self.set_status_text.emit("")
#self.progress_bar.setFormat(progress_format)
#self.progress_bar.setTextVisible(progress_text_visible) # causes a segfault :(
#self.progress_bar.setRange(0, 100)
# self clear loading filename

def _handle_save_clicked(self):
filename = QFileDialog.getSaveFileName(self, self.tr('Save selected region to file...'), '.', self.tr('Bag files {.bag} (*.bag)'))
if filename[0] != '':
self._timeline.copy_region_to_bag(filename[0])

def _set_status_text(self, text):
if text:
self.progress_bar.setFormat(text)
self.progress_bar.setTextVisible(True)
else:
self.progress_bar.setTextVisible(False)

def _update_status_bar(self):
if self._timeline._timeline_frame.playhead is None or self._timeline._timeline_frame.start_stamp is None:
return
Expand Down
3 changes: 2 additions & 1 deletion rqt_bag/src/rqt_bag/plugins/message_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ class MessageView(QObject):
"""
name = 'Untitled'

def __init__(self, timeline):
def __init__(self, timeline, topic):
super(MessageView, self).__init__()
self.timeline = timeline
self.topic = topic

def message_viewed(self, bag, msg_details):
"""
Expand Down
4 changes: 2 additions & 2 deletions rqt_bag/src/rqt_bag/plugins/raw_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@ class RawView(TopicMessageView):
Plugin to view a message in a treeview window
The message is loaded into a custum treewidget
"""
def __init__(self, timeline, parent):
def __init__(self, timeline, parent, topic):
"""
:param timeline: timeline data object, ''BagTimeline''
:param parent: widget that will be added to the ros_gui context, ''QWidget''
"""
super(RawView, self).__init__(timeline, parent)
super(RawView, self).__init__(timeline, parent, topic)
self.message_tree = MessageTree(parent)
parent.layout().addWidget(self.message_tree) # This will automatically resize the message_tree to the windowsize

Expand Down
31 changes: 7 additions & 24 deletions rqt_bag/src/rqt_bag/plugins/topic_message_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,10 @@ class TopicMessageView(MessageView):
"""
A message view with a toolbar for navigating messages in a single topic.
"""
def __init__(self, timeline, parent):
MessageView.__init__(self, timeline)
def __init__(self, timeline, parent, topic):
MessageView.__init__(self, timeline, topic)

self._parent = parent
self._topic = None
self._stamp = None
self._name = parent.objectName()

Expand All @@ -65,55 +64,39 @@ def __init__(self, timeline, parent):
def parent(self):
return self._parent

@property
def topic(self):
return self._topic

@property
def stamp(self):
return self._stamp

# MessageView implementation

def message_viewed(self, bag, msg_details):
self._topic, _, self._stamp = msg_details[:3]
_, _, self._stamp = msg_details[:3]

# Events
def navigate_first(self):
if not self.topic:
return

for entry in self.timeline.get_entries(self._topic, *self.timeline._timeline_frame.play_region):
for entry in self.timeline.get_entries([self.topic], *self.timeline._timeline_frame.play_region):
self.timeline._timeline_frame.playhead = entry.time
break

def navigate_previous(self):
if not self.topic:
return

last_entry = None
for entry in self.timeline.get_entries(self._topic, self.timeline._timeline_frame.start_stamp, self.timeline._timeline_frame.playhead):
for entry in self.timeline.get_entries([self.topic], self.timeline._timeline_frame.start_stamp, self.timeline._timeline_frame.playhead):
if entry.time < self.timeline._timeline_frame.playhead:
last_entry = entry

if last_entry:
self.timeline._timeline_frame.playhead = last_entry.time

def navigate_next(self):
if not self.topic:
return

for entry in self.timeline.get_entries(self._topic, self.timeline._timeline_frame.playhead, self.timeline._timeline_frame.end_stamp):
for entry in self.timeline.get_entries([self.topic], self.timeline._timeline_frame.playhead, self.timeline._timeline_frame.end_stamp):
if entry.time > self.timeline._timeline_frame.playhead:
self.timeline._timeline_frame.playhead = entry.time
break

def navigate_last(self):
if not self.topic:
return

last_entry = None
for entry in self.timeline.get_entries(self._topic, *self.timeline._timeline_frame.play_region):
for entry in self.timeline.get_entries([self.topic], *self.timeline._timeline_frame.play_region):
last_entry = entry

if last_entry:
Expand Down
11 changes: 10 additions & 1 deletion rqt_bag/src/rqt_bag/timeline_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
# POSSIBILITY OF SUCH DAMAGE.


from python_qt_binding.QtCore import qDebug, QPointF, QRectF, Qt, qWarning
from python_qt_binding.QtCore import qDebug, QPointF, QRectF, Qt, qWarning, Signal
from python_qt_binding.QtGui import QBrush, QCursor, QColor, QFont, \
QFontMetrics, QGraphicsItem, QPen, \
QPolygonF
Expand Down Expand Up @@ -68,6 +68,7 @@ class TimelineFrame(QGraphicsItem):
(time delimiters, labels, topic names and backgrounds).
Also handles mouse callbacks since they interact closely with the drawn elements
"""

def __init__(self):
super(TimelineFrame, self).__init__()

Expand Down Expand Up @@ -251,6 +252,9 @@ def play_region(self):
else:
return (self._start_stamp, self._end_stamp)

def emit_play_region(self):
self.scene().selected_region_changed.emit(*self.play_region)

@property
def start_stamp(self):
return self._start_stamp
Expand Down Expand Up @@ -909,6 +913,8 @@ def reset_timeline(self):
self._selected_right = None
self._selecting_mode = _SelectionMode.NONE

self.emit_play_region()

if self._stamp_left is not None:
self.playhead = rospy.Time.from_sec(self._stamp_left)

Expand Down Expand Up @@ -1039,6 +1045,7 @@ def on_left_down(self, event):
self._selected_right = None
self._selecting_mode = _SelectionMode.LEFT_MARKED
self.scene().update()
self.emit_play_region()

elif self._selecting_mode == _SelectionMode.MARKED:
left_x = self.map_stamp_to_x(self._selected_left)
Expand All @@ -1048,6 +1055,7 @@ def on_left_down(self, event):
self._selected_right = None
self._selecting_mode = _SelectionMode.LEFT_MARKED
self.scene().update()
self.emit_play_region()
elif self._selecting_mode == _SelectionMode.SHIFTING:
self.scene().views()[0].setCursor(QCursor(Qt.ClosedHandCursor))

Expand Down Expand Up @@ -1139,6 +1147,7 @@ def on_mouse_move(self, event):
self._selected_left = max(self._start_stamp.to_sec(), min(self._end_stamp.to_sec(), self._selected_left + dstamp))
self._selected_right = max(self._start_stamp.to_sec(), min(self._end_stamp.to_sec(), self._selected_right + dstamp))
self.scene().update()
self.emit_play_region()

elif clicked_x >= self._history_left and clicked_x <= self._history_right and clicked_y >= self._history_top and clicked_y <= self._history_bottom:
# Left and clicked within timeline: change playhead
Expand Down
2 changes: 1 addition & 1 deletion rqt_bag/src/rqt_bag/timeline_menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ def process(self, action):

viewer_type = action.data()

view = viewer_type(self.timeline, frame)
view = viewer_type(self.timeline, frame, str(topic))

self.timeline.popups[popup_name] = frame
self.timeline.get_context().add_widget(frame)
Expand Down
1 change: 1 addition & 0 deletions rqt_bag_plugins/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<version>0.3.6</version>
<description>rqt_bag provides a GUI plugin for displaying and replaying ROS bag files.</description>
<maintainer email="[email protected]">Aaron Blasdel</maintainer>
<maintainer email="[email protected]">Austin Hendrix</maintainer>

<license>BSD</license>

Expand Down
6 changes: 6 additions & 0 deletions rqt_bag_plugins/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,10 @@
A plugin for rqt_bag for showing images.
</description>
</class>

<class name="BagPlotPlugin" type="rqt_bag_plugins.plot_plugin.PlotPlugin" base_class_type="rqt_bag::Plugin">
<description>
A plugin for rqt_bag for plotting data.
</description>
</class>
</library>
Loading