bag_widget.py
Go to the documentation of this file.
1 # Software License Agreement (BSD License)
2 #
3 # Copyright (c) 2012, Willow Garage, Inc.
4 # All rights reserved.
5 #
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
8 # are met:
9 #
10 # * Redistributions of source code must retain the above copyright
11 # notice, this list of conditions and the following disclaimer.
12 # * Redistributions in binary form must reproduce the above
13 # copyright notice, this list of conditions and the following
14 # disclaimer in the documentation and/or other materials provided
15 # with the distribution.
16 # * Neither the name of Willow Garage, Inc. nor the names of its
17 # contributors may be used to endorse or promote products derived
18 # from this software without specific prior written permission.
19 #
20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 # POSSIBILITY OF SUCH DAMAGE.
32 
33 import os
34 import time
35 
36 import rospy
37 import rospkg
38 
39 from python_qt_binding import loadUi
40 from python_qt_binding.QtCore import qDebug, Qt, qWarning, Signal
41 from python_qt_binding.QtGui import QIcon
42 from python_qt_binding.QtWidgets import QFileDialog, QGraphicsView, QWidget
43 
44 import rosbag
45 from rqt_bag import bag_helper
46 from .bag_timeline import BagTimeline
47 from .topic_selection import TopicSelection
48 
49 
50 class BagGraphicsView(QGraphicsView):
51 
52  def __init__(self, parent=None):
53  super(BagGraphicsView, self).__init__()
54 
55 
56 class BagWidget(QWidget):
57 
58  """
59  Widget for use with Bag class to display and replay bag files
60  Handles all widget callbacks and contains the instance of BagTimeline for storing visualizing bag data
61  """
62 
63  set_status_text = Signal(str)
64 
65  def __init__(self, context, publish_clock):
66  """
67  :param context: plugin context hook to enable adding widgets as a ROS_GUI pane, ''PluginContext''
68  """
69  super(BagWidget, self).__init__()
70  rp = rospkg.RosPack()
71  ui_file = os.path.join(rp.get_path('rqt_bag'), 'resource', 'bag_widget.ui')
72  loadUi(ui_file, self, {'BagGraphicsView': BagGraphicsView})
73 
74  self.setObjectName('BagWidget')
75 
76  self._timeline = BagTimeline(context, publish_clock)
77  self.graphics_view.setScene(self._timeline)
78 
79  self.graphics_view.resizeEvent = self._resizeEvent
80  self.graphics_view.setMouseTracking(True)
81 
82  self.play_icon = QIcon.fromTheme('media-playback-start')
83  self.pause_icon = QIcon.fromTheme('media-playback-pause')
84  self.play_button.setIcon(self.play_icon)
85  self.begin_button.setIcon(QIcon.fromTheme('media-skip-backward'))
86  self.end_button.setIcon(QIcon.fromTheme('media-skip-forward'))
87  self.slower_button.setIcon(QIcon.fromTheme('media-seek-backward'))
88  self.faster_button.setIcon(QIcon.fromTheme('media-seek-forward'))
89  self.previous_button.setIcon(QIcon.fromTheme('go-previous'))
90  self.next_button.setIcon(QIcon.fromTheme('go-next'))
91  self.zoom_in_button.setIcon(QIcon.fromTheme('zoom-in'))
92  self.zoom_out_button.setIcon(QIcon.fromTheme('zoom-out'))
93  self.zoom_all_button.setIcon(QIcon.fromTheme('zoom-original'))
94  self.thumbs_button.setIcon(QIcon.fromTheme('insert-image'))
95  self.record_button.setIcon(QIcon.fromTheme('media-record'))
96  self.load_button.setIcon(QIcon.fromTheme('document-open'))
97  self.save_button.setIcon(QIcon.fromTheme('document-save'))
98 
99  self.play_button.clicked[bool].connect(self._handle_play_clicked)
100  self.thumbs_button.clicked[bool].connect(self._handle_thumbs_clicked)
101  self.zoom_in_button.clicked[bool].connect(self._handle_zoom_in_clicked)
102  self.zoom_out_button.clicked[bool].connect(self._handle_zoom_out_clicked)
103  self.zoom_all_button.clicked[bool].connect(self._handle_zoom_all_clicked)
104  self.previous_button.clicked[bool].connect(self._handle_previous_clicked)
105  self.next_button.clicked[bool].connect(self._handle_next_clicked)
106  self.faster_button.clicked[bool].connect(self._handle_faster_clicked)
107  self.slower_button.clicked[bool].connect(self._handle_slower_clicked)
108  self.begin_button.clicked[bool].connect(self._handle_begin_clicked)
109  self.end_button.clicked[bool].connect(self._handle_end_clicked)
110  self.record_button.clicked[bool].connect(self._handle_record_clicked)
111  self.load_button.clicked[bool].connect(self._handle_load_clicked)
112  self.save_button.clicked[bool].connect(self._handle_save_clicked)
113  self.graphics_view.mousePressEvent = self._timeline.on_mouse_down
114  self.graphics_view.mouseReleaseEvent = self._timeline.on_mouse_up
115  self.graphics_view.mouseMoveEvent = self._timeline.on_mouse_move
116  self.graphics_view.wheelEvent = self._timeline.on_mousewheel
119  # TODO when the closeEvent is properly called by ROS_GUI implement that
120  # event instead of destroyed
121  self.destroyed.connect(self.handle_destroy)
122 
123  self.graphics_view.keyPressEvent = self.graphics_view_on_key_press
124  self.play_button.setEnabled(False)
125  self.thumbs_button.setEnabled(False)
126  self.zoom_in_button.setEnabled(False)
127  self.zoom_out_button.setEnabled(False)
128  self.zoom_all_button.setEnabled(False)
129  self.previous_button.setEnabled(False)
130  self.next_button.setEnabled(False)
131  self.faster_button.setEnabled(False)
132  self.slower_button.setEnabled(False)
133  self.begin_button.setEnabled(False)
134  self.end_button.setEnabled(False)
135  self.save_button.setEnabled(False)
136 
137  self._recording = False
138 
139  self._timeline.status_bar_changed_signal.connect(self._update_status_bar)
140  self.set_status_text.connect(self._set_status_text)
141 
142  def graphics_view_on_key_press(self, event):
143  key = event.key()
144  if key in (Qt.Key_Left, Qt.Key_Right, Qt.Key_Up, Qt.Key_Down, Qt.Key_PageUp, Qt.Key_PageDown):
145  # This causes the graphics view to ignore these keys so they can be caught
146  # by the bag_widget keyPressEvent
147  event.ignore()
148  else:
149  # Maintains functionality for all other keys QGraphicsView implements
150  QGraphicsView.keyPressEvent(self.graphics_view, event)
151 
152  # callbacks for ui events
153  def on_key_press(self, event):
154  key = event.key()
155  if key == Qt.Key_Space:
156  self._timeline.toggle_play()
157  elif key == Qt.Key_Home:
158  self._timeline.navigate_start()
159  elif key == Qt.Key_End:
160  self._handle_end_clicked()
161  elif key == Qt.Key_Plus or key == Qt.Key_Equal:
163  elif key == Qt.Key_Minus:
165  elif key == Qt.Key_Left:
166  self._timeline.translate_timeline_left()
167  elif key == Qt.Key_Right:
168  self._timeline.translate_timeline_right()
169  elif key == Qt.Key_Up or key == Qt.Key_PageUp:
171  elif key == Qt.Key_Down or key == Qt.Key_PageDown:
173 
174  def handle_destroy(self, args):
175  self._timeline.handle_close()
176 
177  def handle_close(self, event):
178  self.shutdown_all()
179 
180  event.accept()
181 
182  def _resizeEvent(self, event):
183  # TODO The -2 allows a buffer zone to make sure the scroll bars do not
184  # appear when not needed. # On some systems (Lucid) this doesn't function
185  # properly # need to look at a method to determine the maximum size of the
186  # scene that # will maintain a proper no scrollbar fit in the view.
187  self.graphics_view.scene().setSceneRect(
188  0, 0, self.graphics_view.width() - 2,
189  max(self.graphics_view.height() - 2, self._timeline._timeline_frame._history_bottom))
190 
191  def _handle_publish_clicked(self, checked):
192  self._timeline.set_publishing_state(checked)
193 
194  def _handle_play_clicked(self, checked):
195  if checked:
196  self.play_button.setIcon(self.pause_icon)
197  self._timeline.navigate_play()
198  else:
199  self.play_button.setIcon(self.play_icon)
200  self._timeline.navigate_stop()
201 
203  self._timeline.navigate_next()
204  self.play_button.setChecked(False)
205  self.play_button.setIcon(self.play_icon)
206 
208  self._timeline.navigate_previous()
209  self.play_button.setChecked(False)
210  self.play_button.setIcon(self.play_icon)
211 
213  self._timeline.navigate_fastforward()
214  self.play_button.setChecked(True)
215  self.play_button.setIcon(self.pause_icon)
216 
218  self._timeline.navigate_rewind()
219  self.play_button.setChecked(True)
220  self.play_button.setIcon(self.pause_icon)
221 
223  self._timeline.navigate_start()
224 
226  self._timeline.navigate_end()
227 
228  def _handle_thumbs_clicked(self, checked):
229  self._timeline._timeline_frame.toggle_renderers()
230 
232  self._timeline.reset_zoom()
233 
235  self._timeline.zoom_out()
236 
238  self._timeline.zoom_in()
239 
241  if self._recording:
242  self._timeline.toggle_recording()
243  return
244 
245  # TODO Implement limiting by regex and by number of messages per topic
246  self.topic_selection = TopicSelection()
247  self.topic_selection.recordSettingsSelected.connect(self._on_record_settings_selected)
248 
249  def _on_record_settings_selected(self, all_topics, selected_topics):
250  # TODO verify master is still running
251  filename = QFileDialog.getSaveFileName(
252  self, self.tr('Select prefix for new Bag File'), '.', self.tr('Bag files {.bag} (*.bag)'))
253  if filename[0] != '':
254  prefix = filename[0].strip()
255 
256  # Get filename to record to
257  record_filename = time.strftime('%Y-%m-%d-%H-%M-%S.bag', time.localtime(time.time()))
258  if prefix.endswith('.bag'):
259  prefix = prefix[:-len('.bag')]
260  if prefix:
261  record_filename = '%s_%s' % (prefix, record_filename)
262 
263  rospy.loginfo('Recording to %s.' % record_filename)
264 
265  self.load_button.setEnabled(False)
266  self._recording = True
267  self._timeline.record_bag(record_filename, all_topics, selected_topics)
268 
270  filenames = QFileDialog.getOpenFileNames(
271  self, self.tr('Load from Files'), '.', self.tr('Bag files {.bag} (*.bag)'))
272  for filename in filenames[0]:
273  self.load_bag(filename)
274 
275  def load_bag(self, filename):
276  qDebug("Loading '%s'..." % filename.encode(errors='replace'))
277 
278  # QProgressBar can EITHER: show text or show a bouncing loading bar,
279  # but apparently the text is hidden when the bounding loading bar is
280  # shown
281  # self.progress_bar.setRange(0, 0)
282  self.set_status_text.emit("Loading '%s'..." % filename)
283  # progress_format = self.progress_bar.format()
284  # progress_text_visible = self.progress_bar.isTextVisible()
285  # self.progress_bar.setFormat("Loading %s" % filename)
286  # self.progress_bar.setTextVisible(True)
287 
288  try:
289  bag = rosbag.Bag(filename)
290  self.play_button.setEnabled(True)
291  self.thumbs_button.setEnabled(True)
292  self.zoom_in_button.setEnabled(True)
293  self.zoom_out_button.setEnabled(True)
294  self.zoom_all_button.setEnabled(True)
295  self.next_button.setEnabled(True)
296  self.previous_button.setEnabled(True)
297  self.faster_button.setEnabled(True)
298  self.slower_button.setEnabled(True)
299  self.begin_button.setEnabled(True)
300  self.end_button.setEnabled(True)
301  self.save_button.setEnabled(True)
302  self.record_button.setEnabled(False)
303  self._timeline.add_bag(bag)
304  qDebug("Done loading '%s'" % filename.encode(errors='replace'))
305  # put the progress bar back the way it was
306  self.set_status_text.emit("")
307  except rosbag.ROSBagException as e:
308  qWarning("Loading '%s' failed due to: %s" % (filename.encode(errors='replace'), e))
309  self.set_status_text.emit("Loading '%s' failed due to: %s" % (filename, e))
310 
311  # self.progress_bar.setFormat(progress_format)
312  # self.progress_bar.setTextVisible(progress_text_visible) # causes a segfault :(
313  # self.progress_bar.setRange(0, 100)
314  # self clear loading filename
315 
317  filename = QFileDialog.getSaveFileName(
318  self, self.tr('Save selected region to file...'), '.', self.tr('Bag files {.bag} (*.bag)'))
319  if filename[0] != '':
320  self._timeline.copy_region_to_bag(filename[0])
321 
322  def _set_status_text(self, text):
323  if text:
324  self.progress_bar.setFormat(text)
325  self.progress_bar.setTextVisible(True)
326  else:
327  self.progress_bar.setTextVisible(False)
328 
330  if self._timeline._timeline_frame.playhead is None or self._timeline._timeline_frame.start_stamp is None:
331  return
332  # TODO Figure out why this function is causing a "RuntimeError: wrapped
333  # C/C++ object of %S has been deleted" on close if the playhead is moving
334  try:
335  # Background Process Status
336  self.progress_bar.setValue(self._timeline.background_progress)
337 
338  # Raw timestamp
339  self.stamp_label.setText('%.3fs' % self._timeline._timeline_frame.playhead.to_sec())
340 
341  # Human-readable time
342  self.date_label.setText(
343  bag_helper.stamp_to_str(self._timeline._timeline_frame.playhead))
344 
345  # Elapsed time (in seconds)
346  self.seconds_label.setText(
347  '%.3fs' % (
348  self._timeline._timeline_frame.playhead - self._timeline._timeline_frame.start_stamp).to_sec())
349 
350  # File size
351  self.filesize_label.setText(bag_helper.filesize_to_str(self._timeline.file_size()))
352 
353  # Play speed
354  spd = self._timeline.play_speed
355  if spd != 0.0:
356  if spd > 1.0:
357  spd_str = '>> %.0fx' % spd
358  elif spd == 1.0:
359  spd_str = '>'
360  elif spd > 0.0:
361  spd_str = '> 1/%.0fx' % (1.0 / spd)
362  elif spd > -1.0:
363  spd_str = '< 1/%.0fx' % (1.0 / -spd)
364  elif spd == 1.0:
365  spd_str = '<'
366  else:
367  spd_str = '<< %.0fx' % -spd
368  self.playspeed_label.setText(spd_str)
369  else:
370  self.playspeed_label.setText('')
371  except:
372  return
373  # Shutdown all members
374 
375  def shutdown_all(self):
376  self._timeline.handle_close()
def _handle_thumbs_clicked(self, checked)
Definition: bag_widget.py:228
def _handle_play_clicked(self, checked)
Definition: bag_widget.py:194
def handle_close(self, event)
Definition: bag_widget.py:177
def handle_destroy(self, args)
Definition: bag_widget.py:174
def __init__(self, parent=None)
Definition: bag_widget.py:52
def _resizeEvent(self, event)
Definition: bag_widget.py:182
def _on_record_settings_selected(self, all_topics, selected_topics)
Definition: bag_widget.py:249
def graphics_view_on_key_press(self, event)
Definition: bag_widget.py:142
def __init__(self, context, publish_clock)
Definition: bag_widget.py:65
def _handle_publish_clicked(self, checked)
Definition: bag_widget.py:191
def _set_status_text(self, text)
Definition: bag_widget.py:322
def on_key_press(self, event)
Definition: bag_widget.py:153
def load_bag(self, filename)
Definition: bag_widget.py:275


rqt_bag
Author(s): Aaron Blasdel, Tim Field
autogenerated on Fri Jun 7 2019 22:05:54