2025年4月23日 星期三

擴充版 PyQt5 IMU 顯示器

擴充版 PyQt5 IMU 顯示器 的功能設計與實作規劃,支援以下三大功能:


✅ 擴充功能目標

功能項目說明
🎬 即時播放(滑動窗格)自動每 100ms 顯示下一個視窗(例如 1 秒=100 筆)
🏷️ 顯示實際類別 (type)每個時間窗顯示對應的原始標籤
🤖 疊加分類預測結果使用 LSTM 模型做預測並與實際類別一同顯示

📦 完整 Python 程式碼

此版本含有:

  • 滑動視窗控制 (QTimer)

  • 實際 type 與 LSTM 預測結果顯示

  • 時域資料繪圖更新

python
import sys import pandas as pd import numpy as np from PyQt5.QtWidgets import ( QApplication, QWidget, QVBoxLayout, QPushButton, QFileDialog, QComboBox, QLabel ) from PyQt5.QtCore import QTimer from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.figure import Figure import torch import torch.nn as nn # 模擬用的簡易 LSTM 分類模型(請換成你訓練好的模型) class DummyLSTM(nn.Module): def __init__(self, input_dim=6, hidden_dim=64, num_layers=1, num_classes=4): super().__init__() self.lstm = nn.LSTM(input_dim, hidden_dim, num_layers, batch_first=True) self.fc = nn.Linear(hidden_dim, num_classes) def forward(self, x): out, _ = self.lstm(x) out = self.fc(out[:, -1, :]) return out class IMUGUI(QWidget): def __init__(self): super().__init__() self.setWindowTitle('IMU 時域顯示器(即時播放+預測)') self.resize(900, 700) self.data = None self.index = 0 self.fs = 100 self.seq_len = 100 self.timer = QTimer() self.timer.timeout.connect(self.next_frame) self.model = DummyLSTM().eval() self.device = torch.device('cpu') layout = QVBoxLayout() self.load_button = QPushButton('載入 CSV') self.play_button = QPushButton('▶️ 播放') self.combo = QComboBox() self.combo.addItems(['1 秒', '2 秒', '3 秒']) self.label = QLabel('目前未載入檔案') self.canvas = FigureCanvas(Figure()) self.ax = self.canvas.figure.add_subplot(111) layout.addWidget(self.load_button) layout.addWidget(self.play_button) layout.addWidget(self.combo) layout.addWidget(self.label) layout.addWidget(self.canvas) self.setLayout(layout) self.load_button.clicked.connect(self.load_csv) self.combo.currentIndexChanged.connect(self.set_seq_len) self.play_button.clicked.connect(self.toggle_play) def load_csv(self): fname, _ = QFileDialog.getOpenFileName(self, '選擇 CSV 檔', '', 'CSV files (*.csv)') if fname: self.data = pd.read_csv(fname) self.label.setText(f'已載入:{fname.split("/")[-1]}') self.set_seq_len() self.index = 0 self.update_plot() def set_seq_len(self): self.seq_len = self.fs * (self.combo.currentIndex() + 1) def toggle_play(self): if self.timer.isActive(): self.timer.stop() self.play_button.setText("▶️ 播放") else: self.timer.start(100) self.play_button.setText("⏸️ 暫停") def next_frame(self): if self.data is None: return self.index += 1 if self.index + self.seq_len > len(self.data): self.timer.stop() self.play_button.setText("▶️ 播放") self.index = 0 self.update_plot() def update_plot(self): if self.data is None or self.index + self.seq_len > len(self.data): return segment = self.data.iloc[self.index:self.index + self.seq_len] time = [i * 0.01 for i in range(self.seq_len)] self.ax.clear() colors = ['red', 'blue', 'green'] for i in range(3): # b1~b3 self.ax.plot(time, segment[f'b{i+1}'], color=colors[i], label=f'b{i+1}') for i in range(3): # b4~b6 self.ax.plot(time, segment[f'b{i+4}'], linestyle='--', color=colors[i], label=f'b{i+4}') # 顯示實際類別與預測類別 actual_label = segment['type'].iloc[-1] imu_values = segment[[f'b{i+1}' for i in range(6)]].values imu_tensor = torch.tensor(imu_values, dtype=torch.float32).unsqueeze(0) with torch.no_grad(): pred = self.model(imu_tensor).argmax().item() self.ax.set_title(f'第 {self.index} ~ {self.index+self.seq_len} 筆 | 實際: {actual_label} | 預測: {pred}') self.ax.set_xlabel('時間 (秒)') self.ax.set_ylabel('IMU 數值') self.ax.legend() self.canvas.draw() if __name__ == '__main__': app = QApplication(sys.argv) gui = IMUGUI() gui.show() sys.exit(app.exec_())

🧪 進階功能你可以加入:

  • ✅ 使用你訓練好的 LSTM 模型(替換 DummyLSTM

  • ✅ 使用 LabelEncoder.inverse_transform() 顯示類別名稱

  • ✅ 支援匯出預測結果、顯示混淆矩陣


沒有留言:

張貼留言