【python第三方库】使用asammdf处理mf4文件

参考链接:
https://www.cnpython.com/pypi/asammdf
https://blog.csdn.net/weixin_44808082/article/details/107603221
https://blog.csdn.net/IHTY_NUI/article/details/125053403

文章目录

  • asammdf库介绍
    • 目标
    • 功能
    • 用法
    • 安装
  • 代码示例
    • 示例1
    • 示例2
    • 示例3
  • 补充阅读

asammdf库介绍

ASAMMDF是ASAM(自动化和测量系统标准化协会)MDF(测量数据格式)文件的快速解析器编辑器

asammdf支持MDF版本2(.dat)、3(.mdf)和4(.mf4)。

asammdf适用于python>;=3.6(对于python 2.7、3.4和3.5,请参阅4.x.y版本)

目标

这个库的主要目标是:

  • 比其他基于python的mdf库更快
  • 使代码库简洁易懂
  • 使第三方的依赖性最小

功能

  • 创建新的MDF从头开始的文件

  • 附加新频道

  • 读取未排序的mdf v3和v4文件

  • 读取can总线日志文件

  • 从匿名can总线记录测量中提取can信号

  • 从原始MDF文件中筛选频道子集

  • 将测量值切割到指定的时间间隔

  • 转换为不同的MDF版本

  • 导出到HDF5、Matlab(v4、v5和v7.3)、CSV和Parquet

  • 合并共享同一内部结构的多个文件

  • 读取并保存包含压缩数据块的MDF 4.10版文件

  • 保存文件的空间优化(无重复块)

  • 为MDF版本4拆分大数据块(可配置大小)

  • 完全支持以下映射类型(多维数组通道):

    • 带CDBlock的MDF版本3频道

    • MDF版本4结构通道组成

    • MDF版本4通道阵列,具有CNTemplate存储和其中一种阵列类型:

      • 0-数组
      • 1-缩放轴
      • 2-查找
  • 添加和提取MDF版本4的附件

  • 处理大文件(例如在raspberrypi上合并两个文件,每个文件具有14000个通道和5gb大小)

  • 将通道数据、主通道和额外通道信息提取为信号对象,以便使用v3和v4文件进行统一操作

  • 使用信号的时域操作类

    • 如果所有通道都基于同一时间,则pandas数据帧是好的
    • 测量通常有来自不同来源的不同速率的通道
    • 信号类有助于使用此类通道进行操作
  • 图形界面,用于可视化频道并对文件执行操作

用法

from asammdf import MDF 
mdf=MDF('sample.mdf')
speed=mdf.get('WheelSpeed')
speed.plot()
important_signals=['WheelSpeed','VehicleSpeed','VehicleAcceleration']

# get short measurement with a subset of channels from 10s to 12s
short=mdf.filter(important_signals).cut(start=10,stop=12)

# convert to version 4.10 and save to disk
short.convert('4.10').save('important signals.mf4')

# plot some channels from a huge file
efficient=MDF('huge.mf4') 
for signal in efficient.select(['Sensor1','Voltage3']):
	signal.plot()

查看examples文件夹以获取扩展使用演示或文档
官方文档

库的解析GUI工具,这个很实用,具体位置在scripts中有个asammdf.exe的文件

在文档中,GUI一节,教你怎样用。还有动画显示,一看就明白

安装

pip install asammdf
当然它依赖许多库,主要是pumpy(一切数据的基础) , pandas(适于作图的数据), pyqtgraph(作图), pyqt5(搞界面的)

代码示例

示例1

Python读取和处理mf4

# 处理mf4数据
# 20220530 FQF

# 引入库文件
from asammdf import MDF     # 用于处理MDF文件
import os       # 用于获取文件路径
import numpy as np      # 用于数学处理
import matplotlib.pyplot as plt     # 用于绘图

# 读取mf4
path = 'C:/fqf'
os.chdir(path)      # 更改python的工作空间
files = os.listdir(path)

for f in files:
    if '_decoded' in f and f.endswith('.mf4') and f.startswith('df'):      # 查找文件名字含有fish且以.png后缀的文件
        # print('Find the mf4: ' + f)
        # print(f.title())
        mf4_name = f.split()
        mdf = MDF(f)
        # print(mdf.info())

# 读取数据
        Prompt_info = mdf.get("InterSysInfoDisp")
        Prompt_value = ICA_Prompt_info.samples
        Prompt_timestamps = Prompt_info .timestamps

# 搜索工况
        i = 1
        while i < len(Prompt_timestamps )-1:
            if Prompt_value [i] != 9 & Prompt_value [i+1] == 9:       # 搜索工况
                print( "============================")
                print(f)
                print(Prompt_timestamps [i])
            i = i + 1

# 绘图
# plt.plot(Prompt_timestamps ,Prompt_value )#r表示颜色,v表示下三角线类型
# plt.xlabel('xlabel',fontsize = 25)
# plt.ylabel('ylabel',fontsize = 16)
# plt.grid()
# plt.show()

示例2

关于Python处理车辆控制单元MDF文件,其功能如下:

  1. 查找MDF文件中特定信号最大值;
   2. 计算平均车速和里程,使用matplotlib作图;
   3. 计算两信号差值,进行波峰判断并记录峰值个数,避免信号毛刺干扰,设定差值门限值,使用matplotlib作图(含主次坐标);
  2. 以上信息直接写入word保存。
"""
    名称:  CheckMDF.py
    作者:  Morven_Xie
    版本:  1.1
    时间:  2020/7/21 1:49
    更新:  2020/8/2 1:35
    功能:  用于车辆排查车辆VCU记录的MDF文件
    简介:  ·温度信号A峰值
            ·温度信号A与油底壳油温温度信号B差值大于门限值的峰值数量及时间
            ·通过车速信号计算车辆行驶里程
    Email:  [email protected]
"""


# coding=utf-8
from   asammdf import MDF                                    # 用于处理MDF文件
import os                                                    # 用于获取文件路径
import pandas as pd                                          # 用于数据分析
import numpy as np                                           # 用于数组处理
from numpy import trapz                                      # 可用于积分求面积
from matplotlib import pyplot as plt                         # 用于作图 
import datetime as dt                                        # 用于调用系统时间
import docx                                                  # 调用word文档库
from docx.shared import Inches                               # 用于设置图片尺寸


def check_MDF(Document_Adress):                              # 定义check_MDF函数
    print('文件名: '+Adress_str+',相关统计信息如下:')       # 提取地址中文件名信息,打印

    new_Doc = document.add_paragraph\
        ('文件名: '+Adress_str+',相关统计信息如下:')        # 新增段落文本


    MDF_File=MDF(Document_Adress)                            # 使用asammdf第三方包调取.mdf文件内容
    TCOil= MDF_File.get("TCOil")                             # 调用.mdf中零件处油温Signal   
    TOil=MDF_File.get("TOil")                                # 调用.mdf中油底壳温度Signal
    VehSpd=MDF_File.get("VehSpd")                            # 调用.mdf中车速Signal

    VehSpdCheck(VehSpd)                                      # check_MDF()函数调用VehSpdCheck()函数
    Signal_Data_Max(TCOil)                                   # check_MDF()函数调用Signal_Data_Max()函数
    T_Subtract_Peak(TCOil, TOil, 20)                         # check_MDF()函数调用T_Subtract_Peak()函数

    document.save(Veh_VIN+'.docx')                           # 运行完程序后保存word文档

def timeconvert(second):                                     # 将时间秒转化str类型的时分秒
    Timestr =str(int(second/3600)) + 'h ' + \
             str(int((second%3600)/ 60)) + 'min ' + \
             str(int((second%3600) % 60)) + 's'
    return Timestr

def Signal_Data_Max(Signal):                                 # 定义Signal_Data_Max()函数
    x=Signal.timestamps                                      # 提取Signal中所有时刻值存放列表x()中
    y=Signal.samples                                         # 提取Signal中所有实测值存放列表y()中
    T_C_Max=np.max(y)                                        # 使用Numpy库中max()函数求y中数据的最大值
    Positon=np.where(y==np.max(y))                           # 找到y最大值对应的索引值(可理解为序号)
    Time=(x[Positon[0][0]])                                  # 提取索引值对应时刻

    print('零件处油温最大值%d' %T_C_Max+\
          '时间是'+timeconvert(Time))                        # 打印零件处油温最大值及对应的时间(秒转化为分钟)

    new_Doc = document.add_paragraph\
        ('零件处油温最大值为 '+str(T_C_Max)+' ℃,'\
         '时间是'+timeconvert(Time),style='List Bullet')     # 新增段落文本

def VehSpdCheck(Singal):                                     # 定义Signal_Data_Max()函数
    x=Singal.timestamps                                      # 提取Signal中所有时刻值存放列表x()中
    #print(Singal.display_name)
    y=Singal.samples                                         # 提取Signal中所有实测值存放列表y()中
    Average_Vehspd=np.sum(y)/len(y)                          # 使用Numpy库中sum()函数求和后除以点的个数
    Drive_Distance=Average_Vehspd*x[-1]/3600                 # 行驶里程通过均值与时间乘积获得
    print('该文件平均车速 %.2f km/h,' %Average_Vehspd+\
    '行驶里程 %.2f km' %Drive_Distance)                      # 打印平均车速和行驶里程(使用换行\)

    Average_Vehspd_str=str(round(Average_Vehspd, 2))         # 平均速度保留两位,转化为字符串
    Drive_Distance_str =str(round( Drive_Distance, 2))       # 行驶里程保留两位,转化为字符串
    new_Doc = document.add_paragraph\
        ('该文件平均车速 '+ Average_Vehspd_str+' km/h,'+\
         '行驶里程= '+Drive_Distance_str+' km'+',见下图:',style='List Bullet')
                                                             # 新增段落文本

    plt.figure(Adress_str+'--'+Singal.display_name)          # 图表命名为文件名加信号名称
    plt.axis([np.min(x),np.max(x),0,np.max(y)])              # 使用pyplot函数定义x轴和y轴最大及最小值
    plt.plot(x,y,label='$VehSpd$')                           # 使用pyplot作图,图例为VehSpd
    plt.xlabel('t (s) ')                                     # 增加X轴-坐标轴标题
    plt.ylabel('VehicleSpeed (km/h) ')                       # 增加y轴-坐标轴标题

    plt.fill_between(x,y1=y,y2=0,facecolor='purple',\
                     alpha=0.2)                              # 对y与x围成面积着色,展示速度求积分→里程


    plt.text(x[-1]/3,np.max(y)-3, 'DriveDistance:%.2f km' \
             %Drive_Distance, \
             fontdict={'size': 10, 'color': 'red'})          #  x轴坐标,y轴坐标,显示内容,字体大小、颜色

    plt.title(Adress_str)                                    # 将全局变量信息增加入表头
    #plt.title(Adress_str+'__'+'(DriveDistance: %.2f '%Drive_Distance+' km)')
    plt.legend(loc='upper right')                            # 图例选择最佳位置,可选择best
    #plt.show()                                              # 显示图例
    picture_name=Adress_str +'-'+ \
                 Singal.display_name+'.png'                  # 图片保存时名称
    plt.savefig(picture_name, dpi=200)                       # 保存图片

    new_Doc = document.add_picture(picture_name, width=Inches(5.0))
                                                             # 图片写入word
    os.remove(picture_name)                                  # 删除图片文件

def T_Subtract_Peak(T1,T2,threshold):
    # T1.plot()                                              # 使用ASAMMDF自带Plot制图
    # T2.plot()
    x = T1.timestamps                                        # 提取信号T1中所有时刻值的点存放列表x()中
    y1 = T1.samples                                          # 提取信号T1中所有信号值的点存放列表y()中
    y2 = T2.samples                                          # 提取信号T2中所有信号值的点存放列表y()中
    k = len(x)                                               # 统计数据的总个数

    a = 0                                                    # 定义数字a
    i = 0                                                    # 定义数字i
    m = 0                                                    # 定义数字m

    T_Up = []                                                # 定义T_Up为列表
    for a in range(0, k):
        T_Up.append(y1[a] - y2[a])                           # 将所有温差点写入T_Up
    y3 = T_Up

    while i < k - 1:                                         # 从0到k,使用while循环,条件满足一直执行
        if int(T1.samples[i] - T2.samples[i]) >= \
                threshold:                                   # T1和T2在某时刻温差大于门限值,执行一次
            j = i                                            # 过滤信号毛刺,寻找大于门限值波峰个数
            while j < k - 1:
                j += 1
                if int(T1.samples[j] - \
                       T2.samples[j]) < threshold - 3:        # 设置差值为2作为过滤信号
                    m = m + 1                                 # 记录波峰个数
                    time = int(j / k * T1.timestamps[-1] / 60)
                    print('该文件零件处油温与油底壳温差大于' + str(threshold) + '的波峰第 %d 处,' % m + \
                          '时间是 %d s,' % time + '零件处油温 %d ℃' % T1.samples[j])

                    new_Doc = document.add_paragraph \
                        ('该文件零件处油温与油底壳温差大于'\
                         + str(threshold) + '℃的波峰第' +\
                         str(m) + '处,时间是' + timeconvert(time))
                                                              # 新增段落文本

                    i = j                                     # 局部变量i更新为j

                    break                                     # 循环退出
        i = i + 1                                             # 以上程序不执行或者退出,执行i自加

    plt.figure(Adress_str + '—' + T1.display_name +
               '-' + T1.display_name)                         # 图表命名为文件名加两信号名称

    fig = plt.figure(num=1, figsize=(15, 8), dpi=80)         # 开启一个窗口,同时设置大小,分辨率
    fig, ax1 = plt.subplots()                                # 作1个图
    ax2 = ax1.twinx()                                        # 坐标轴2为次要坐标
    ax1.plot(x, y1, '-', color='g', label='TCOil')           # 绘制曲线y1,含线型、颜色、图例名称
    ax1.plot(x, y2, '-', color='b', label='TOil')            # 绘制曲线y2
    ax2.plot(x, y3, '-', color='r', label='T_Up')            # 绘制曲线y3
    ax1.legend(loc='upper left')                             # 显示图例位置,plt.legend(),左上
    ax2.legend(loc='upper right')                            # 显示图例位置,右上

    # ax1.set_xlim(-5,5)
    ax1.set_ylim(np.min(y2) - 5, np.max(y1) + 5)             # 设定y1轴最大最小值
    ax2.set_ylim(np.min(y3) - 5, np.max(y3) + 5)             #  同上

    ax1.set_title(Adress_str)                                # 图表表头

    ax1.set_xlabel('time(s)')                                # 设定x轴标题
    ax1.set_ylabel("TC_Oil & T_Oil (℃)", color='b')         #  设定y1轴标题
    ax2.set_ylabel("T_Up(℃)", color='r')                    # 设定y2轴标题
    # plt.show()                                             # 参考之前注释

    picture_name = Adress_str + '-' + T1.display_name + '-' + T2.display_name +'.png'
    plt.savefig(picture_name, dpi=100)                       # 保存图片文件到文件夹

    new_Doc = document.add_picture(picture_name, width=Inches(6.0))
                                                             # 保存图片到word
    os.remove(picture_name)                                  # 删除图片文件

file_path = 'D:\SoftApp\Python\HardWay2StudyPython\MDFData'  # 文件所在文件夹
now_time=dt.datetime.now().strftime('%F  %T')                # 调用系统时间
Veh_VIN=input("请在下方输入故障排查车辆VIN数字编号后,回车:") # 提示输入车辆信息
print(Veh_VIN+"车辆"+'数据分析时间:'+now_time)               # 打印输出车辆和系统时间

document = docx.Document()                                  # 创建word文档对象
document.add_heading(Veh_VIN+'车排查信息', 0)                # 添加文档标题
new_Doc= document.add_paragraph \
    ('主要排查行驶里程、最高油温、\
    零件处与变速箱油温差值出现的峰值数。 ')                    # 新增段落文本,字符\用于换行
new_Doc=document.add_paragraph\
     (Veh_VIN+"车辆"+'数据分析时间:'+ now_time)              # 新增段落文本


for root, dirs, files in os.walk(file_path):                 # os.walk返回三个对象: dirpath(目录路径,string类型) ;
                                                             # dirname(多个子目录名,列表); filename(多个文件名,列表)
    Documents=[os.path.join(root, name) for name in files]   # 遍历文件名存放list(列表)类型的Documets

for Document_Path in Documents:                              # 遍历文件名对应的地址(假设有3个文件地址)
    # print(Document_path.info())                            # 打印MDF文件信号,例如可以看到有哪些信号
    print('-'*60)                                            # 打印分割行

    new_Doc = document.add_paragraph('-'*100)                # 新增段落文本,字符\用于换行
    new_Doc = document.add_paragraph('-' * 100)              # 新增段落文本,字符\用于换行

    Adress=Document_Path.split('\\')[-1]                     # 提取路径中的含后缀的文件名
    Adress_str=Adress.split('.')[0]                          # 提取路径中的文件名

    check_MDF(Document_Path)                                 # 调用def check_MDF()函数


示例3

功能车辆行车数据采集设备记录多文件MF4,特定功能下触发,记录该段数据的最大横纵向加速,并作图。

"""
Check the testdata from ADAS_MDF
"""
# coding=utf-8
import os
from asammdf import MDF
from matplotlib import pyplot as plt

# read the document from the file
def Read_Docmt_Addr():
    File_Path='D:\SoftApp\Python\MDF_ADAS\MF4_Data'
    Document_Name=os.listdir(File_Path)
    Document_Address=[]
    for i in range(0,len(Document_Name)):
        Document_Address.append(File_Path+'/'+Document_Name[i])
    return Document_Address

# Filter the signal and plot the accelerate data when auto lane change
def Data_Filter_Function(Document_Addr):
    print(Document_Addr.split('/')[-1])
    MDF_File = MDF(Document_Addr)
    Signal_VehSpd = MDF_File.get("VehSpd_Signal_Name")
    Signal_Turn_Lamp = MDF_File.get("Signal_Turn_Lamp_Signal_Name")
    Signal_Turn_Lamp.plot()
    Signal_Lateral_Acc= MDF_File.get("Signal_Lateral_Acc_Signal_Name")
    Signal_Longitudinal_Acc= MDF_File.get("Signal_Longitudinal_Acc_Signal_Name")

    Signal_Data_Num=len(Signal_Turn_Lamp.timestamps)
    i=0
    Lamp_Threshold=6
    Signal_Burr=-3
    while i <Signal_Data_Num:
        if Signal_Turn_Lamp.samples[i]>=Lamp_Threshold:
            t1=Signal_Turn_Lamp.timestamps[i]
            j=i
            while j <Signal_Data_Num:
                if Signal_Turn_Lamp.samples[j] <Lamp_Threshold+Signal_Burr:
                    t2 = Signal_Turn_Lamp.timestamps[j]
                    Delta_t=t2-t1
                    print('换道时间:%.2f s'% (Delta_t))
                    # print(Signal_Lateral_Acc.samples[i])
                    MaxValue_lateral = max(Signal_Lateral_Acc.samples[i:j])
                    MaxValue_longitudinal = max(Signal_Longitudinal_Acc.samples[i:j])
                    print('最大横向和纵向加速度分别是:%.2f m/s^2和 %.2f m/s^2' %(MaxValue_lateral,MaxValue_longitudinal))
                    MDF_file_New = MDF_File.cut(Signal_Turn_Lamp.timestamps[i], Signal_Turn_Lamp.timestamps[j])
                    Filter_Signal_Figure(MDF_file_New)
                    i = j
                    break
                else:
                    j = j + 1
        i = i + 1

def Filter_Signal_Figure(MDF_file):
    Signal_Lateral_Acc_New = MDF_file.get("Signal_Lateral_Acc_Signal_Name")
    Signal_Longitudinal_Acc_New = MDF_file.get("Signal_longitudinal_acc_Signal_Name")
    x_axis = Signal_Lateral_Acc_New.timestamps
    y_axis_1 = Signal_Lateral_Acc_New.samples
    y_axis_2 = Signal_Longitudinal_Acc_New.samples

    plt.figure(str('变道时横纵向加速'))
    fig = plt.figure(num=1, figsize=(15, 8), dpi=100)
    ax = fig.add_subplot(1, 1, 1)
    plt.xlabel('t(s)')
    ax.set_axis_on()
    ax.plot(x_axis, y_axis_1, '-', color='g', label='Lateral_Acc')
    ax.legend(loc='upper left')
    ax.set_ylabel('Lateral(m/s^2)')
    ax2 = ax.twinx()
    ax2.plot(x_axis, y_axis_2, '-', color='b', label='Longitudinal_Acc')  # 绘制曲线y2
    ax2.legend(loc='upper right')
    ax2.set_ylabel('Longitudinal(m/s^2)')

    plt.savefig('Figure')
    plt.show()
    plt.close('all')

if __name__ == "__main__":
    for Addr in Read_Docmt_Addr():
        Data_Filter_Function(Addr)

读取MDF文件所有信号的名称。

# Get signal name from MDF Document
 def Get_Signal_Name(MDF_File_Address):
     MDF_File = MDF(MDF_File_Address)
     Singal_Library=MDF_File.info()
     Signal_List=[]
     Signal_List_Number = Singal_Library['groups']
     for i in range(0,Signal_List_Number):
         Signal_List.append(Singal_Library[str('group '+str(i))]['channel 1'].split('"')[1])
     print(Signal_List)

补充阅读

blog

你可能感兴趣的:(【自动驾驶】,【python】,python,开发语言)