流程概述:
步骤 1:CAN报文数据解析;
步骤 2:数据可视化;
步骤 3:人工判定运行工况;
步骤 4:工况标注工具;
步骤 5:输出标注数据;
其中,重点在于步骤4,标注工具的开发,使用了Matplotlib和Tkinter做一个交互工具,并做了功能的调试和简单完善,在此仅做探索开发。
[Python] 纯文本查看 复制代码import tkinter as tk
from tkinter import ttk, messagebox
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import pandas as pd
from matplotlib.patches import Rectangle
# 示例数据
data = pd.DataFrame({
'time': range(100),
'speed': [10 + i * 0.2 for i in range(100)],
'throttle': [5 + i * 0.1 for i in range(100)],
'torque': [50 + i * 0.3 for i in range(100)]
})
LABEL_COLORS = {
"重载上坡": "red",
"重载下坡": "blue",
"空载平路": "green",
"空载下坡": "orange"
}
class AnnotationTool:
def __init__(self, root, data):
self.root = root
self.data = data
self.annotations = []
self.rects = []
self.start_time = None
self.end_time = None
self.current_rect = None
self.zoom_axis = 'x' # 默认缩放x轴
self.fullscreen = False # 全屏状态记录
# 初始化多子图结构
self.fig, self.axs = plt.subplots(nrows=len(data.columns) - 1, sharex=True, figsize=(8, 6))
# 逐列绘制数据
for i, col in enumerate(data.columns[1:]):
self.axs.plot(data['time'], data[col], label=col)
self.axs.legend()
self.axs.set_ylabel(col)
self.axs[-1].set_xlabel('Time')
# 嵌入Matplotlib到Tkinter中
self.canvas = FigureCanvasTkAgg(self.fig, master=self.root)
self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
self.canvas.mpl_connect("button_press_event", self.on_click)
self.canvas.mpl_connect("scroll_event", self.on_scroll)
self.canvas.mpl_connect("motion_notify_event", self.on_motion)
# 标签选择和布局
self.label_var = tk.StringVar(value="Select Condition")
self.label_menu = ttk.Combobox(root, textvariable=self.label_var, values=list(LABEL_COLORS.keys()))
self.label_menu.set("Select Condition")
# 创建按钮和布局
self.button_frame = tk.Frame(root)
self.save_button = tk.Button(self.button_frame, text="Save Annotations", command=self.save_annotations)
self.delete_button = tk.Button(self.button_frame, text="Delete Last Annotation",
command=self.delete_last_annotation)
self.zoom_in_button = tk.Button(self.button_frame, text="Zoom In", command=self.zoom_in)
self.zoom_out_button = tk.Button(self.button_frame, text="Zoom Out", command=self.zoom_out)
self.fullscreen_button = tk.Button(self.button_frame, text="Toggle Fullscreen", command=self.toggle_fullscreen)
# 将按钮横向排列
self.label_menu.pack(side=tk.LEFT, padx=5)
self.save_button.pack(side=tk.LEFT, padx=5)
self.delete_button.pack(side=tk.LEFT, padx=5)
self.zoom_in_button.pack(side=tk.LEFT, padx=5)
self.zoom_out_button.pack(side=tk.LEFT, padx=5)
self.fullscreen_button.pack(side=tk.LEFT, padx=5)
self.button_frame.pack(fill=tk.X, pady=5)
def on_click(self, event):
# 检查点击位置是否在子图内
if event.inaxes in self.axs:
ax = event.inaxes
# 如果是右键点击
if event.button == 3:
# 切换当前子图的纵轴缩放功能
if self.zoom_axis == 'y' and self.zoom_enabled_ax == ax:
self.zoom_axis = None
self.zoom_enabled_ax = None
print("Right-clicked again: Y-axis zoom disabled for this subplot")
else:
self.zoom_axis = 'y'
self.zoom_enabled_ax = ax
print("Right-clicked: Y-axis zoom enabled for this subplot")
# 如果是左键点击用于标注
elif event.button == 1:
label = self.label_var.get()
if label == "Select Condition":
messagebox.showwarning("Warning", "Please select a condition before annotating.")
return
if event.xdata is None:
print("Invalid click: event.xdata is None")
return
# 初始化或更新标注框的起始位置
if self.start_time is None:
self.start_time = event.xdata
self.current_rects = []
for ax in self.axs:
rect = Rectangle(
(self.start_time, ax.get_ylim()[0]), 0,
ax.get_ylim()[1] - ax.get_ylim()[0],
color='gray', alpha=0.3
)
ax.add_patch(rect)
self.current_rects.append(rect)
else:
self.end_time = event.xdata
self.add_annotation(self.start_time, self.end_time, label)
self.reset_selection()
def on_motion(self, event):
# 检查是否处于标注模式
if self.start_time is not None and event.xdata is not None:
# 更新每个子图的矩形宽度
width = event.xdata - self.start_time
for rect in self.current_rects:
rect.set_width(width)
self.canvas.draw()
else:
print("Invalid motion event: either start_time or event.xdata is None")
def on_scroll(self, event):
# 滚轮缩放
ax = self.axs[-1] if self.zoom_axis == 'x' else event.inaxes
if ax:
scale_factor = 1.1 if event.button == 'up' else 0.9
x_min, x_max = ax.get_xlim() if self.zoom_axis == 'x' else ax.get_ylim()
center = (x_max + x_min) / 2
delta = (x_max - x_min) * scale_factor / 2
if self.zoom_axis == 'x':
ax.set_xlim(center - delta, center + delta)
else:
ax.set_ylim(center - delta, center + delta)
self.canvas.draw()
def add_annotation(self, start, end, label):
color = LABEL_COLORS[label]
rect = self.axs[0].axvspan(start, end, color=color, alpha=0.3)
self.annotations.append((start, end, label))
self.rects.append(rect)
self.canvas.draw()
def reset_selection(self):
self.start_time = None
self.end_time = None
self.current_rect = None
def save_annotations(self):
df = pd.DataFrame(self.annotations, columns=['start_time', 'end_time', 'label'])
df.to_csv("annotations.csv", index=False)
messagebox.showinfo("Info", "Annotations saved successfully.")
def delete_last_annotation(self):
if self.annotations:
self.annotations.pop()
rect = self.rects.pop()
rect.remove()
self.canvas.draw()
else:
messagebox.showwarning("Warning", "No annotations to delete.")
def zoom_in(self):
for ax in self.axs:
x_min, x_max = ax.get_xlim()
ax.set_xlim(x_min + (x_max - x_min) * 0.1, x_max - (x_max - x_min) * 0.1)
self.canvas.draw()
def zoom_out(self):
for ax in self.axs:
x_min, x_max = ax.get_xlim()
ax.set_xlim(x_min - (x_max - x_min) * 0.1, x_max + (x_max - x_min) * 0.1)
self.canvas.draw()
def toggle_fullscreen(self):
self.fullscreen = not self.fullscreen
self.root.attributes("-fullscreen", self.fullscreen)
# 主程序入口
root = tk.Tk()
root.title("CAN Data Annotation Tool")
root.geometry("800x600")
app = AnnotationTool(root, data)
root.mainloop()