class MainWindow(QtGui.QMainWindow): deletePressed = pyqtSignal(bool) quadrotorStateChanged = pyqtSignal(object) motorSpeedChanged = pyqtSignal(object) quadrotorStateReseted = pyqtSignal(bool) SCALE_FACTOR = 100 def __init__(self): """ Frame of GUI ========================= |_MenuBar_______________| | | | |plot| graph1 | |list|------------------| |----| | |data| graph2 | |list| | ========================= """ super(MainWindow, self).__init__() self.log_data = None self.log_file_name = None self.data_dict = None self.main_widget = QtGui.QWidget(self) self.mainlayout = QtGui.QHBoxLayout() self.main_widget.setLayout(self.mainlayout) # ToolBar self.toolbar = self.addToolBar('FileManager') loadfile_action = QtGui.QAction( QtGui.QIcon(get_source_name('icons/open.gif')), 'Open log file', self) loadfile_action.setShortcut('Ctrl+O') loadfile_action.triggered.connect(self.callback_open_log_file) self.toolbar.addAction(loadfile_action) self.show_quadrotor_3d = QtGui.QAction( QtGui.QIcon(get_source_name('icons/quadrotor.gif')), 'show 3d viewer', self) self.show_quadrotor_3d.setShortcut('Ctrl+Shift+Q') self.show_quadrotor_3d.triggered.connect(self.callback_show_quadrotor) self.toolbar.addAction(self.show_quadrotor_3d) # Left plot item widget self.plot_data_frame = QtGui.QFrame(self) self.plot_data_frame.setFrameShape(QtGui.QFrame.StyledPanel) self.plot_data_layout = QtGui.QVBoxLayout(self.plot_data_frame) ## Data Plotting self.data_plotting = [] ### There exist a Default graph self.line_ID = 0 lbl_ploting_data = QtGui.QLabel('Data Plotting') self.plotting_data_tableView = TableView(self.plot_data_frame) self.plotting_data_tableView.setEditTriggers( QtGui.QAbstractItemView.DoubleClicked | QtGui.QAbstractItemView.SelectedClicked) self.plotting_data_tableView.setSortingEnabled(False) self.plotting_data_tableView.horizontalHeader().setStretchLastSection( True) self.plotting_data_tableView.resizeColumnsToContents() self.plotting_data_tableView.setColumnCount(3) self.plotting_data_tableView.setHorizontalHeaderLabels( ['Label', 'Color', 'Visible']) self.id = 0 lbl_ploting_data.setBuddy(self.plotting_data_tableView) self.plot_data_layout.addWidget(lbl_ploting_data) self.plot_data_layout.addWidget(self.plotting_data_tableView) edit_layout = QtGui.QHBoxLayout() self.delete_btn = QtGui.QPushButton('Delete') self.delete_btn.clicked.connect(self.callback_del_plotting_data) self.clear_btn = QtGui.QPushButton('Clear') self.clear_btn.clicked.connect(self.callback_clear_plotting_data) edit_layout.addWidget(self.delete_btn) edit_layout.addWidget(self.clear_btn) self.plot_data_layout.addLayout(edit_layout) ## Data in the log file self.list_data_frame = QtGui.QFrame(self) self.list_data_frame.setMinimumWidth(300) self.list_data_frame.setMaximumWidth(600) self.list_data_frame.resize(200, 500) self.list_data_frame.setFrameShape(QtGui.QFrame.StyledPanel) self.list_data_layout = QtGui.QVBoxLayout(self.list_data_frame) ### line to search item self.choose_item_lineEdit = QtGui.QLineEdit(self.list_data_frame) self.choose_item_lineEdit.setPlaceholderText('filter by data name') self.choose_item_lineEdit.textChanged.connect(self.callback_filter) ### tree to show data to plot self.item_list_treeWidget = QtGui.QTreeWidget(self.list_data_frame) self.item_list_treeWidget.clear() self.item_list_treeWidget.setColumnCount(3) self.item_list_treeWidget.setHeaderLabels( ['Flight Data', 'Type', 'Length']) self.item_list_treeWidget.itemDoubleClicked.connect( self.callback_tree_double_clicked) self.item_list_treeWidget.resizeColumnToContents(2) self.list_data_layout.addWidget(self.choose_item_lineEdit) self.list_data_layout.addWidget(self.item_list_treeWidget) # Right plot item self.graph_frame = QtGui.QFrame(self) self.graph_frame.setFrameShape(QtGui.QFrame.StyledPanel) self.animation_layout = QtGui.QVBoxLayout(self.graph_frame) ## quadrotor 3d self.quadrotor_win = QuadrotorWin(self) self.quadrotor_win.closed.connect(self.quadrotor_win_closed_event) self.quadrotor_win.hide() self.first_load = True self.quadrotor_widget_isshow = False ## default plot self.default_graph_widget = pg.GraphicsLayoutWidget() ### a hidable ROI region self.detail_graph = self.default_graph_widget.addPlot(row=0, col=0) self.detail_graph.setAutoVisible(True) self.detail_graph.hide() ### main graph to plot curves self.main_graph = self.default_graph_widget.addPlot(row=1, col=0) self.main_graph.keyPressEvent = self.keyPressed self.deletePressed.connect(self.callback_del_plotting_data) self.main_graph.scene().sigMouseClicked.connect( self.callback_graph_clicked) self.main_graph.addLegend() ROI_action = QtGui.QAction('show/hide ROI graph', self.main_graph) ROI_action.triggered.connect(self.callback_ROI_triggered) self.main_graph.scene().contextMenu.append(ROI_action) self.ROI_region = pg.LinearRegionItem() self.ROI_region.setZValue(10) self.ROI_region.hide() self.ROI_showed = False def update(): self.ROI_region.setZValue(10) minX, maxX = self.ROI_region.getRegion() self.detail_graph.setXRange(minX, maxX, padding=0) self.ROI_region.sigRegionChanged.connect(update) def updateRegion(window, viewRange): rgn = viewRange[0] self.ROI_region.setRegion(rgn) self.detail_graph.sigRangeChanged.connect(updateRegion) self.main_graph.addItem(self.ROI_region, ignoreBounds=True) ## vertical line self.vLine = pg.InfiniteLine(angle=90, movable=False) self.vLine.hide() self.main_graph.addItem(self.vLine, ignoreBounds=True) self.vLine_detail = pg.InfiniteLine(angle=90, movable=False) self.vLine_detail.hide() self.detail_graph.addItem(self.vLine_detail, ignoreBounds=True) ## flag whether there is a curve clicked after last clicked event self.curve_clicked = False self.curve_highlighted = [] self.animation_layout.addWidget(self.default_graph_widget) ## time line self.time_line_frame = QtGui.QFrame(self) self.time_line_frame.setMaximumHeight(45) self.time_line_frame.setMinimumHeight(45) self.time_line_layout = QtGui.QHBoxLayout(self.time_line_frame) time_line_lbl = QtGui.QLabel('x') time_line_lbl.setToolTip('set play speed') speed_combo = QtGui.QComboBox() speed_combo.addItems(['1', '2', '4', '8']) self.speed_factor = 500 self.time_line_layout.addWidget(time_line_lbl) self.time_line_layout.addWidget(speed_combo) speed_combo.currentIndexChanged.connect( self.callback_speed_combo_indexChanged) self.current_factor = 500 / 1 self.time_line_button_play = QtGui.QPushButton(self.time_line_frame) self.time_line_button_play.setEnabled(False) self.time_line_button_play.setIcon( QtGui.QIcon(get_source_name("icons/play.jpg"))) self.time_line_play = False self.time_line_button_play.clicked.connect(self.callback_play_clicked) self.time_line_button_stop = QtGui.QPushButton(self.time_line_frame) self.time_line_button_stop.setEnabled(False) self.time_line_button_stop.setIcon( QtGui.QIcon(get_source_name("icons/stop.jpg"))) self.time_line_button_stop.clicked.connect(self.callback_stop_clicked) self.time_line_layout.addWidget(self.time_line_button_play) self.time_line_layout.addWidget(self.time_line_button_stop) self.time_slider = QtGui.QSlider(QtCore.Qt.Horizontal) self.time_slider.setRange(0, 100) #### index for time_stamp self.time_line_layout.addWidget(self.time_slider) ## timer self.timer = QtCore.QTimer() self.timer.timeout.connect(self.animation_update) self.current_time = 0 self.dt = 50 self.splitter1 = QtGui.QSplitter(QtCore.Qt.Vertical) self.splitter1.addWidget(self.plot_data_frame) self.splitter1.addWidget(self.list_data_frame) self.splitter2 = QtGui.QSplitter(QtCore.Qt.Vertical) self.splitter2.addWidget(self.graph_frame) self.splitter2.addWidget(self.time_line_frame) self.splitter3 = QtGui.QSplitter(QtCore.Qt.Horizontal) self.splitter3.addWidget(self.splitter1) self.splitter3.addWidget(self.splitter2) self.mainlayout.addWidget(self.splitter3) self.setCentralWidget(self.main_widget) self.setGeometry(200, 200, 800, 800) self.setWindowTitle("pyFlightAnalysis") self.quadrotorStateChanged.connect( self.quadrotor_win.callback_update_quadrotor_pos) self.quadrotorStateReseted.connect( self.quadrotor_win.callback_quadrotor_state_reset) def keyPressed(self, event): """Key Pressed function for graph""" if event.key() == QtCore.Qt.Key_Delete: self.deletePressed.emit(True) elif event.key() == QtCore.Qt.Key_R: # ROI graph can also be triggered by press 'r' self.callback_ROI_triggered() @staticmethod def getIndex(data, item): for ind, d in enumerate(data): if d > item: return ind return len(data) - 1 @staticmethod # ref:https://github.com/PX4/Firmware/blob/master/src/lib/mathlib/math/Quaternion.hpp def quat_to_euler(q0, q1, q2, q3): #321 angles = [] for i in range(len(q0)): yaw = 180 / np.pi * np.arctan2( 2.0 * (q0[i] * q1[i] + q2[i] * q3[i]), 1.0 - 2.0 * (q1[i]**2 + q2[i]**2)) roll = 180 / np.pi * np.arcsin(2.0 * (q0[i] * q2[i] - q3[i] * q1[i])) pitch = 180 / np.pi * np.arctan2( 2.0 * (q0[i] * q3[i] + q1[i] * q2[i]), 1.0 - 2.0 * (q2[i]**2 + q3[i]**2)) angles.append([yaw, roll, pitch]) return angles def callback_open_log_file(self): from os.path import expanduser home_path = expanduser('~') filename = QtGui.QFileDialog.getOpenFileName(self, 'Open Log File', home_path, 'Log Files (*.ulg)') # On Window, filename will be a tuple (fullpath, filter) if isinstance(filename, tuple): filename = filename[0] if filename: try: self.log_file_name = filename self.load_data() self.load_data_tree() self.time_line_button_play.setEnabled(True) except Exception as ex: print(ex) def callback_play_clicked(self): """Time line play""" self.time_line_play = not self.time_line_play if self.log_file_name is not None: if self.time_line_play: self.time_line_button_play.setIcon( QtGui.QIcon(get_source_name("icons/pause.jpg"))) self.time_line_button_stop.setEnabled(True) if self.ROI_showed: region = self.ROI_region.getRegion() self.vLine.setPos(region[0]) self.vLine_detail.setPos(region[0]) else: self.vLine.setPos(self.time_range[0]) self.vLine_detail.setPos(self.time_range[0]) self.vLine.show() self.vLine_detail.show() # start timer self.timer.start(self.dt) else: self.time_line_button_play.setIcon( QtGui.QIcon(get_source_name("icons/play.jpg"))) self.time_line_button_stop.setEnabled(False) self.timer.stop() def callback_stop_clicked(self): self.time_line_play = False self.timer.stop() self.time_line_button_play.setIcon( QtGui.QIcon(get_source_name("icons/play.jpg"))) self.time_line_button_stop.setEnabled(False) self.time_slider.setValue(0) self.time_index = 0 self.vLine.hide() self.vLine_detail.hide() self.quadrotorStateReseted.emit(True) def animation_update(self): """update the quadrotor state""" dV = 100.0 / (self.time_range[1] - self.time_range[0]) if self.ROI_showed: start, end = self.ROI_region.getRegion() t = self.current_time + start # emit data indexes = map(self.getIndex, [ self.time_stamp_position, self.time_stamp_attitude, self.time_stamp_output ], [t, t, t]) state_data = [ self.position_history[indexes[0]], self.attitude_history[indexes[1]], self.output_history[indexes[2]] ] self.quadrotorStateChanged.emit(state_data) # update slider self.time_slider.setValue( int(dV * (self.current_time + start - self.time_range[0]))) # update vLine pos self.vLine.setPos(t) self.vLine_detail.setPos(t) if self.current_time > (end - start): self.current_time = 0 self.quadrotorStateReseted.emit(True) else: t = self.current_time + self.time_range[0] self.time_slider.setValue(int(dV * self.current_time)) # update quadrotor position and attitude and motor speed indexes = map(self.getIndex, [ self.time_stamp_position, self.time_stamp_attitude, self.time_stamp_output ], [t, t, t]) state_data = [ self.position_history[indexes[0]], self.attitude_history[indexes[1]], self.output_history[indexes[2]] ] self.quadrotorStateChanged.emit(state_data) # update vLine pos self.vLine.setPos(t) self.vLine_detail.setPos(t) # if arrive end just replay if self.current_time > (self.time_range[1] - self.time_range[0]): self.current_time = 0 self.quadrotorStateReseted.emit(True) self.current_time += self.dt / self.current_factor def callback_show_quadrotor(self): if self.quadrotor_widget_isshow: self.show_quadrotor_3d.setIcon( QtGui.QIcon(get_source_name('icons/quadrotor.gif'))) self.quadrotor_widget_isshow = not self.quadrotor_widget_isshow self.quadrotor_win.hide() self.update() else: self.quadrotor_widget_isshow = not self.quadrotor_widget_isshow self.show_quadrotor_3d.setIcon( QtGui.QIcon(get_source_name('icons/quadrotor_pressed.gif'))) splash = ThreadQDialog(self.quadrotor_win.quadrotor_widget, self.quadrotor_win) splash.run() self.quadrotor_win.show() self.update() def callback_speed_combo_indexChanged(self, index): self.current_factor = self.speed_factor / 2**index def callback_filter(self, filtertext): """Accept filter and update the tree widget""" filtertext = str(filtertext) if self.data_dict is not None: if filtertext == '': self.load_data_tree() else: self.item_list_treeWidget.clear() for key, values_name in self.data_dict.items(): values_satisfied = [] for value in values_name: if filtertext in value[0]: values_satisfied.append(value) if values_satisfied: param_name = QtGui.QTreeWidgetItem( self.item_list_treeWidget, [key]) self.item_list_treeWidget.expandItem(param_name) for data_name in values_satisfied: self.item_list_treeWidget.expandItem( QtGui.QTreeWidgetItem( param_name, [data_name[0], data_name[1], data_name[2] ])) def callback_graph_clicked(self, event): """ set the curve highlighted to be normal """ if self.curve_clicked: if event.modifiers() == QtCore.Qt.ControlModifier: pass else: for curve in self.curve_highlighted[:-1]: curve.setShadowPen( pg.mkPen((200, 200, 200), width=1, cosmetic=True)) self.curve_highlighted = self.curve_highlighted[-1:] if len(self.curve_highlighted) > 0 and not self.curve_clicked: for curve in self.curve_highlighted: curve.setShadowPen( pg.mkPen((120, 120, 120), width=1, cosmetic=True)) self.curve_highlighted = [] self.plotting_data_tableView.setCurrentCell(0, 0) self.curve_clicked = False def callback_tree_double_clicked(self, item, col): """Add clicked item to Data plotting area""" def expand_name(item): if item.parent() is None: return str(item.text(0)) else: return expand_name(item.parent()) + '->' + str(item.text(0)) # When click high top label, no action will happened if item.parent() is None: return item_label = expand_name(item) row = len(self.data_plotting) self.plotting_data_tableView.insertRow(row) # Label self.plotting_data_tableView.setCellWidget(row, 0, QtGui.QLabel(item_label)) # Curve Color ## rgb + a color = [random.randint(0, 255) for _ in range(3)] btn = ColorPushButton(self.id, self.plotting_data_tableView, color) btn.sigColorChanged.connect(self.callback_color_changed) self.plotting_data_tableView.setCellWidget(row, 1, btn) # Curve Visible chk = Checkbox(self.id, '') chk.setChecked(True) chk.sigStateChanged.connect(self.callback_visible_changed) self.plotting_data_tableView.setCellWidget(row, 2, chk) data_index = self.data_dict.keys().index(item_label.split('->')[0]) data_name = item_label.split('->')[-1] ## ms to s t = self.log_data[data_index].data['timestamp'] / 10**6 data = self.log_data[data_index].data[data_name] curve = self.main_graph.plot(t, data, pen=color, clickable=True, name=item_label) curve.sigClicked.connect(self.callback_curve_clicked) curve.curve.setClickable(True) # whether show the curve showed = True self.data_plotting.append( [item_label, color, curve, showed, (t, data), self.id]) # increase the id self.id += 1 self.update_ROI_graph() def callback_curve_clicked(self, curve): """""" self.curve_clicked = True curves = [data[2] for data in self.data_plotting] ind = curves.index(curve) curve.setShadowPen(pg.mkPen((70, 70, 70), width=5, cosmetic=True)) self.curve_highlighted.append(curve) self.plotting_data_tableView.setCurrentCell(ind, 0) def callback_del_plotting_data(self): """""" indexes = self.plotting_data_tableView.selectedIndexes() rows_del = set([ind.row() for ind in indexes]) rows_all = set(range(len(self.data_plotting))) rows_reserved = list(rows_all - rows_del) data_plotting = [] for row in rows_reserved: data_plotting.append(self.data_plotting[row]) self.data_plotting = data_plotting self.update_graph() def callback_visible_changed(self, chk): """""" state = True if chk.checkState() == QtCore.Qt.Checked else False ids = [item[5] for item in self.data_plotting] self.data_plotting[ids.index(chk.id)][3] = state self.update_graph() def callback_color_changed(self, btn): color = [c * 255 for c in btn.color('float')[:-1]] ids = [item[5] for item in self.data_plotting] self.data_plotting[ids.index(btn.id)][1] = color self.update_graph() def update_graph(self): self.plotting_data_tableView.setRowCount(0) for ind, item in enumerate(self.data_plotting): self.plotting_data_tableView.insertRow(ind) self.plotting_data_tableView.setCellWidget(ind, 0, QtGui.QLabel(item[0])) btn = ColorPushButton(self.id, self.plotting_data_tableView, item[1]) btn.sigColorChanged.connect(self.callback_color_changed) self.plotting_data_tableView.setCellWidget(ind, 1, btn) chkbox = Checkbox(self.id, '') chkbox.setChecked(self.data_plotting[ind][3]) chkbox.sigStateChanged.connect(self.callback_visible_changed) self.plotting_data_tableView.setCellWidget(ind, 2, chkbox) self.data_plotting[ind][5] = self.id self.id += 1 # remove curves in graph items_to_be_removed = [] for item in self.main_graph.items: if isinstance(item, pg.PlotDataItem): items_to_be_removed.append(item) for item in items_to_be_removed: self.main_graph.removeItem(item) self.main_graph.legend.scene().removeItem(self.main_graph.legend) self.main_graph.addLegend() # redraw curves for ind, item in enumerate(self.data_plotting): label, color, _, showed, data, _ = item if showed: curve = self.main_graph.plot(data[0], data[1], pen=color, name=label) self.data_plotting[ind][2] = curve self.update_ROI_graph() def callback_clear_plotting_data(self): """""" self.data_plotting = [] self.curve_highlighted = [] self.update_graph() def callback_graph_index_combobox_changed(self, index): """Add clicked item to Data plotting area""" if index == self.graph_number: # choose new self.graph_number += 1 # add a graph graph_widget = pg.GraphicsLayoutWidget() graph_widget.addPlot(row=0, col=0) self.graph_lines_dict.setdefault(graph_widget, 0) for data in self.data_plotting: data[1].clear() for i in range(1, self.graph_number + 1): data[1].addItem(str(i)) data[1].addItem('New') else: # change current curve's graph pass def callback_visible_checkBox(self, checked): """Set the curve visible or invisible""" if checked: pass else: pass def callback_ROI_triggered(self): """Show the graph""" if self.ROI_showed: self.detail_graph.hide() self.ROI_region.hide() self.ROI_showed = not self.ROI_showed else: self.update_ROI_graph() self.detail_graph.show() self.ROI_region.show() self.ROI_showed = not self.ROI_showed def update_ROI_graph(self): items_to_be_removed = [] for item in self.detail_graph.items: if isinstance(item, pg.PlotDataItem): items_to_be_removed.append(item) for item in items_to_be_removed: self.detail_graph.removeItem(item) items = self.main_graph.items for item in items: if isinstance(item, pg.PlotDataItem): self.detail_graph.plot(item.xData, item.yData, pen=item.opts['pen']) def load_data(self): self.log_data = ULog(str(self.log_file_name)).data_list self.data_dict = OrderedDict() for d in self.log_data: data_items_list = [f.field_name for f in d.field_data] data_items_list.remove('timestamp') data_items_list.insert(0, 'timestamp') data_items = [(item, str(d.data[item].dtype), str(len(d.data[item]))) for item in data_items_list] self.data_dict.setdefault(d.name, data_items[1:]) # attitude index = self.data_dict.keys().index('vehicle_attitude') self.time_stamp_attitude = self.log_data[index].data[ 'timestamp'] / 10**6 q0 = self.log_data[index].data['q[0]'] q1 = self.log_data[index].data['q[1]'] q2 = self.log_data[index].data['q[2]'] q3 = self.log_data[index].data['q[3]'] self.attitude_history = self.quat_to_euler(q0, q1, q2, q3) # position index = self.data_dict.keys().index('vehicle_local_position') self.time_stamp_position = self.log_data[index].data[ 'timestamp'] / 10**6 x = self.log_data[index].data['x'] y = self.log_data[index].data['y'] z = self.log_data[index].data['z'] self.position_history = [ (x[i] * self.SCALE_FACTOR, y[i] * self.SCALE_FACTOR, z[i] * self.SCALE_FACTOR) for i in range(len(x)) ] # motor rotation index = self.data_dict.keys().index('actuator_outputs') self.time_stamp_output = self.log_data[index].data['timestamp'] / 10**6 output0 = self.log_data[index].data['output[0]'] output1 = self.log_data[index].data['output[1]'] output2 = self.log_data[index].data['output[2]'] output3 = self.log_data[index].data['output[3]'] self.output_history = [(output0[i], output1[i], output2[i], output3[i]) for i in range(len(output0))] # get common time range self.time_range = max([self.time_stamp_attitude[0],self.time_stamp_output[0],self.time_stamp_position[0]]),\ min([self.time_stamp_attitude[-1],self.time_stamp_output[-1],self.time_stamp_position[-1]]) def load_data_tree(self): # update the tree list table self.item_list_treeWidget.clear() for key, values in self.data_dict.items(): param_name = QtGui.QTreeWidgetItem(self.item_list_treeWidget, [key]) self.item_list_treeWidget.expandItem(param_name) for data_name in values: self.item_list_treeWidget.expandItem( QtGui.QTreeWidgetItem( param_name, [data_name[0], data_name[1], data_name[2]])) param_name.setExpanded(False) def quadrotor_win_closed_event(self, closed): if closed: self.quadrotor_widget_isshow = not self.quadrotor_widget_isshow self.show_quadrotor_3d.setIcon( QtGui.QIcon(get_source_name('icons/quadrotor.gif')))
class MainWindow(QtGui.QMainWindow): deletePressed = pyqtSignal(bool) quadrotorStateChanged = pyqtSignal(object) motorSpeedChanged = pyqtSignal(object) quadrotorStateReseted = pyqtSignal(bool) SCALE_FACTOR = 80 def __init__(self): """ Frame of GUI =========================== | ToolBar____ ____ | |_______|tab1|tab2|_______| | | | | |plot| | graph1 | |list| |------------------| |----|<| | |data| | graph2 | |list| | | =========================== """ super(MainWindow, self).__init__() self.log_data_list = None self.log_file_name = None self.data_dict = None self.log_info_data = None self.log_params_data = None self.log_changed_params = [] self.main_widget = QtGui.QWidget(self) self.mainlayout = QtGui.QHBoxLayout() self.main_widget.setLayout(self.mainlayout) # ToolBar self.toolbar = self.addToolBar('FileManager') self.basic_tool_group = QtGui.QActionGroup(self) ## load log file self.loadfile_action = QtGui.QAction(QtGui.QIcon(get_source_name('icons/open.gif')), 'Open log file', self) self.loadfile_action.setShortcut('Ctrl+O') self.loadfile_action.triggered.connect(self.callback_open_log_file) self.toolbar.addAction(self.loadfile_action) ## plot quadrotor in 3d graph self.show_quadrotor_3d = QtGui.QAction(QtGui.QIcon(get_source_name('icons/quadrotor.gif')), 'show 3d viewer', self) self.show_quadrotor_3d.setShortcut('Ctrl+Shift+Q') self.show_quadrotor_3d.triggered.connect(self.callback_show_quadrotor) self.toolbar.addAction(self.show_quadrotor_3d) ## show quadrotor info self.show_info = QtGui.QAction(QtGui.QIcon(get_source_name('icons/info.gif')), 'show log info', self) self.show_info.setShortcut('Ctrl+I') self.show_info.triggered.connect(self.callback_show_info_pane) self.toolbar.addAction(self.show_info) self.info_pane_showed = False ## show quadrotor param self.show_params = QtGui.QAction(QtGui.QIcon(get_source_name('icons/params.gif')), 'show params', self) self.show_params.setShortcut('Ctrl+P') self.show_params.triggered.connect(self.callback_show_parameters_pane) self.toolbar.addAction(self.show_params) self.params_pane_showed = False ## show some default analysis graphs self.show_analysis_graphs = QtGui.QAction(QtGui.QIcon(get_source_name('icons/analysis_graph.gif')), 'show default analysis graph control pane', self) self.show_analysis_graphs.setShortcut('Ctrl+G') self.show_analysis_graphs.triggered.connect(self.callback_show_analysis_graph_pane) self.toolbar.addAction(self.show_analysis_graphs) self.analysis_graphs_showed = False self.analysis_graphs_pane = None ## show help self.show_help = QtGui.QAction(QtGui.QIcon(get_source_name('icons/help.gif')), 'show help', self) self.show_help.setShortcut('Ctrl+H') self.show_help.triggered.connect(self.callback_show_help_pane) self.toolbar.addAction(self.show_help) self.help_pane_showed = False self.help_pane = None self.basic_tool_group.addAction(self.loadfile_action) self.basic_tool_group.addAction(self.show_quadrotor_3d) self.basic_tool_group.addAction(self.show_info) self.basic_tool_group.addAction(self.show_params) self.basic_tool_group.addAction(self.show_analysis_graphs) self.basic_tool_group.addAction(self.show_help) ## show some analysis graph # Left plot item widget self.plot_data_frame = QtGui.QFrame(self) self.plot_data_frame.setFrameShape(QtGui.QFrame.StyledPanel) # self.plot_data_layout_H = QtGui.QHBoxLayout(self.plot_data_frame) self.plot_data_layout_V = QtGui.QVBoxLayout(self.plot_data_frame) ## Data Plotting [id, filesystem, ] self.data_plotting = OrderedDict() ### There exists a Default graph self.line_ID = 0 lbl_ploting_data = QtGui.QLabel('Data Plotting') self.plotting_data_tableView = TableView(self.plot_data_frame) self.plotting_data_tableView.setEditTriggers(QtGui.QAbstractItemView.DoubleClicked | QtGui.QAbstractItemView.SelectedClicked) self.plotting_data_tableView.setSortingEnabled(False) self.plotting_data_tableView.horizontalHeader().setStretchLastSection(True) self.plotting_data_tableView.resizeColumnsToContents() self.plotting_data_tableView.setColumnCount(3) self.plotting_data_tableView.setColumnWidth(0, 200) self.plotting_data_tableView.setColumnWidth(1, 60) self.plotting_data_tableView.setColumnWidth(2, 50) self.plotting_data_tableView.setHorizontalHeaderLabels(['Label', 'Visible', 'Curve Style']) self.id = 0 lbl_ploting_data.setBuddy(self.plotting_data_tableView) self.plot_data_layout_V.addWidget(lbl_ploting_data) self.plot_data_layout_V.addWidget(self.plotting_data_tableView) edit_layout = QtGui.QHBoxLayout() self.delete_btn = QtGui.QPushButton('Delete') self.delete_btn.clicked.connect(self.callback_del_plotting_data) self.clear_btn = QtGui.QPushButton('Clear') self.clear_btn.clicked.connect(self.callback_clear_plotting_data) edit_layout.addWidget(self.clear_btn) edit_layout.addWidget(self.delete_btn) self.plot_data_layout_V.addLayout(edit_layout) ## Data in the log file self.list_data_frame = QtGui.QFrame(self) self.list_data_frame.setMinimumWidth(400) self.list_data_frame.setMaximumWidth(600) self.list_data_frame.resize(400, 500) self.list_data_frame.setFrameShape(QtGui.QFrame.StyledPanel) self.list_data_layout = QtGui.QVBoxLayout(self.list_data_frame) ### line to search item self.choose_item_lineEdit = QtGui.QLineEdit(self.list_data_frame) self.choose_item_lineEdit.setPlaceholderText('filter by data name') self.choose_item_lineEdit.textChanged.connect(self.callback_filter) ### tree to show data to plot self.item_list_treeWidget = QtGui.QTreeWidget(self.list_data_frame) self.item_list_treeWidget.clear() self.item_list_treeWidget.setColumnCount(3) self.item_list_treeWidget.setColumnWidth(0, 160) self.item_list_treeWidget.setHeaderLabels(['Flight Data', 'Type', 'Length']) self.item_list_treeWidget.itemDoubleClicked.connect(self.callback_tree_double_clicked) self.item_list_treeWidget.resizeColumnToContents(2) self.list_data_layout.addWidget(self.choose_item_lineEdit) self.list_data_layout.addWidget(self.item_list_treeWidget) # Right plot item self.graph_frame = QtGui.QFrame(self) self.default_tab = TabWidget(self.graph_frame) self.graph_frame.setFrameShape(QtGui.QFrame.StyledPanel) self.animation_layout = QtGui.QVBoxLayout(self.graph_frame) ## quadrotor 3d self.quadrotor_win = QuadrotorWin(self) self.quadrotor_win.closed.connect(self.quadrotor_win_closed_event) self.quadrotor_win.hide() self.first_load = True self.quadrotor_widget_isshowed = False ## default plot self.default_graph_widget_t = pg.GraphicsLayoutWidget() self.default_graph_widget_2d = pg.GraphicsLayoutWidget() self.default_graph_widget_3d = pg.GraphicsLayoutWidget() self.default_tab.addTab(self.default_graph_widget_t, 't') self.default_tab.addTab(self.default_graph_widget_2d, '2D') self.default_tab.addTab(self.default_graph_widget_3d, '3D') ### a hidable ROI region self.detail_graph = self.default_graph_widget_t.addPlot(row=0, col=0) self.detail_graph.setAutoVisible(True) self.detail_graph.hide() ### main graph to plot curves self.main_graph_t = self.default_graph_widget_t.addPlot(row=1, col=0) self.main_graph_t.showGrid(x=True, y=True) self.main_graph_t.keyPressEvent = self.keyPressed self.deletePressed.connect(self.callback_del_plotting_data) self.main_graph_t.addLegend() ROI_action = QtGui.QAction('show/hide ROI graph', self.main_graph_t) ROI_action.triggered.connect(self.callback_ROI_triggered) self.main_graph_t.scene().contextMenu.append(ROI_action) self.ROI_region = pg.LinearRegionItem() self.ROI_region.setZValue(10) self.ROI_region.hide() self.ROI_showed = False ### main graph self.main_graph_2d = self.default_graph_widget_2d.addPlot(row=0, col=0) self.main_graph_2d.showGrid(x=True, y=True) self.main_graph_3d = self.default_graph_widget_3d.addPlot(row=0, col=0) def update(): self.ROI_region.setZValue(10) minX, maxX = self.ROI_region.getRegion() self.detail_graph.setXRange(minX, maxX, padding=0) self.ROI_region.sigRegionChanged.connect(update) def updateRegion(window, viewRange): rgn = viewRange[0] self.ROI_region.setRegion(rgn) self.detail_graph.sigRangeChanged.connect(updateRegion) self.main_graph_t.addItem(self.ROI_region, ignoreBounds=True) ## vertical line self.vLine = pg.InfiniteLine(angle=90, movable=False) self.vLine.hide() self.main_graph_t.addItem(self.vLine, ignoreBounds=True) self.vLine_detail = pg.InfiniteLine(angle=90, movable=False) self.vLine_detail.hide() self.detail_graph.addItem(self.vLine_detail, ignoreBounds=True) ## flag whether there is a curve clicked after last clicked event self.curve_clicked = False self.curve_clicked_time = time.time() self.curve_highlighted = [] self.animation_layout.addWidget(self.default_tab) ## time line self.time_line_frame = QtGui.QFrame(self) self.time_line_frame.setMaximumHeight(45) self.time_line_frame.setMinimumHeight(45) self.time_line_layout = QtGui.QHBoxLayout(self.time_line_frame) time_line_lbl = QtGui.QLabel('x') time_line_lbl.setToolTip('set play speed') speed_combo = QtGui.QComboBox() speed_combo.addItems(['1', '2', '4', '8']) self.speed_factor = 500 self.time_line_layout.addWidget(time_line_lbl) self.time_line_layout.addWidget(speed_combo) speed_combo.currentIndexChanged.connect(self.callback_speed_combo_indexChanged) self.current_factor = 500/1 self.time_line_button_play = QtGui.QPushButton(self.time_line_frame) self.time_line_button_play.setEnabled(False) self.time_line_button_play.setIcon(QtGui.QIcon(get_source_name("icons/play.jpg"))) self.time_line_play = False self.time_line_button_play.clicked.connect(self.callback_play_clicked) self.time_line_button_stop = QtGui.QPushButton(self.time_line_frame) self.time_line_button_stop.setEnabled(False) self.time_line_button_stop.setIcon(QtGui.QIcon(get_source_name("icons/stop.jpg"))) self.time_line_button_stop.clicked.connect(self.callback_stop_clicked) self.time_line_layout.addWidget(self.time_line_button_play) self.time_line_layout.addWidget(self.time_line_button_stop) self.time_slider = QtGui.QSlider(QtCore.Qt.Horizontal) self.time_slider.setRange(0, 100) #### index for time_stamp self.time_line_layout.addWidget(self.time_slider) ## timer self.timer = QtCore.QTimer() self.timer.timeout.connect(self.animation_update) self.current_time = 0 self.dt = 50 self.splitter1 = QtGui.QSplitter(QtCore.Qt.Vertical) self.splitter1.addWidget(self.plot_data_frame) self.splitter1.addWidget(self.list_data_frame) self.splitter2 = QtGui.QSplitter(QtCore.Qt.Vertical) self.splitter2.addWidget(self.graph_frame) self.splitter2.addWidget(self.time_line_frame) self.splitter3 = QtGui.QSplitter(QtCore.Qt.Horizontal) self.splitter3.addWidget(self.splitter1) self.splitter3.addWidget(self.splitter2) self.mainlayout.addWidget(self.splitter3) self.setCentralWidget(self.main_widget) self.setGeometry(200, 200, 1000, 800) self.setWindowTitle("pyFlightAnalysis") self.quadrotorStateChanged.connect(self.quadrotor_win.callback_update_quadrotor_pos) self.quadrotorStateReseted.connect(self.quadrotor_win.callback_quadrotor_state_reset) def keyPressed(self, event): """Key Pressed function for graph""" if event.key() == QtCore.Qt.Key_Delete: self.deletePressed.emit(True) elif event.key() == QtCore.Qt.Key_R: # ROI graph can also be triggered by press 'r' self.callback_ROI_triggered() @staticmethod def getIndex(data, item): for ind, d in enumerate(data): if d > item: return ind return len(data) - 1 @staticmethod def quat_to_euler(q0, q1, q2, q3): #321 angles = [] for i in range(len(q0)): roll = 180/np.pi * np.arctan2(2.0 * (q0[i] * q1[i] + q2[i] * q3[i]), 1.0 - 2.0 * (q1[i]**2 + q2[i]**2)) pitch = 180/np.pi * np.arcsin(2.0 * (q0[i] * q2[i] - q3[i] * q1[i])) yaw = 180/np.pi * np.arctan2(2.0 * (q0[i] * q3[i] + q1[i] * q2[i]), 1.0 - 2.0 * (q2[i]**2 + q3[i]**2)) angles.append([roll, pitch, yaw]) return angles def callback_open_log_file(self): config_path = os.path.join(os.getcwd(), get_source_name('config.txt')) if os.path.exists(config_path): with open(config_path, 'r') as conf: path_hist = conf.readline() path = path_hist.split(':')[-1] else: path = '' if not path: from os.path import expanduser path = expanduser('~') filename = QtGui.QFileDialog.getOpenFileName(self, 'Open Log File', path, 'Log Files (*.ulg)') # On Window, filename will be a tuple (fullpath, filter) if isinstance(filename, tuple): filename = filename[0] if filename: try: self.log_file_name = filename self.id = 0 self.load_data() self.load_data_tree() self.analysis_graph_list = OrderedDict() self.update_graph_after_log_changed() self.time_line_button_play.setEnabled(True) # write the file path to config.txt with open(get_source_name('config.txt'), 'w') as conf: conf.write('last_path:' + os.path.split(filename)[0]) except Exception as ex: print(ex) def callback_play_clicked(self): """Time line play""" self.time_line_play = not self.time_line_play if self.log_file_name is not None: if self.time_line_play: self.time_line_button_play.setIcon(QtGui.QIcon(get_source_name("icons/pause.jpg"))) self.time_line_button_stop.setEnabled(True) if self.ROI_showed: region = self.ROI_region.getRegion() self.vLine.setPos(region[0]) self.vLine_detail.setPos(region[0]) else: self.vLine.setPos(self.time_range[0]) self.vLine_detail.setPos(self.time_range[0]) self.vLine.show() self.vLine_detail.show() # start timer self.timer.start(self.dt) else: self.time_line_button_play.setIcon(QtGui.QIcon(get_source_name("icons/play.jpg"))) self.time_line_button_stop.setEnabled(False) self.timer.stop() def callback_stop_clicked(self): self.time_line_play = False self.timer.stop() self.time_line_button_play.setIcon(QtGui.QIcon(get_source_name("icons/play.jpg"))) self.time_line_button_stop.setEnabled(False) self.time_slider.setValue(0) self.time_index = 0 self.vLine.hide() self.vLine_detail.hide() self.quadrotorStateReseted.emit(True) def animation_update(self): """update the quadrotor state""" dV = 100.0/(self.time_range[1] - self.time_range[0]) if self.ROI_showed: start, end = self.ROI_region.getRegion() t = self.current_time + start # emit data indexes = list(map(self.getIndex, [self.time_stamp_position, self.time_stamp_attitude, self.time_stamp_output], [t, t, t])) state_data = [self.position_history[indexes[0]], self.attitude_history[indexes[1]], self.output_history[indexes[2]]] self.quadrotorStateChanged.emit(state_data) # update slider self.time_slider.setValue(int(dV * (self.current_time + start - self.time_range[0]))) # update vLine pos self.vLine.setPos(t) self.vLine_detail.setPos(t) if self.current_time > (end - start): self.current_time = 0 self.quadrotorStateReseted.emit(True) else: t = self.current_time + self.time_range[0] self.time_slider.setValue(int(dV * self.current_time)) # update quadrotor position and attitude and motor speed indexes = list(map(self.getIndex, [self.time_stamp_position, self.time_stamp_attitude, self.time_stamp_output], [t, t, t])) state_data = [self.position_history[indexes[0]], self.attitude_history[indexes[1]], self.output_history[indexes[2]]] # print('state:',state_data) self.quadrotorStateChanged.emit(state_data) # update vLine pos self.vLine.setPos(t) self.vLine_detail.setPos(t) # if arrive end just replay if self.current_time > (self.time_range[1] - self.time_range[0]): self.current_time = 0 self.quadrotorStateReseted.emit(True) self.current_time += self.dt/self.current_factor def callback_show_quadrotor(self): if self.quadrotor_widget_isshowed: self.show_quadrotor_3d.setIcon(QtGui.QIcon(get_source_name('icons/quadrotor.gif'))) self.quadrotor_widget_isshowed = not self.quadrotor_widget_isshowed self.quadrotor_win.hide() self.update() else: self.quadrotor_widget_isshowed = not self.quadrotor_widget_isshowed self.show_quadrotor_3d.setIcon(QtGui.QIcon(get_source_name('icons/quadrotor_pressed.gif'))) splash = ThreadQDialog(self.quadrotor_win.quadrotor_widget, self.quadrotor_win) splash.run() self.quadrotor_win.show() self.update() def callback_show_info_pane(self): if self.log_info_data is not None: if self.info_pane_showed: self.show_info.setIcon(QtGui.QIcon(get_source_name('icons/info.gif'))) self.info_pane.close() del self.info_pane else: self.show_info.setIcon(QtGui.QIcon(get_source_name('icons/info_pressed.gif'))) self.info_pane = InfoWin(self.log_info_data) self.info_pane.closed.connect(self.callback_show_info_pane_closed) self.info_pane.show() self.info_pane_showed = not self.info_pane_showed else: load_file_first_info() def callback_show_info_pane_closed(self, closed): if closed: self.show_info.setIcon(QtGui.QIcon(get_source_name('icons/info.gif'))) def callback_show_parameters_pane(self): if self.log_params_data is not None: if self.params_pane_showed: self.show_params.setIcon(QtGui.QIcon(get_source_name('icons/params.gif'))) self.params_pane.close() del self.params_pane else: self.show_params.setIcon(QtGui.QIcon(get_source_name('icons/params_pressed.gif'))) self.params_pane = ParamsWin(self.log_params_data, self.log_changed_params) self.params_pane.closed.connect(self.callback_show_params_pane_closed) self.params_pane.show() self.params_pane_showed = not self.params_pane_showed else: load_file_first_info() def callback_show_params_pane_closed(self, closed): if closed: self.params_pane_showed = False self.show_params.setIcon(QtGui.QIcon(get_source_name('icons/params.gif'))) def callback_show_analysis_graph_pane(self): if self.log_data_list is not None: if self.analysis_graphs_showed: self.show_analysis_graphs.setIcon(QtGui.QIcon(get_source_name('icons/analysis_graph.gif'))) self.analysis_graphs_pane.hide() else: self.show_analysis_graphs.setIcon(QtGui.QIcon(get_source_name('icons/analysis_graph_pressed.gif'))) if self.analysis_graphs_pane is None: self.analysis_graphs_pane = AnalysisGraphWin(self) self.analysis_graphs_pane.closed.connect(self.callback_analysis_graphs_pane_closed) self.analysis_graphs_pane.sigChecked.connect(self.callback_analysis_graph_data_checked) self.analysis_graphs_pane.sigUnchecked.connect(self.callback_analysis_graph_data_unchecked) self.analysis_graphs_pane.show() self.analysis_graphs_showed = not self.analysis_graphs_showed else: load_file_first_info() def callback_analysis_graphs_pane_closed(self, closed): if closed: self.analysis_graphs_showed = False self.show_analysis_graphs.setIcon(QtGui.QIcon(get_source_name('icons/analysis_graph.gif'))) def callback_analysis_graph_data_checked(self, curve_name_with_data): color_list = [(255, 0, 0), (0, 255, 0), (0, 0, 255), (0, 255, 255), (255, 0, 255), (155, 0, 160), (0, 155, 155)] curve_name, data = curve_name_with_data data_type = data[0] if curve_name not in self.analysis_graph_list: new_graph = pg.GraphicsLayoutWidget() ax = new_graph.addPlot(row=0, col=0) ax.addLegend() self.analysis_graph_list[curve_name] = new_graph self.default_tab.addTab(new_graph, curve_name) self.default_tab.setCurrentWidget(new_graph) for ind, curve_data in enumerate(data[1:]): ax.plot(curve_data[0], curve_data[1], pen=color_list[ind%len(color_list)], name=curve_data[2]) def callback_analysis_graph_data_unchecked(self, graph_name): tab_index = 0 for i in range(self.default_tab.count()): if self.default_tab.widget(i) == self.analysis_graph_list[graph_name]: tab_index = i break self.default_tab.removeTab(tab_index) self.analysis_graph_list.pop(graph_name) def callback_show_help_pane(self): if self.help_pane_showed: self.show_help.setIcon(QtGui.QIcon(get_source_name('icons/help.gif'))) self.help_pane.hide() else: if self.help_pane is None: self.help_pane = HelpWin() self.help_pane.closed.connect(self.callback_help_pane_closed) self.show_help.setIcon(QtGui.QIcon(get_source_name('icons/help_pressed.gif'))) self.help_pane.show() self.help_pane_showed = not self.help_pane_showed def callback_help_pane_closed(self, closed): if closed: self.help_paned_showed = False self.show_help.setIcon(QtGui.QIcon(get_source_name('icons/help.gif'))) def callback_speed_combo_indexChanged(self, index): self.current_factor = self.speed_factor / 2**index def callback_filter(self, filtertext): """Accept filter and update the tree widget""" filtertext = str(filtertext) if self.data_dict is not None: if filtertext == '': self.load_data_tree() else: self.item_list_treeWidget.clear() for key, values_name in self.data_dict.items(): values_satisfied = [] if filtertext in key: for value in values_name: values_satisfied.append(value) else: for value in values_name: if filtertext in value[0]: values_satisfied.append(value) if values_satisfied: param_name = QtGui.QTreeWidgetItem(self.item_list_treeWidget, [key]) self.item_list_treeWidget.expandItem(param_name) for data_name in values_satisfied: self.item_list_treeWidget.expandItem( QtGui.QTreeWidgetItem(param_name, [data_name[0], data_name[1], data_name[2]])) def callback_graph_clicked(self, event): """ set the curve highlighted to be normal """ print('graph clicked') if self.curve_clicked: if event.modifiers() == QtCore.Qt.ControlModifier: pass else: for curve in self.curve_highlighted[:-1]: curve.setShadowPen(pg.mkPen((200, 200, 200), width=1, cosmetic=True)) self.curve_highlighted = self.curve_highlighted[-1:] if len(self.curve_highlighted) > 0 and not self.curve_clicked: for curve in self.curve_highlighted: curve.setShadowPen(pg.mkPen((120, 120, 120), width=1, cosmetic=True)) self.curve_highlighted = [] self.plotting_data_tableView.setCurrentCell(0, 0) self.curve_clicked = False def callback_tree_double_clicked(self, item, col): """Add clicked item to Data plotting area""" def expand_name(item): if item.parent() is None: return str(item.text(0)) else: return expand_name(item.parent()) + '->' + str(item.text(0)) # When click high top label, no action will happened if item.parent() is None: return item_label = expand_name(item) row = len(self.data_plotting) self.plotting_data_tableView.insertRow(row) # Label self.plotting_data_tableView.setCellWidget(row, 0, QtGui.QLabel(item_label)) # Curve Visible chk = Checkbox(self.id, '') chk.setChecked(True) chk.sigStateChanged.connect(self.callback_visible_changed) self.plotting_data_tableView.setCellWidget(row, 1, chk) # Curve Color ## rgb, prefer deep color color = [random.randint(0, 150) for _ in range(3)] color_text = '#{0[0]:02x}{0[1]:02x}{0[2]:02x}'.format(color) ## Curve Marker marker = None lbl = PropertyLabel(self.id, self, "<font color='{0}'>{1}</font> {2}".format(color_text,'▇▇',str(marker))) lbl.sigPropertyChanged.connect(self.callback_property_changed) self.plotting_data_tableView.setCellWidget(row, 2, lbl) data_index = list(list(self.data_dict.keys())).index(item_label.split('->')[0]) data_name = item_label.split('->')[-1] ## ms to s t = self.log_data_list[data_index].data['timestamp']/10**6 data = self.log_data_list[data_index].data[data_name] if len(self.data_plotting) == 0: label_style = {'color': '#EEEEEE', 'font-size':'14pt'} self.main_graph_t.setLabel('bottom', 't(s)', **label_style) curve = self.main_graph_t.plot(t, data, symbol=marker, pen=color, clickable=True, name=item_label) curve.curve.setClickable(True) # functional method curve.mouseDoubleClickEvent = show_curve_property_diag(self.id, self) # whether show the curve showed = True self.data_plotting[self.id] = [item_label, showed, curve] # increase the id self.id += 1 self.update_ROI_graph() self.default_tab.setCurrentWidget(self.default_graph_widget_t) def callback_curve_clicked(self, curve): """""" print('curve clicked') self.curve_clicked = True dt = time.time() - self.curve_clicked_time self.curve_clicked_time = time.time() if dt < 0.3: win = CurveModifyWin() curves = [data[2] for data in self.data_plotting.values()] ind = curves.index(curve) curve.setShadowPen(pg.mkPen((70, 70, 70), width=5, cosmetic=True)) self.curve_highlighted.append(curve) self.plotting_data_tableView.setCurrentCell(ind, 0) def callback_del_plotting_data(self): """""" indexes = self.plotting_data_tableView.selectedIndexes() rows_del = set([ind.row() for ind in indexes]) rows_all = set(range(len(self.data_plotting))) rows_reserved = list(rows_all - rows_del) data_plotting = OrderedDict() keys, values = list(self.data_plotting.keys()), list(self.data_plotting.values()) for row in rows_reserved: data_plotting[keys[row]] = values[row] self.data_plotting = data_plotting self.update_graph() def callback_visible_changed(self, chk): """""" state = True if chk.checkState() == QtCore.Qt.Checked else False self.data_plotting[chk.id][1] = state self.update_graph() def callback_color_changed(self, btn): color = [c*255 for c in btn.color('float')[:-1]] self.data_plotting[btn.id][2].opts['pen'] = color self.update_graph() def callback_marker_changed(self, mkr): self.data_plotting[mkr.id][2].opts['symbol'] = mkr.marker self.update_graph() def keyPressEvent(self, event, *args, **kwargs): print(event) if event.key() == QtCore.Qt.Key_S: print('S pressed') if self.splitter1.isHidden(): self.splitter1.show() else: self.splitter1.hide() elif event.key() == QtCore.Qt.Key_D: print('D pressed') for curve in self.curve_highlighted: del(curve) return QtGui.QMainWindow.keyPressEvent(self, event, *args, **kwargs) def update_graph(self): # update tableView # clear self.plotting_data_tableView.setRowCount(0) # add for ind, (item_id, item) in enumerate(self.data_plotting.items()): self.plotting_data_tableView.insertRow(ind) self.plotting_data_tableView.setCellWidget(ind, 0, QtGui.QLabel(item[0])) chkbox = Checkbox(item_id, '') chkbox.setChecked(item[1]) chkbox.sigStateChanged.connect(self.callback_visible_changed) self.plotting_data_tableView.setCellWidget(ind, 1, chkbox) curve = item[2] color = curve.opts['pen'] if isinstance(color, QtGui.QColor): color = color.red(), color.green(), color.blue() marker = curve.opts['symbol'] marker_dict = OrderedDict([(None,'None'), ('s','☐'), ('t','▽'), ('o','○'), ('+','+')]) color_text = '#{0[0]:02x}{0[1]:02x}{0[2]:02x}'.format(color) lbl_txt = "<font color='{0}'>{1}</font> {2}".format(color_text,'▇▇',str(marker_dict[marker])) lbl = PropertyLabel(item_id, self, lbl_txt) lbl.sigPropertyChanged.connect(self.callback_property_changed) self.plotting_data_tableView.setCellWidget(ind, 2, lbl) # update curve # remove curves in graph items_to_be_removed = [] for item in self.main_graph_t.items: if isinstance(item, pg.PlotDataItem): items_to_be_removed.append(item) for item in items_to_be_removed: self.main_graph_t.removeItem(item) self.main_graph_t.legend.scene().removeItem(self.main_graph_t.legend) self.main_graph_t.addLegend() # redraw curves for ind, (item_id, item) in enumerate(self.data_plotting.items()): label, showed, curve = item color = curve.opts['pen'] if isinstance(color, QtGui.QColor): color = color.red(), color.green(), color.blue() data = curve.xData, curve.yData marker = curve.opts['symbol'] symbolSize = curve.opts['symbolSize'] if showed: curve = self.main_graph_t.plot(data[0], data[1], symbol=marker, pen=color, name=label, symbolSize=symbolSize) self.data_plotting[item_id][2] = curve self.update_ROI_graph() def callback_property_changed(self): self.update_graph() def callback_clear_plotting_data(self): """""" self.data_plotting = OrderedDict() self.curve_highlighted = [] self.update_graph() def callback_graph_index_combobox_changed(self, index): """Add clicked config graph to Data plotting area""" print(index) # if index == self.graph_number: # # choose new # self.graph_number += 1 # # add a graph # graph_widget = pg.GraphicsLayoutWidget() # graph_widget.addPlot(row=0, col=0) # self.graph_lines_dict.setdefault(graph_widget, 0) # for data in self.data_plotting: # data[1].clear() # for i in range(1, self.graph_number + 1): # data[1].addItem(str(i)) # data[1].addItem('New') # else: # # change current curve's graph # pass def callback_visible_checkBox(self, checked): """Set the curve visible or invisible""" if checked: pass else: pass def callback_ROI_triggered(self): """Show the graph""" if self.ROI_showed: self.detail_graph.hide() self.ROI_region.hide() self.ROI_showed = not self.ROI_showed else: self.update_ROI_graph() self.detail_graph.show() self.ROI_region.show() self.ROI_showed = not self.ROI_showed def update_ROI_graph(self): items_to_be_removed = [] for item in self.detail_graph.items: if isinstance(item, pg.PlotDataItem): items_to_be_removed.append(item) for item in items_to_be_removed: self.detail_graph.removeItem(item) items = self.main_graph_t.items for item in items: if isinstance(item, pg.PlotDataItem): self.detail_graph.plot(item.xData, item.yData, symbol=item.opts['symbol'], pen=item.opts['pen']) def load_data(self): log_data = ULog(str(self.log_file_name)) self.log_info_data = {index:value for index,value in log_data.msg_info_dict.items() if 'perf_' not in index} self.log_info_data['SW version'] = log_data.get_version_info_str() self.log_params_data = log_data.initial_parameters self.log_params_data = OrderedDict([(key, self.log_params_data[key]) for key in sorted(self.log_params_data)]) self.log_data_list = log_data.data_list self.data_dict = OrderedDict() for d in self.log_data_list: data_items_list = [f.field_name for f in d.field_data] data_items_list.remove('timestamp') data_items_list.insert(0, 'timestamp') data_items = [(item, str(d.data[item].dtype), str(len(d.data[item]))) for item in data_items_list] # add suffix to distinguish same name i = 0 name = d.name while True: if i > 0: name = d.name + '_' + str(i) if name in self.data_dict: i += 1 else: break self.data_dict.setdefault(name, data_items[1:]) # pdb.set_trace() # attitude index = list(self.data_dict.keys()).index('vehicle_attitude') self.time_stamp_attitude = self.log_data_list[index].data['timestamp']/10**6 q0 = self.log_data_list[index].data['q[0]'] q1 = self.log_data_list[index].data['q[1]'] q2 = self.log_data_list[index].data['q[2]'] q3 = self.log_data_list[index].data['q[3]'] self.attitude_history = self.quat_to_euler(q0, q1, q2, q3) index = list(self.data_dict.keys()).index('vehicle_attitude_setpoint') self.time_stamp_attitude_setpoint = self.log_data_list[index].data['timestamp']/10**6 q0_d = self.log_data_list[index].data['q_d[0]'] q1_d = self.log_data_list[index].data['q_d[1]'] q2_d = self.log_data_list[index].data['q_d[2]'] q3_d = self.log_data_list[index].data['q_d[3]'] self.attitude_setpoint_history = self.quat_to_euler(q0_d, q1_d, q2_d, q3_d) # position index = list(self.data_dict.keys()).index('vehicle_local_position') self.time_stamp_position = self.log_data_list[index].data['timestamp']/10**6 x = self.log_data_list[index].data['x'] y = self.log_data_list[index].data['y'] z = self.log_data_list[index].data['z'] self.position_history = [(x[i]*self.SCALE_FACTOR, y[i]*self.SCALE_FACTOR, z[i]*self.SCALE_FACTOR) for i in range(len(x))] # motor rotation index = list(self.data_dict.keys()).index('actuator_outputs') self.time_stamp_output = self.log_data_list[index].data['timestamp']/10**6 output0 = self.log_data_list[index].data['output[0]'] output1 = self.log_data_list[index].data['output[1]'] output2 = self.log_data_list[index].data['output[2]'] output3 = self.log_data_list[index].data['output[3]'] self.output_history = [(output0[i], output1[i], output2[i], output3[i]) for i in range(len(output0))] # get common time range self.time_range = max([self.time_stamp_attitude[0], self.time_stamp_output[0], self.time_stamp_position[0]]), \ min([self.time_stamp_attitude[-1], self.time_stamp_output[-1], self.time_stamp_position[-1]]) self.data_loaded = True def load_data_tree(self): # update the tree list table self.item_list_treeWidget.clear() for key, values in self.data_dict.items(): param_name = QtGui.QTreeWidgetItem(self.item_list_treeWidget, [key]) self.item_list_treeWidget.expandItem(param_name) for data_name in values: self.item_list_treeWidget.expandItem( QtGui.QTreeWidgetItem(param_name, [data_name[0], data_name[1], data_name[2]])) param_name.setExpanded(False) def update_graph_after_log_changed(self): # after load_data_tree data_plotting = OrderedDict() if self.data_plotting: data_keys, data_values = list(self.data_dict.keys()), list(self.data_dict.values()) for item_id, item in self.data_plotting.items(): item_label, showed, curve = item t, data = curve.xData, curve.yData parent_name, data_name = item_label.split('->') found = False if parent_name in data_keys: for item in data_values[data_keys.index(parent_name)]: if data_name == item[0]: found = True if found: data_index = list(list(self.data_dict.keys())).index(parent_name) t = self.log_data_list[data_index].data['timestamp']/10**6 data = self.log_data_list[data_index].data[data_name] curve.setData(t, data) data_plotting[item_id] = [item_label, showed, curve] self.data_plotting = data_plotting self.update_graph() def quadrotor_win_closed_event(self, closed): if closed: self.quadrotor_widget_isshowed = not self.quadrotor_widget_isshowed self.show_quadrotor_3d.setIcon(QtGui.QIcon(get_source_name('icons/quadrotor.gif'))) def draw_predefined_graph(self, name): def add_context_action(ax): def callback(*args, **kargs): for item in ax.items(): if isinstance(item, pg.PlotDataItem): if item.opts['symbol'] is None: item.setData(item.xData, item.yData, symbol='s') else: item.setData(item.xData, item.yData, symbol=None) return callback if name == 'XY_Estimation': graph_xy = pg.GraphicsLayoutWidget() self.default_tab.addTab(graph_xy, name) ax = graph_xy.addPlot(row=0, col=0) show_marker_action = QtGui.QAction('show/hide marker', graph_xy) show_marker_action.triggered.connect(add_context_action(ax)) data_index = list(list(self.data_dict.keys())).index('vehicle_local_position') x = self.log_data_list[data_index].data['x'] y = self.log_data_list[data_index].data['y'] # plot the xy trace line in red ax.plot(x, y, pen=(255, 0, 0)) def closeEvent(self, *args, **kwargs): if self.analysis_graphs_showed: self.analysis_graphs_pane.close() if self.params_pane_showed: self.params_pane.close() if self.info_pane_showed: self.info_pane.close() if self.help_pane_showed: pass return QtGui.QMainWindow.closeEvent(self, *args, **kwargs)