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, QFileInfo, Qt, qWarning, Signal
41 from python_qt_binding.QtGui import QIcon, QResizeEvent
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  last_open_dir = os.getcwd()
64  set_status_text = Signal(str)
65 
66  def __init__(self, context, publish_clock):
67  """
68  :param context: plugin context hook to enable adding widgets as a ROS_GUI pane, ''PluginContext''
69  """
70  super(BagWidget, self).__init__()
71  rp = rospkg.RosPack()
72  ui_file = os.path.join(rp.get_path('rqt_bag'), 'resource', 'bag_widget.ui')
73  loadUi(ui_file, self, {'BagGraphicsView': BagGraphicsView})
74 
75  self.setObjectName('BagWidget')
76 
77  self._timeline = BagTimeline(context, publish_clock)
78  self.graphics_view.setScene(self._timeline)
79 
80  self.graphics_view.resizeEvent = self._resizeEvent
81  self.graphics_view.setMouseTracking(True)
82 
83  self.play_icon = QIcon.fromTheme('media-playback-start')
84  self.pause_icon = QIcon.fromTheme('media-playback-pause')
85  self.play_button.setIcon(self.play_icon)
86  self.begin_button.setIcon(QIcon.fromTheme('media-skip-backward'))
87  self.end_button.setIcon(QIcon.fromTheme('media-skip-forward'))
88  self.slower_button.setIcon(QIcon.fromTheme('media-seek-backward'))
89  self.faster_button.setIcon(QIcon.fromTheme('media-seek-forward'))
90  self.previous_button.setIcon(QIcon.fromTheme('go-previous'))
91  self.next_button.setIcon(QIcon.fromTheme('go-next'))
92  self.zoom_in_button.setIcon(QIcon.fromTheme('zoom-in'))
93  self.zoom_out_button.setIcon(QIcon.fromTheme('zoom-out'))
94  self.zoom_all_button.setIcon(QIcon.fromTheme('zoom-original'))
95  self.thumbs_button.setIcon(QIcon.fromTheme('insert-image'))
96  self.record_button.setIcon(QIcon.fromTheme('media-record'))
97  self.load_button.setIcon(QIcon.fromTheme('document-open'))
98  self.save_button.setIcon(QIcon.fromTheme('document-save'))
99 
100  self.play_button.clicked[bool].connect(self._handle_play_clicked)
101  self.thumbs_button.clicked[bool].connect(self._handle_thumbs_clicked)
102  self.zoom_in_button.clicked[bool].connect(self._handle_zoom_in_clicked)
103  self.zoom_out_button.clicked[bool].connect(self._handle_zoom_out_clicked)
104  self.zoom_all_button.clicked[bool].connect(self._handle_zoom_all_clicked)
105  self.previous_button.clicked[bool].connect(self._handle_previous_clicked)
106  self.next_button.clicked[bool].connect(self._handle_next_clicked)
107  self.faster_button.clicked[bool].connect(self._handle_faster_clicked)
108  self.slower_button.clicked[bool].connect(self._handle_slower_clicked)
109  self.begin_button.clicked[bool].connect(self._handle_begin_clicked)
110  self.end_button.clicked[bool].connect(self._handle_end_clicked)
111  self.record_button.clicked[bool].connect(self._handle_record_clicked)
112  self.load_button.clicked[bool].connect(self._handle_load_clicked)
113  self.save_button.clicked[bool].connect(self._handle_save_clicked)
114  self.graphics_view.mousePressEvent = self._timeline.on_mouse_down
115  self.graphics_view.mouseReleaseEvent = self._timeline.on_mouse_up
116  self.graphics_view.mouseMoveEvent = self._timeline.on_mouse_move
117  self.graphics_view.wheelEvent = self._timeline.on_mousewheel
120  # TODO when the closeEvent is properly called by ROS_GUI implement that
121  # event instead of destroyed
122  self.destroyed.connect(self.handle_destroy)
123 
124  self.graphics_view.keyPressEvent = self.graphics_view_on_key_press
125  self.play_button.setEnabled(False)
126  self.thumbs_button.setEnabled(False)
127  self.zoom_in_button.setEnabled(False)
128  self.zoom_out_button.setEnabled(False)
129  self.zoom_all_button.setEnabled(False)
130  self.previous_button.setEnabled(False)
131  self.next_button.setEnabled(False)
132  self.faster_button.setEnabled(False)
133  self.slower_button.setEnabled(False)
134  self.begin_button.setEnabled(False)
135  self.end_button.setEnabled(False)
136  self.save_button.setEnabled(False)
137 
138  self._recording = False
139 
140  self._timeline.status_bar_changed_signal.connect(self._update_status_bar)
141  self.set_status_text.connect(self._set_status_text)
142 
143  def graphics_view_on_key_press(self, event):
144  key = event.key()
145  if key in (Qt.Key_Left, Qt.Key_Right, Qt.Key_Up, Qt.Key_Down, Qt.Key_PageUp, Qt.Key_PageDown):
146  # This causes the graphics view to ignore these keys so they can be caught
147  # by the bag_widget keyPressEvent
148  event.ignore()
149  else:
150  # Maintains functionality for all other keys QGraphicsView implements
151  QGraphicsView.keyPressEvent(self.graphics_view, event)
152 
153  # callbacks for ui events
154  def on_key_press(self, event):
155  key = event.key()
156  if key == Qt.Key_Space:
157  self._timeline.toggle_play()
158  elif key == Qt.Key_Home:
159  self._timeline.navigate_start()
160  elif key == Qt.Key_End:
161  self._handle_end_clicked()
162  elif key == Qt.Key_Plus or key == Qt.Key_Equal:
164  elif key == Qt.Key_Minus:
166  elif key == Qt.Key_Left:
167  self._timeline.translate_timeline_left()
168  elif key == Qt.Key_Right:
169  self._timeline.translate_timeline_right()
170  elif key == Qt.Key_Up or key == Qt.Key_PageUp:
172  elif key == Qt.Key_Down or key == Qt.Key_PageDown:
174 
175  def handle_destroy(self, args):
176  self._timeline.handle_close()
177 
178  def handle_close(self, event):
179  self.shutdown_all()
180 
181  event.accept()
182 
183  def _resizeEvent(self, event):
184  # TODO The -2 allows a buffer zone to make sure the scroll bars do not
185  # appear when not needed. # On some systems (Lucid) this doesn't function
186  # properly # need to look at a method to determine the maximum size of the
187  # scene that # will maintain a proper no scrollbar fit in the view.
188  self.graphics_view.scene().setSceneRect(
189  0, 0, self.graphics_view.width() - 2,
190  max(self.graphics_view.height() - 2, self._timeline._timeline_frame._history_bottom))
191 
192  def _handle_publish_clicked(self, checked):
193  self._timeline.set_publishing_state(checked)
194 
195  def _handle_play_clicked(self, checked):
196  if checked:
197  self.play_button.setIcon(self.pause_icon)
198  self._timeline.navigate_play()
199  else:
200  self.play_button.setIcon(self.play_icon)
201  self._timeline.navigate_stop()
202 
204  self._timeline.navigate_next()
205  self.play_button.setChecked(False)
206  self.play_button.setIcon(self.play_icon)
207 
209  self._timeline.navigate_previous()
210  self.play_button.setChecked(False)
211  self.play_button.setIcon(self.play_icon)
212 
214  self._timeline.navigate_fastforward()
215  self.play_button.setChecked(True)
216  self.play_button.setIcon(self.pause_icon)
217 
219  self._timeline.navigate_rewind()
220  self.play_button.setChecked(True)
221  self.play_button.setIcon(self.pause_icon)
222 
224  self._timeline.navigate_start()
225 
227  self._timeline.navigate_end()
228 
229  def _handle_thumbs_clicked(self, checked):
230  self._timeline._timeline_frame.toggle_renderers()
231 
233  self._timeline.reset_zoom()
234 
236  self._timeline.zoom_out()
237 
239  self._timeline.zoom_in()
240 
242  if self._recording:
243  self._timeline.toggle_recording()
244  return
245 
246  # TODO Implement limiting by regex and by number of messages per topic
247  self.topic_selection = TopicSelection()
248  self.topic_selection.recordSettingsSelected.connect(self._on_record_settings_selected)
249 
250  def _on_record_settings_selected(self, all_topics, selected_topics):
251  # TODO verify master is still running
252 
253  # Get the bag name to record to, prepopulating with a file name based on the current date/time
254  proposed_filename = time.strftime('%Y-%m-%d-%H-%M-%S', time.localtime(time.time()))
255  filename = QFileDialog.getSaveFileName(
256  self, self.tr('Select name for new bag'), proposed_filename, self.tr('Bag files {.bag} (*.bag)'))[0]
257 
258  if filename != '':
259  filename = filename.strip()
260  if not filename.endswith('.bag'):
261  filename += ".bag"
262 
263  # Begin recording
264  self.load_button.setEnabled(False)
265  self._recording = True
266  self._timeline.record_bag(filename, all_topics, selected_topics)
267 
269  filenames = QFileDialog.getOpenFileNames(
270  self, self.tr('Load from Files'), self.last_open_dir, self.tr('Bag files {.bag} (*.bag)'))
271  if filenames and filenames[0]:
272  self.last_open_dir = QFileInfo(filenames[0][0]).absoluteDir().absolutePath()
273  for filename in filenames[0]:
274  self.load_bag(filename)
275 
276  # After loading bag(s), force a resize event on the bag widget so that
277  # it can take the new height of the timeline into account (and show
278  # the scroll bar if necessary)
279  self._timeline._timeline_frame._layout()
280  self._resizeEvent(QResizeEvent(self.size(), self.size()))
281 
282  def load_bag(self, filename):
283  qDebug("Loading '%s'..." % filename.encode(errors='replace'))
284 
285  # QProgressBar can EITHER: show text or show a bouncing loading bar,
286  # but apparently the text is hidden when the bounding loading bar is
287  # shown
288  # self.progress_bar.setRange(0, 0)
289  self.set_status_text.emit("Loading '%s'..." % filename)
290  # progress_format = self.progress_bar.format()
291  # progress_text_visible = self.progress_bar.isTextVisible()
292  # self.progress_bar.setFormat("Loading %s" % filename)
293  # self.progress_bar.setTextVisible(True)
294 
295  try:
296  bag = rosbag.Bag(filename)
297  self.play_button.setEnabled(True)
298  self.thumbs_button.setEnabled(True)
299  self.zoom_in_button.setEnabled(True)
300  self.zoom_out_button.setEnabled(True)
301  self.zoom_all_button.setEnabled(True)
302  self.next_button.setEnabled(True)
303  self.previous_button.setEnabled(True)
304  self.faster_button.setEnabled(True)
305  self.slower_button.setEnabled(True)
306  self.begin_button.setEnabled(True)
307  self.end_button.setEnabled(True)
308  self.save_button.setEnabled(True)
309  self.record_button.setEnabled(False)
310  self._timeline.add_bag(bag)
311  qDebug("Done loading '%s'" % filename.encode(errors='replace'))
312  # put the progress bar back the way it was
313  self.set_status_text.emit("")
314  # reset zoom to show entirety of all loaded bags
315  self._timeline.reset_zoom()
316  except rosbag.ROSBagException as e:
317  qWarning("Loading '%s' failed due to: %s" % (filename.encode(errors='replace'), e))
318  self.set_status_text.emit("Loading '%s' failed due to: %s" % (filename, e))
319 
320  # self.progress_bar.setFormat(progress_format)
321  # self.progress_bar.setTextVisible(progress_text_visible) # causes a segfault :(
322  # self.progress_bar.setRange(0, 100)
323  # self clear loading filename
324 
326  # Get the bag name to record to, prepopulating with a file name based on the current date/time
327  proposed_filename = time.strftime('%Y-%m-%d-%H-%M-%S', time.localtime(time.time()))
328  filename = QFileDialog.getSaveFileName(
329  self, self.tr('Save selected region...'), proposed_filename, self.tr('Bag files {.bag} (*.bag)'))[0]
330  if filename != '':
331  filename = filename.strip()
332  if not filename.endswith('.bag'):
333  filename += '.bag'
334 
335  # Copy the highlighted region
336  self._timeline.copy_region_to_bag(filename)
337 
338  def _set_status_text(self, text):
339  if text:
340  self.progress_bar.setFormat(text)
341  self.progress_bar.setTextVisible(True)
342  else:
343  self.progress_bar.setTextVisible(False)
344 
346  if self._timeline._timeline_frame.playhead is None or self._timeline._timeline_frame.start_stamp is None:
347  return
348  # TODO Figure out why this function is causing a "RuntimeError: wrapped
349  # C/C++ object of %S has been deleted" on close if the playhead is moving
350  try:
351  # Background Process Status
352  self.progress_bar.setValue(self._timeline.background_progress)
353 
354  # Raw timestamp
355  self.stamp_label.setText('%.9fs' % self._timeline._timeline_frame.playhead.to_sec())
356 
357  # Human-readable time
358  self.date_label.setText(
359  bag_helper.stamp_to_str(self._timeline._timeline_frame.playhead))
360 
361  # Elapsed time (in seconds)
362  self.seconds_label.setText(
363  '%.3fs' % (
364  self._timeline._timeline_frame.playhead - self._timeline._timeline_frame.start_stamp).to_sec())
365 
366  # File size
367  self.filesize_label.setText(bag_helper.filesize_to_str(self._timeline.file_size()))
368 
369  # Play speed
370  spd = self._timeline.play_speed
371  if spd != 0.0:
372  if spd > 1.0:
373  spd_str = '>> %.0fx' % spd
374  elif spd == 1.0:
375  spd_str = '>'
376  elif spd > 0.0:
377  spd_str = '> 1/%.0fx' % (1.0 / spd)
378  elif spd > -1.0:
379  spd_str = '< 1/%.0fx' % (1.0 / -spd)
380  elif spd == 1.0:
381  spd_str = '<'
382  else:
383  spd_str = '<< %.0fx' % -spd
384  self.playspeed_label.setText(spd_str)
385  else:
386  self.playspeed_label.setText('')
387  except:
388  return
389  # Shutdown all members
390 
391  def shutdown_all(self):
392  self._timeline.handle_close()
def _handle_thumbs_clicked(self, checked)
Definition: bag_widget.py:229
def _handle_play_clicked(self, checked)
Definition: bag_widget.py:195
def handle_close(self, event)
Definition: bag_widget.py:178
def handle_destroy(self, args)
Definition: bag_widget.py:175
def __init__(self, parent=None)
Definition: bag_widget.py:52
def _resizeEvent(self, event)
Definition: bag_widget.py:183
def _on_record_settings_selected(self, all_topics, selected_topics)
Definition: bag_widget.py:250
def graphics_view_on_key_press(self, event)
Definition: bag_widget.py:143
def __init__(self, context, publish_clock)
Definition: bag_widget.py:66
def _handle_publish_clicked(self, checked)
Definition: bag_widget.py:192
def _set_status_text(self, text)
Definition: bag_widget.py:338
def on_key_press(self, event)
Definition: bag_widget.py:154
def load_bag(self, filename)
Definition: bag_widget.py:282


rqt_bag
Author(s): Dirk Thomas , Aaron Blasdel , Austin Hendrix , Tim Field
autogenerated on Fri Feb 19 2021 03:14:14