From e71547d3c8dd2704e880c84e8856b75540ae6ea5 Mon Sep 17 00:00:00 2001 From: Kiran Jojare Date: Mon, 22 Jul 2024 21:15:24 -0600 Subject: [PATCH] GUI With multi feature iteractive widow working and tested --- gui/gui_main.py | 198 +++++++++++++++++++++++++++ requirements.txt | 1 + src/__pycache__/main.cpython-312.pyc | Bin 0 -> 8737 bytes 3 files changed, 199 insertions(+) create mode 100644 gui/gui_main.py create mode 100644 src/__pycache__/main.cpython-312.pyc diff --git a/gui/gui_main.py b/gui/gui_main.py new file mode 100644 index 0000000..c3bdea8 --- /dev/null +++ b/gui/gui_main.py @@ -0,0 +1,198 @@ +import sys +import os +from PyQt5.QtWidgets import (QApplication, QMainWindow, QFileDialog, QVBoxLayout, QWidget, + QPushButton, QListWidget, QHBoxLayout, QSplitter, QLabel, + QComboBox, QInputDialog, QGridLayout) +from PyQt5.QtCore import Qt +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas, NavigationToolbar2QT as NavigationToolbar +from matplotlib.figure import Figure + +# Add the parent directory to sys.path +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from src.main import parse_dbc, parse_log + +class CANLogParserGUI(QMainWindow): + def __init__(self): + super().__init__() + + self.setWindowTitle("CAN Log Parser") + + self.dbc_file = None + self.log_file = None + self.db = None + self.parsed_data = None + self.subplots = [] + + self.initUI() + + def initUI(self): + """ + Initialize the GUI layout and components. + """ + main_widget = QWidget() + main_layout = QVBoxLayout() + main_widget.setLayout(main_layout) + + # Toolbar for actions + toolbar = QHBoxLayout() + + # Add buttons for loading files, clearing plots, and exporting plots + load_dbc_btn = QPushButton('Load DBC File') + load_log_btn = QPushButton('Load Log File') + clear_plots_btn = QPushButton('Clear Plots') + export_plot_btn = QPushButton('Export Plot') + + load_dbc_btn.clicked.connect(self.load_dbc_file) + load_log_btn.clicked.connect(self.load_log_file) + clear_plots_btn.clicked.connect(self.clear_plots) + export_plot_btn.clicked.connect(self.export_plot) + + toolbar.addWidget(load_dbc_btn) + toolbar.addWidget(load_log_btn) + toolbar.addWidget(clear_plots_btn) + toolbar.addWidget(export_plot_btn) + + main_layout.addLayout(toolbar) + + # Splitter for controls and plotting area + splitter = QSplitter(Qt.Horizontal) + + # Left panel for signal selection and controls + controls_layout = QVBoxLayout() + self.signal_list = QListWidget() + self.signal_list.setSelectionMode(QListWidget.MultiSelection) + + self.plot_button = QPushButton('Plot Signals') + self.clear_selection_button = QPushButton('Clear Selection') + self.plot_button.clicked.connect(self.plot_signals) + self.clear_selection_button.clicked.connect(self.clear_selection) + + controls_layout.addWidget(QLabel("Select Signals:")) + controls_layout.addWidget(self.signal_list) + controls_layout.addWidget(self.clear_selection_button) + controls_layout.addWidget(self.plot_button) + + controls_container = QWidget() + controls_container.setLayout(controls_layout) + splitter.addWidget(controls_container) + + # Right panel for plotting area + self.figure = Figure() + self.canvas = FigureCanvas(self.figure) + self.toolbar = NavigationToolbar(self.canvas, self) + + plot_layout = QVBoxLayout() + plot_layout.addWidget(self.toolbar) + plot_layout.addWidget(self.canvas) + + plot_container = QWidget() + plot_container.setLayout(plot_layout) + splitter.addWidget(plot_container) + + splitter.setSizes([200, 800]) + main_layout.addWidget(splitter) + + self.setCentralWidget(main_widget) + self.init_subplots(6) + + def init_subplots(self, num_subplots): + """ + Initialize a specified number of subplots. + """ + self.figure.clear() + self.subplots = [] + for i in range(num_subplots): + ax = self.figure.add_subplot(num_subplots, 1, i + 1) + self.subplots.append(ax) + self.canvas.draw() + + def load_dbc_file(self): + """ + Load the DBC file and parse it. + """ + options = QFileDialog.Options() + file_name, _ = QFileDialog.getOpenFileName(self, "Load DBC File", "", "DBC Files (*.dbc);;All Files (*)", options=options) + if file_name: + self.dbc_file = file_name + self.db = parse_dbc(self.dbc_file) + if self.db: + self.signal_list.clear() + for message in self.db.messages: + for signal in message.signals: + self.signal_list.addItem(signal.name) + + def load_log_file(self): + """ + Load the log file and parse it. + """ + options = QFileDialog.Options() + file_name, _ = QFileDialog.getOpenFileName(self, "Load Log File", "", "Log Files (*.txt);;All Files (*)", options=options) + if file_name: + self.log_file = file_name + self.parsed_data = parse_log(self.db, self.log_file) + + def plot_signals(self): + """ + Plot the selected signals based on user input. + """ + selected_signals = [item.text() for item in self.signal_list.selectedItems()] + if not selected_signals or not self.parsed_data: + return + + for i, signal_name in enumerate(selected_signals): + subplot_index = self.select_subplot() + ax = self.subplots[subplot_index] + + timestamps = [] + values = [] + for data in self.parsed_data: + for signal in data['signals']: + s_name, s_value, s_timestamp = signal + if s_name == signal_name: + timestamps.append(float(s_timestamp) / 1000) # Convert to seconds + values.append(float(s_value)) + + ax.plot(timestamps, values, label=signal_name) + ax.set_xlabel('Time (s)', fontsize=8) + ax.set_ylabel('Value', fontsize=8) + ax.tick_params(axis='both', which='major', labelsize=8) + ax.grid(True) + ax.legend(fontsize=6) + self.canvas.draw() + + def select_subplot(self): + """ + Select a subplot index for plotting based on user input. + """ + subplot_index, ok = QInputDialog.getInt(self, "Select Subplot", "Enter subplot number (1 to 6):", 1, 1, len(self.subplots), 1) + return subplot_index - 1 if ok else 0 + + def clear_selection(self): + """ + Clear the selected signals in the list. + """ + self.signal_list.clearSelection() + + def clear_plots(self): + """ + Clear all the plots. + """ + self.figure.clear() + self.init_subplots(6) + self.canvas.draw() + + def export_plot(self): + """ + Export the current plot as an image file. + """ + options = QFileDialog.Options() + file_name, _ = QFileDialog.getSaveFileName(self, "Export Plot", "", "PNG Files (*.png);;All Files (*)", options=options) + if file_name: + self.figure.savefig(file_name) + +if __name__ == '__main__': + app = QApplication(sys.argv) + gui = CANLogParserGUI() + gui.show() + sys.exit(app.exec_()) diff --git a/requirements.txt b/requirements.txt index b71227b..b2be542 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ cantools matplotlib +PyQt5 \ No newline at end of file diff --git a/src/__pycache__/main.cpython-312.pyc b/src/__pycache__/main.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..231cc2826c28229a02baf048f5310316be1b816d GIT binary patch literal 8737 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 C9+6D| literal 0 HcmV?d00001