class MorseChallenger(QMainWindow): def __init__(self): super(MorseChallenger,self).__init__(); self.PIXELS_TO_MOVE_PER_TIMEOUT = 3 self.ABSOLUTE_MAX_NUM_FLOATERS = 10; self.FLOATER_POINT_SIZE = 20; self.LETTER_CHECKBOX_NUM_COLS = 6; # Number of timeout cycles that detonated # letters stay visible: self.DETONATION_VISIBLE_CYCLES = 4; self.maxNumFloaters = 1; self.lettersToUse = set(); self.floatersAvailable = set(); self.floatersInUse = set(); self.bookeepingAccessLock = threading.Lock(); # Floaters that detonated. Values are number of timeouts # the detonation was visible: self.floatersDetonated = {}; # Used to launch new floaters at random times: self.cyclesSinceLastLaunch = 0; self.dialogService = DialogService(); self.optionsFilePath = os.path.join(os.getenv('HOME'), '.morser/morseChallenger.cfg'); # Load UI for Morse Challenger: relPathQtCreatorFileMainWin = "qt_files/morseChallenger/morseChallenger.ui"; qtCreatorXMLFilePath = self.findFile(relPathQtCreatorFileMainWin); if qtCreatorXMLFilePath is None: raise ValueError("Can't find QtCreator user interface file %s" % relPathQtCreatorFileMainWin); # Make QtCreator generated UI a child of this instance: loadUi(qtCreatorXMLFilePath, self); self.windowTitle = "Morse Challenger"; self.setWindowTitle(self.windowTitle); self.iconDir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'icons') self.explosionPixMap = QPixmap(os.path.join(self.iconDir, 'explosion.png')); CommChannel.registerSignals(MorseChallengerSignals); self.connectWidgets(); self.generateLetterCheckBoxes(); self.simultaneousLettersComboBox.addItems(['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']); self.generateFloaters(); self.setFocusPolicy(Qt.ClickFocus); self.letterMoveTimer = QTimer(); self.letterMoveTimer.setInterval(self.timerIntervalFromSpeedSlider()); # milliseconds self.letterMoveTimer.setSingleShot(False); self.letterMoveTimer.timeout.connect(self.moveLetters); # Bring options from config file: self.setOptions(); # Styling: self.createColors(); self.setStyleSheet("QWidget{background-color: %s}" % self.lightBlueColor.name()); self.show(); def connectWidgets(self): self.speedHSlider.valueChanged.connect(self.speedChangedAction); self.startPushButton.clicked.connect(self.startAction); self.stopPushButton.clicked.connect(self.stopAction); self.simultaneousLettersComboBox.currentIndexChanged.connect(self.maxNumSimultaneousFloatersAction); CommChannel.getSignal('MorseChallengerSignals.focusLostInadvertently').connect(self.reassertWindowFocus); def createColors(self): self.grayBlueColor = QColor(89,120,137); # Letter buttons self.lightBlueColor = QColor(206,230,243); # Background self.darkGray = QColor(65,88,101); # Central buttons self.wordListFontColor = QColor(62,143,185); # Darkish blue. self.purple = QColor(147,124,195); # Gesture button pressed def generateLetterCheckBoxes(self): self.lettersToCheckBoxes = {}; self.checkBoxesToLetters = {}; letters = sorted(codeKey.values()); numRows = (len(letters) % self.LETTER_CHECKBOX_NUM_COLS) + 1; col = 0; row = 0; letterIndex = 0; while(True): letter = letters[letterIndex]; checkbox = QCheckBox(); self.lettersToCheckBoxes[letter] = checkbox; self.checkBoxesToLetters[checkbox] = letter; checkbox.setText(str(letter)); checkbox.toggled.connect(partial(self.letterCheckAction, checkbox)); self.letterCheckboxGridLayout.addWidget(checkbox, row, col); col += 1; if (col >= self.LETTER_CHECKBOX_NUM_COLS): col = 0; row += 1; letterIndex += 1; if letterIndex >= len(letters): break; def generateFloaters(self): for i in range(self.ABSOLUTE_MAX_NUM_FLOATERS): floaterLabel = QLabel(); font = QtGui.QFont() font.setFamily("Courier") font.setFixedPitch(True) font.setPointSize(self.FLOATER_POINT_SIZE); floaterLabel.setFont(font); self.floatersAvailable.add(floaterLabel); def letterCheckAction(self, checkbox, checkedOrNot): changedLetter = self.checkBoxesToLetters[checkbox]; configedLetters = self.cfgParser.get('Main', 'letters'); if checkedOrNot: self.lettersToUse.add(self.checkBoxesToLetters[checkbox]); configedLetters += changedLetter; else: self.lettersToUse.discard(self.checkBoxesToLetters[checkbox]); configedLetters.replace(changedLetter, ""); self.cfgParser.set('Main', 'letters', configedLetters); self.saveOptions(); def startAction(self): self.launchFloaters(1); self.letterMoveTimer.start(); def stopAction(self): self.letterMoveTimer.stop(); floatersInUseCopy = self.floatersInUse.copy(); for floater in floatersInUseCopy: self.decommissionFloater(floater); def maxNumSimultaneousFloatersAction(self, newNum): # New Combo box picked: 0-based: self.maxNumFloaters = newNum + 1; try: self.cfgParser.set('Main','numLettersTogether', str(newNum)); self.saveOptions(); except AttributeError: # At startup cfgParser won't exist yet. Ignore: pass def speedChangedAction(self, newSpeed): self.letterMoveTimer.setInterval(self.timerIntervalFromSpeedSlider(newSpeed)); # msec. self.cfgParser.set('Main','letterSpeed', str(newSpeed)); self.saveOptions(); def keyPressEvent(self, keyEvent): letter = keyEvent.text(); self.letterLineEdit.setText(letter); matchingFloaters = {}; # Find all active floaters that have the pressed key's letter: for floater in self.floatersInUse: if floater.text() == letter: floaterY = floater.y(); try: matchingFloaters[floaterY].append(floater); except KeyError: matchingFloaters[floaterY] = [floater]; if len(matchingFloaters) == 0: self.activateWindow(); self.setFocus(); return; # Find the lowest ones: lowestY = self.projectionScreenWidget.y(); yPositions = matchingFloaters.keys(); for floater in matchingFloaters[max(yPositions)]: self.decommissionFloater(floater); self.activateWindow(); self.setFocus(); def focusInEvent(self, event): ''' Ensure that floaters are always on top of the app window, even if user changes speed slider, checkmarks, etc.: @param event: @type event: ''' self.raiseAllFloaters(); def resizeEvent(self, event): newWinRect = self.geometry(); self.cfgParser.set('Appearance', 'winGeometry', str(newWinRect.x()) + ',' + str(newWinRect.y()) + ',' + str(newWinRect.width()) + ',' + str(newWinRect.height())); self.saveOptions(); def raiseAllFloaters(self): for floater in self.floatersInUse: floater.raise_(); for floater in self.floatersDetonated.keys(): floater.raise_(); def decommissionFloater(self, floaterLabel): floaterLabel.setHidden(True); # Just in case: protect removal, in case caller # passes in an already decommissioned floater: try: self.floatersInUse.remove(floaterLabel); except KeyError: pass # Adding a floater twice is not an issue, # b/c floatersAvailable is a set: self.floatersAvailable.add(floaterLabel); def timerIntervalFromSpeedSlider(self, newSpeed=None): if newSpeed is None: newSpeed = self.speedHSlider.value(); return max(500 - newSpeed, 20); #msec def randomLetters(self, numLetters): if len(self.lettersToUse) == 0: self.dialogService.showErrorMsg("You must turn on a checkmark for at least one letter."); return None; lettersToDeploy = []; for i in range(numLetters): letter = random.sample(self.lettersToUse, 1); lettersToDeploy.extend(letter); return lettersToDeploy; def moveLetters(self): self.cyclesSinceLastLaunch += 1; # Did floater detonate during previous timeout?: detonatedFloaters = self.floatersDetonated.keys(); for detonatedFloater in detonatedFloaters: self.floatersDetonated[detonatedFloater] += 1; if self.floatersDetonated[detonatedFloater] > self.DETONATION_VISIBLE_CYCLES: self.decommissionFloater(detonatedFloater); del self.floatersDetonated[detonatedFloater]; remainingFloaters = self.floatersDetonated.keys(); for floaterLabel in self.floatersInUse: if floaterLabel in remainingFloaters: continue; geo = floaterLabel.geometry(); newY = geo.y() + self.PIXELS_TO_MOVE_PER_TIMEOUT; savedHeight = geo.height(); if newY > self.height(): newY = self.height(); #******** self.detonate(floaterLabel); geo.setY(newY) geo.setHeight(savedHeight); floaterLabel.setGeometry(geo); # Done advancing each floater. Is it time to start a new floater? numFloatersToLaunch = self.maxNumFloaters - len(self.floatersInUse) + len(self.floatersDetonated); if numFloatersToLaunch > 0: # Use the following commented lines if you want the launching of # new floaters to be random, e.g. between 2 and 10 timeout intervals: # if self.cyclesSinceLastLaunch > random.randint(2,10): # self.launchFloaters(self.maxNumFloaters - len(self.floatersInUse)); self.launchFloaters(numFloatersToLaunch); # Launching floaters deactivates the main window, thereby losing the keyboard focus. # We can't seem to reassert that focus within this timer interrupt service routine. # So, issue a signal that will do it after return: CommChannel.getSignal('MorseChallengerSignals.focusLostInadvertently').emit(); def launchFloaters(self, numFloaters): newLetters = self.randomLetters(numFloaters); if newLetters is None: return; for letter in newLetters: # Pick a random horizontal location, at least 3 pixels in # from the left edge, and at most 3 pixels back from the right edge: screenGeo = self.projectionScreenWidget.geometry(); appWinGeo = self.geometry(); appWinX = appWinGeo.x(); # Random number among all the application window's X coordinates: xLoc = random.randint(appWinX + 3, appWinX + screenGeo.width() - 3); yLoc = appWinGeo.y(); self.getFloaterLabel(letter, xLoc, yLoc); self.cyclesSinceLastLaunch = 0; def getFloaterLabel(self, letter, x, y): try: label = self.floatersAvailable.pop(); label.clear(); except KeyError: return None; self.floatersInUse.add(label); label.setText(letter); label.move(x,y); label.setHidden(False); def detonate(self, floaterLabel): # Floater was detonated zero cycles ago: self.floatersDetonated[floaterLabel] = 0; floaterLabel.clear(); floaterLabel.setPixmap(self.explosionPixMap); def findFile(self, path, matchFunc=os.path.isfile): if path is None: return None for dirname in sys.path: candidate = os.path.join(dirname, path) if matchFunc(candidate): return candidate return None; def reassertWindowFocus(self): self.window().activateWindow(); self.raise_(); self.raiseAllFloaters(); def setOptions(self): self.optionsDefaultDict = { 'letters' : "", 'letterSpeed' : str(10), 'numLettersTogether' : str(0), 'winGeometry' : '100,100,700,560', } self.cfgParser = ConfigParser.SafeConfigParser(self.optionsDefaultDict); self.cfgParser.add_section('Main'); self.cfgParser.add_section('Appearance'); self.cfgParser.read(self.optionsFilePath); mainWinGeometry = self.cfgParser.get('Appearance', 'winGeometry'); # Get four ints from the comma-separated string of upperLeftX, upperLeftY, # Width,Height numbers: try: nums = mainWinGeometry.split(','); self.setGeometry(QRect(int(nums[0].strip()),int(nums[1].strip()),int(nums[2].strip()),int(nums[3].strip()))); except Exception as e: self.dialogService.showErrorMsg("Could not set window size; config file spec not grammatical: %s. (%s" % (mainWinGeometry, `e`)); letterSpeed = self.cfgParser.getint('Main', 'letterSpeed'); self.speedHSlider.setValue(letterSpeed); #self.speedChangedAction(letterSpeed); self.letterMoveTimer.setInterval(self.timerIntervalFromSpeedSlider()); # milliseconds numLettersTogether = self.cfgParser.getint('Main', 'numLettersTogether'); self.simultaneousLettersComboBox.setCurrentIndex(numLettersTogether); lettersToUse = self.cfgParser.get('Main', 'letters'); for letter in lettersToUse: self.lettersToCheckBoxes[letter].setChecked(True); def saveOptions(self): try: # Does the config dir already exist? If not # create it: optionsDir = os.path.dirname(self.optionsFilePath); if not os.path.isdir(optionsDir): os.makedirs(optionsDir, 0777); with open(self.optionsFilePath, 'wb') as outFd: self.cfgParser.write(outFd); except IOError as e: self.dialogService.showErrorMsg("Could not save options: %s" % `e`); def exit(self): QApplication.quit(); def closeEvent(self, event): QApplication.quit(); # Bubble event up: event.ignore();
class Foo(QObject): current_values_changed = Signal(str) current_duration_changed = Signal(float) _update_current_value_signal = Signal() _show_input_notification_signal = Signal() _hide_input_notification_signal = Signal() _show_scene_notification_signal = Signal() _hide_scene_notification_signal = Signal() _update_color_of_line_edit_signal = Signal() def __init__(self): super(Foo, self).__init__() self._action_set = None self._update_current_value_signal.connect(self._update_current_value) self._scene_notification = QDialog(main_window) ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'src', 'wrong_scene_dialog.ui') loadUi(ui_file, self._scene_notification) self._scene_notification_timer = QTimer(main_window) self._scene_notification_timer.setInterval(5000) self._scene_notification_timer.setSingleShot(True) self._scene_notification_timer.timeout.connect( self._hide_scene_notification) self._scene_notification.finished.connect( self._hide_scene_notification) self._input_notification = QDialog(main_window) ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'src', 'wrong_input_method_dialog.ui') loadUi(ui_file, self._input_notification) self._input_notification_timer = QTimer(main_window) self._input_notification_timer.setInterval(5000) self._input_notification_timer.setSingleShot(True) self._input_notification_timer.timeout.connect( self._hide_input_method_notification) self._input_notification.finished.connect( self._hide_input_method_notification) self._show_input_notification_signal.connect( self._show_input_notification) self._hide_input_notification_signal.connect( self._hide_input_method_notification) self._show_input_notification_signal.connect( self._show_scene_notification) self._hide_input_notification_signal.connect( self._hide_scene_notification) self._update_color_of_line_edit_signal.connect( self._update_color_of_line_edit) def _hide_scene_notification(self, result=None): self._scene_notification_timer.stop() self._scene_notification.hide() def _hide_input_method_notification(self, result=None): self._input_notification_timer.stop() self._input_notification.hide() # this method is call by ROS, all UI interaction is delayed via signals def update_current_value(self): global check_collisions global currently_in_collision if self._action_set is not None: self._action_set.stop() self._action_set = kontrol_subscriber.get_action_set() if self._action_set is not None: self.current_duration_changed.emit(self._action_set.get_duration()) if not is_in_slider_mode(): currently_in_collision = False self._show_input_notification_signal.emit() return if self._input_notification_timer.isActive(): self._hide_input_notification_signal.emit() #print('update_current_value()') if not kontrol_subscriber.is_valid_action_set(): self._show_scene_notification_signal.emit() return if self._scene_notification_timer.isActive(): self._hide_scene_notification_signal.emit() joint_values = kontrol_subscriber.get_joint_values() if check_collisions: in_collision = collision_checker.is_in_collision(joint_values) else: in_collision = False collision_toggled = (currently_in_collision != in_collision) currently_in_collision = in_collision if collision_toggled: self._update_color_of_line_edit_signal.emit() self._update_current_value_signal.emit() def _show_input_notification(self): # open dialog which closes after some s_hide_scene_notification_hide_scene_notificationeconds or when confirmed manually self._input_notification.show() self._input_notification_timer.start() def _show_scene_notification(self): # open dialog which closes after some seconds or when confirmed manually self._scene_notification.show() self._scene_notification_timer.start() def _update_color_of_line_edit(self): global currently_in_collision palette = main_window.lineEdit.palette() if currently_in_collision: palette.setColor(QPalette.Text, QColor(255, 0, 0)) else: palette.setColor(QPalette.Text, default_color) main_window.lineEdit.setPalette(palette) def _update_current_value(self): global currently_in_collision main_window.append_pushButton.setEnabled(not currently_in_collision) main_window.insert_before_pushButton.setEnabled( not currently_in_collision) value = self._action_set.to_string() self.current_values_changed.emit(value) for action in self._action_set._actions: action._duration = 0.1 if use_sim_time else 0.5 self._action_set.execute()
class Foo(QObject): current_values_changed = Signal(str) current_duration_changed = Signal(float) _update_current_value_signal = Signal() _show_input_notification_signal = Signal() _hide_input_notification_signal = Signal() _show_scene_notification_signal = Signal() _hide_scene_notification_signal = Signal() _update_color_of_line_edit_signal = Signal() def __init__(self): super(Foo, self).__init__() self._action_set = None self._update_current_value_signal.connect(self._update_current_value) self._scene_notification = QDialog(main_window) ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'src', 'wrong_scene_dialog.ui') loadUi(ui_file, self._scene_notification) self._scene_notification_timer = QTimer(main_window) self._scene_notification_timer.setInterval(5000) self._scene_notification_timer.setSingleShot(True) self._scene_notification_timer.timeout.connect(self._hide_scene_notification) self._scene_notification.finished.connect(self._hide_scene_notification) self._input_notification = QDialog(main_window) ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'src', 'wrong_input_method_dialog.ui') loadUi(ui_file, self._input_notification) self._input_notification_timer = QTimer(main_window) self._input_notification_timer.setInterval(5000) self._input_notification_timer.setSingleShot(True) self._input_notification_timer.timeout.connect(self._hide_input_method_notification) self._input_notification.finished.connect(self._hide_input_method_notification) self._show_input_notification_signal.connect(self._show_input_notification) self._hide_input_notification_signal.connect(self._hide_input_method_notification) self._show_input_notification_signal.connect(self._show_scene_notification) self._hide_input_notification_signal.connect(self._hide_scene_notification) self._update_color_of_line_edit_signal.connect(self._update_color_of_line_edit) def _hide_scene_notification(self, result=None): self._scene_notification_timer.stop() self._scene_notification.hide() def _hide_input_method_notification(self, result=None): self._input_notification_timer.stop() self._input_notification.hide() # this method is call by ROS, all UI interaction is delayed via signals def update_current_value(self): global check_collisions global currently_in_collision if self._action_set is not None: self._action_set.stop() self._action_set = kontrol_subscriber.get_action_set() if self._action_set is not None: self.current_duration_changed.emit(self._action_set.get_duration()) if not is_in_slider_mode(): currently_in_collision = False self._show_input_notification_signal.emit() return if self._input_notification_timer.isActive(): self._hide_input_notification_signal.emit() #print('update_current_value()') if not kontrol_subscriber.is_valid_action_set(): self._show_scene_notification_signal.emit() return if self._scene_notification_timer.isActive(): self._hide_scene_notification_signal.emit() joint_values = kontrol_subscriber.get_joint_values() if check_collisions: in_collision = collision_checker.is_in_collision(joint_values) else: in_collision = False collision_toggled = (currently_in_collision != in_collision) currently_in_collision = in_collision if collision_toggled: self._update_color_of_line_edit_signal.emit() self._update_current_value_signal.emit() def _show_input_notification(self): # open dialog which closes after some s_hide_scene_notification_hide_scene_notificationeconds or when confirmed manually self._input_notification.show() self._input_notification_timer.start() def _show_scene_notification(self): # open dialog which closes after some seconds or when confirmed manually self._scene_notification.show() self._scene_notification_timer.start() def _update_color_of_line_edit(self): global currently_in_collision palette = main_window.lineEdit.palette() if currently_in_collision: palette.setColor(QPalette.Text, QColor(255, 0, 0)) else: palette.setColor(QPalette.Text, default_color) main_window.lineEdit.setPalette(palette) def _update_current_value(self): global currently_in_collision main_window.append_pushButton.setEnabled(not currently_in_collision) main_window.insert_before_pushButton.setEnabled(not currently_in_collision) value = self._action_set.to_string() self.current_values_changed.emit(value) for action in self._action_set._actions: action._duration = 0.1 if use_sim_time else 0.5 self._action_set.execute()
class MonitorDashWidget(IconToolButton): """ A widget which brings up the rqt_robot_monitor. :param context: The plugin context to create the monitor in. :type context: qt_gui.plugin_context.PluginContext """ def __init__(self, context, icon_paths=[]): self._graveyard = [] ok_icon = ['bg-green.svg', 'ic-diagnostics.svg'] warn_icon = ['bg-yellow.svg', 'ic-diagnostics.svg', 'ol-warn-badge.svg'] err_icon = ['bg-red.svg', 'ic-diagnostics.svg', 'ol-err-badge.svg'] stale_icon = ['bg-grey.svg', 'ic-diagnostics.svg', 'ol-stale-badge.svg'] icons = [ok_icon, warn_icon, err_icon, stale_icon] super(MonitorDashWidget, self).__init__('MonitorWidget', icons, icon_paths=icon_paths) self.setFixedSize(self._icons[0].actualSize(QSize(50, 30))) self._monitor = None self._close_mutex = QMutex() self._show_mutex = QMutex() self._last_update = rospy.Time.now() self.context = context self.clicked.connect(self._show_monitor) self._monitor_shown = False self.setToolTip('Diagnostics') self._diagnostics_toplevel_state_sub = rospy.Subscriber('diagnostics_toplevel_state', DiagnosticStatus, self.toplevel_state_callback) self._top_level_state = -1 self._stall_timer = QTimer() self._stall_timer.timeout.connect(self._stalled) self._stalled() self._plugin_settings = None self._instance_settings = None def toplevel_state_callback(self, msg): self._is_stale = False self._stall_timer.start(5000) if self._top_level_state != msg.level: if (msg.level >= 2): self.update_state(2) self.setToolTip("Diagnostics: Error") elif (msg.level == 1): self.update_state(1) self.setToolTip("Diagnostics: Warning") else: self.update_state(0) self.setToolTip("Diagnostics: OK") self._top_level_state = msg.level def _stalled(self): self._stall_timer.stop() self._is_stale = True self.update_state(3) self.setToolTip("Diagnostics: Stale\nNo message received on dashboard_agg in the last 5 seconds") def _show_monitor(self): with QMutexLocker(self._show_mutex): try: if self._monitor_shown: self.context.remove_widget(self._monitor) self._monitor_close() self._monitor_shown = False else: self._monitor = RobotMonitorWidget(self.context, 'diagnostics_agg') if self._plugin_settings: self._monitor.restore_settings(self._plugin_settings, self._instance_settings) self.context.add_widget(self._monitor) self._monitor_shown = True except Exception: if self._monitor_shown == False: raise # TODO when closeEvents is available fix this hack (It ensures the button will toggle correctly) self._monitor_shown = False self._show_monitor() def _monitor_close(self): if self._monitor_shown: with QMutexLocker(self._close_mutex): if self._plugin_settings: self._monitor.save_settings(self._plugin_settings, self._instance_settings) self._monitor.shutdown() self._monitor.close() self._graveyard.append(self._monitor) self._monitor = None def shutdown_widget(self): if self._monitor: self._monitor.shutdown() self._diagnostics_toplevel_state_sub.unregister() def save_settings(self, plugin_settings, instance_settings): if self._monitor_shown: self._monitor.save_settings(self._plugin_settings, self._instance_settings) def restore_settings(self, plugin_settings, instance_settings): self._plugin_settings = plugin_settings self._instance_settings = instance_settings
class VideoPlayer(QWidget): """Enables playback functions and broadcasting of video stream to UI elements Subscribers are named individually because they require different images or data """ video = None videoFrame = None focalFrame = None corticalFrame = None focusFrame = None framePos = 0 maxFrames = 0 retina = None cortex = None fixation = None filetypes = {'mp4': 'mp4v', 'jpg': 'jpeg', 'avi': 'xvid'} def __init__(self, file, isRetinaEnabled, parent, webcammode=False): super(QWidget, self).__init__() self.parent = parent self.isVideo = False self.webcam = webcammode if file: self.file = file self.isVideo = isinstance(file, Video) self.timer = QTimer() self.timer.timeout.connect(self.nextFrame) self.frames = parent.currentFrames # set up video capture dependent on source if self.isVideo: self.cap = cv2.VideoCapture(self.file.filepath) codec = cv2.VideoWriter_fourcc(*self.filetypes[self.file.type]) self.cap.set(cv2.CAP_PROP_FOURCC, codec) self.maxFrames = self.cap.get(cv2.CAP_PROP_FRAME_COUNT) # if webcam mode, start immediately elif self.webcam: self.cap = cv2.VideoCapture(0) self.isBGR = True self.timer.start(1000.0 / 30) # if still images, no need for video capture else: self.maxFrames = len(self.frames) - 1 self.parent.scrubSlider.setRange(0, self.maxFrames) self.parent.scrubSlider_2.setRange(0, self.maxFrames) self.framePos = 0 self.videoFrame = parent.label self.focalFrame = parent.focallabel self.corticalFrame = parent.corticallabel self.focusFrame = parent.biglabel if isRetinaEnabled: self.retina, self.fixation = ip.prepareLiveRetina(self.cap) self.cortex = ip.createCortex() def nextFrame(self): """Retrieves next frame for display whether video or image""" if self.isVideo or self.webcam: ret, frame = self.cap.read() if ret: self.framePos = self.cap.get(cv2.CAP_PROP_POS_FRAMES) self.updateDisplay(frame) else: self.framePos += 1 if self.framePos < self.maxFrames else 0 self.setCurrent() def start(self): self.timer.start(1000.0 / 30) self.parent.startButton.setDisabled(True) self.parent.startButton_2.setDisabled(True) self.parent.pauseButton.setDisabled(False) self.parent.pauseButton_2.setDisabled(False) def pause(self): self.timer.stop() self.parent.pauseButton.setDisabled(True) self.parent.pauseButton_2.setDisabled(True) self.parent.startButton.setDisabled(False) self.parent.startButton_2.setDisabled(False) def setCurrent(self): """Sets the current frame based on user input from playback buttons""" if self.framePos <= self.maxFrames: if self.isVideo: self.cap.set(cv2.CAP_PROP_POS_FRAMES, self.framePos) else: self.currentframe = self.frames[self.framePos] self.updateDisplay(self.currentframe.image) def skip(self, framePos): self.framePos = framePos self.setCurrent() def skipBck(self): self.framePos = self.framePos - 1 if self.framePos > 0 else 0 self.setCurrent() def skipFwd(self): self.framePos = self.framePos + 1 if self.framePos < self.maxFrames else 0 self.setCurrent() def updateDisplay(self, frame): """Update all subscribed UI elements with images or data""" self.parent.scrubSlider.setValue(self.framePos) self.parent.scrubSlider_2.setValue(self.framePos) self.parent.frameNum.display(self.framePos) self.parent.frameNum_2.display(self.framePos) # update the metadata table if we're on the main tab if self.parent.maintabWidget.currentIndex() == 1: self.parent.displayMetaData(self.framePos) if not (self.isVideo or self.webcam): self.isBGR = self.currentframe.vectortype == 'BGR' self.videoFrame.setPixmap( ip.convertToPixmap(frame, 480, 360, self.isBGR)) # if retina is activated display live backprojection and cortical image if self.retina: v = self.retina.sample(frame, self.fixation) tight = self.retina.backproject_last() cortical = self.cortex.cort_img(v) self.focalFrame.setPixmap( ip.convertToPixmap(tight, 480, 360, self.isBGR)) self.corticalFrame.setPixmap( ip.convertToPixmap(cortical, 480, 360, self.isBGR)) self.focusFrame.setPixmap( ip.convertToPixmap(cortical, 1280, 720, self.isBGR)) else: self.focusFrame.setPixmap( ip.convertToPixmap(frame, 1280, 720, self.isBGR))
class MatPlotWidget(QWidget): """The actual Widget """ def __init__(self): super(MatPlotWidget, self).__init__() self.setObjectName("MatPlotWidget") ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "MatPlot.ui") loadUi(ui_file, self, {"MatDataPlot": MatDataPlot}) self.subscribe_topic_button.setEnabled(False) self._topic_completer = TopicCompleter(self.topic_edit) self.topic_edit.setCompleter(self._topic_completer) self._start_time = rospy.get_time() self._rosdata = {} # setup drag 'n drop self.data_plot.dropEvent = self.dropEvent self.data_plot.dragEnterEvent = self.dragEnterEvent # init and start update timer for plot self._update_plot_timer = QTimer(self) self._update_plot_timer.timeout.connect(self.update_plot) self._update_plot_timer.start(40) # connect combobox self.comboBox.currentIndexChanged.connect(self.on_combo_box_changed) # params (colors) self.texts = {} self.data_plot._colors = {} self.sub_topics = {} n = 0 for tag in rospy.get_param("/tags"): self.texts[tag] = rospy.get_param("/" + tag + "_text") color = [float(i) for i in list(rospy.get_param("/" + tag + "_color").split(","))] self.data_plot._colors[self.texts[tag]] = color if n != 0: # dont have a topic for the reference self.sub_topics[tag] = "/error_mags/" + rospy.get_param("/tags")[0] + "_ref/" + tag + "_tgt/mag_horiz" else: n += 1 # start with subscription to filtered self.add_topic("/error_mags/rtk_ref/flt_tgt/mag_horiz") def update_plot(self): for topic_name, rosdata in self._rosdata.items(): data_x, data_y = rosdata.next() self.data_plot.update_value(topic_name, data_x, data_y) self.data_plot.draw_plot() @Slot("QDragEnterEvent*") def dragEnterEvent(self, event): if not event.mimeData().hasText(): if not hasattr(event.source(), "selectedItems") or len(event.source().selectedItems()) == 0: qWarning( "MatPlot.dragEnterEvent(): not hasattr(event.source(), selectedItems) or len(event.source().selectedItems()) == 0" ) return item = event.source().selectedItems()[0] ros_topic_name = item.data(0, Qt.UserRole) if ros_topic_name == None: qWarning("MatPlot.dragEnterEvent(): not hasattr(item, ros_topic_name_)") return # get topic name if event.mimeData().hasText(): topic_name = str(event.mimeData().text()) else: droped_item = event.source().selectedItems()[0] topic_name = str(droped_item.data(0, Qt.UserRole)) # check for numeric field type is_numeric, message = is_slot_numeric(topic_name) if is_numeric: event.acceptProposedAction() else: qWarning('MatPlot.dragEnterEvent(): rejecting: "%s"' % (message)) @Slot("QDropEvent*") def dropEvent(self, event): if event.mimeData().hasText(): topic_name = str(event.mimeData().text()) else: droped_item = event.source().selectedItems()[0] topic_name = str(droped_item.data(0, Qt.UserRole)) self.add_topic(topic_name) @Slot(str) def on_topic_edit_textChanged(self, topic_name): # on empty topic name, update topics if topic_name in ("", "/"): self._topic_completer.update_topics() is_numeric, message = is_slot_numeric(topic_name) self.subscribe_topic_button.setEnabled(is_numeric) self.subscribe_topic_button.setToolTip(message) @Slot() def on_subscribe_topic_button_clicked(self): self.add_topic(str(self.topic_edit.text())) def add_topic(self, topic_name): if topic_name in self._rosdata: qWarning("MatPlot.add_topic(): topic already subscribed: %s" % topic_name) return self._rosdata[topic_name] = ROSData(topic_name, self._start_time) data_x, data_y = self._rosdata[topic_name].next() color = self.data_plot._colors[self.comboBox.currentText()] self.data_plot.add_curve(topic_name, data_x, data_y, color) @Slot() def on_clear_button_clicked(self): self.clean_up_subscribers() @Slot(bool) def on_pause_button_clicked(self, checked): if checked: self._update_plot_timer.stop() else: self._update_plot_timer.start(40) def clean_up_subscribers(self): for topic_name, rosdata in self._rosdata.items(): rosdata.close() self.data_plot.remove_curve(topic_name) self._rosdata = {} @Slot(str) def on_combo_box_changed(self, text): # print('In on_combo_box_changed') self.on_clear_button_clicked() self.data_plot.tgt_name = self.comboBox.currentText() tag = [tg for tg, txt in self.texts.iteritems() if txt == self.comboBox.currentText()][0] self.add_topic(self.sub_topics[tag]) window_title = " ".join( [ "Ground Plane Error Magnitude of Sensor:", self.comboBox.currentText(), "for Reference:", self.data_plot.ref_name, ] ) self.setWindowTitle(QApplication.translate("MatPlotWidget", window_title, None, QApplication.UnicodeUTF8))