python解析mdf或mf4文件利器之a(chǎn)sammdf用法
安裝
pip install asammdf
解析
1. 讀取文件,獲取信號(hào)
from asammdf import MDF f = r"xxx.mdf" mdf = MDF(f) if '信號(hào)名' in mdf: #注意,一個(gè)mdf里可能有重名的信號(hào),這個(gè)時(shí)候要加上group_index和channel_index,比如mdf.get('信號(hào)名',3,2),至于后面兩個(gè)索引號(hào)怎么獲取,這個(gè)就需要看后面講的的channels_db了 signal = mdf.get('信號(hào)名') data = signal.samples timestamps = signal.timestamps
如果對(duì)mdf的格式不熟悉,可以百度下資料,本文主要介紹asammdf的使用。
2. 獲取所有信號(hào)名及其索引
chn_db = mdf.channels_db
3. mdf轉(zhuǎn)成dataframe
#代碼接上 df = mdf.to_dataframe()
這個(gè)方法有個(gè)坑,canape采集數(shù)據(jù)允許不同信號(hào)不同頻率,也就是說(shuō)數(shù)據(jù)對(duì)不齊,這種情況下調(diào)這個(gè)方法會(huì)導(dǎo)致爆內(nèi)存,所以使用時(shí)應(yīng)該注意一下,如果轉(zhuǎn)換時(shí)間過(guò)長(zhǎng),或者報(bào)錯(cuò),多數(shù)是信號(hào)采集頻率不同,如果事先就已經(jīng)知道頻率不同,只能用get方法獲取單個(gè)信號(hào),然后分析,或者用后面講到的方法先進(jìn)行數(shù)據(jù)對(duì)齊(重采樣),然后在進(jìn)行轉(zhuǎn)換。
4. 獲取channelgroup和chnannel
mdf文件一般是用channel和channel group組織的,一個(gè)文件可能包含多個(gè)chnannel group,一個(gè)channel group也可以包含多個(gè)channel,channel和signal一一對(duì)應(yīng),channel保存了一些描述信息,數(shù)據(jù)和時(shí)間戳保存在signal里,asammdf里提供了幾個(gè)方法來(lái)獲取信息,下面是一段從源碼里摘取的一段
for group_index, (virtual_group_index, virtual_group) in enumerate( mdf_ins.virtual_groups.items() ): if virtual_group.cycles_nr == 0 and empty_channels == "skip": continue channels = [ (None, gp_index, ch_index) for gp_index, channel_indexes in mdf_ins.included_channels( virtual_group_index )[virtual_group_index].items() for ch_index in channel_indexes if ch_index != mdf_ins.masters_db.get(gp_index, None) ]
從中可以看出,我們可以通過(guò)mdf.virtual_groups獲取到全部的channel group,virtual_groups是一個(gè)字典,所以可以通過(guò)items方法遍歷,然后通過(guò)mdf.included_channels(virtual_group_index)方法獲取到當(dāng)前channel group下包含的channels,這里我們注意到,他獲取的channels是一個(gè)元組列表,第一個(gè)都是None,第二個(gè)和第三個(gè)分別是channel group的index和channel的index,再看mdf.get()方法的參數(shù)其實(shí)有很多,前三個(gè)是signal name,group index,channel index,其實(shí)就是列表元組的三個(gè),因?yàn)閏hannel group、channel是一個(gè)二維結(jié)構(gòu),所以,其實(shí)可以不用信號(hào)名來(lái)獲取信號(hào),也可以通過(guò)group index,和channel index,然后第一個(gè)參數(shù)傳None,同時(shí),不同通道組中的通道名字可能相同,所以這時(shí),如果僅僅傳信號(hào)名,代碼會(huì)報(bào)錯(cuò),因?yàn)樗恢滥闳〉媚膫€(gè)組里的信號(hào),所以這時(shí)候就需要傳group index和channel index,但是如果一開始就用索引就不會(huì),因?yàn)檫@兩個(gè)索引會(huì)唯一確定一個(gè)信號(hào)。
5. 獲取一部分信號(hào)數(shù)據(jù)
基于上段的通道列表,我們就可以用mdf.select方法篩選出我們需要的信號(hào),信號(hào)和通道是一一對(duì)應(yīng)關(guān)系,我們可以把這兩個(gè)東西當(dāng)成一個(gè)東西理解,只不過(guò)兩種數(shù)據(jù)結(jié)構(gòu)存的數(shù)據(jù)不一樣,所以這里大家不要混淆或者蒙圈。上面獲取的其實(shí)是全部的信號(hào),其實(shí)也可以是部分。
old_signals = [ signal for signal in mdf_ins.select( channels, raw=True, copy_master=False, validate=False ) ]
mdf.select返回的是信號(hào)列表,而不是包含這些信號(hào)的mdf實(shí)列,mdf.filter()返回的是mdf實(shí)列,這里需要注意下,當(dāng)然select要保證傳入的信號(hào)列表都在mdf里,否則會(huì)報(bào)錯(cuò),另外不要用for循環(huán)+get('信號(hào)名')的方式獲取多個(gè)信號(hào)數(shù)據(jù),經(jīng)過(guò)測(cè)試,這個(gè)函數(shù)性能很差,獲取單個(gè)信號(hào)的數(shù)據(jù)還可以,信號(hào)一多最好用select,速度非常快。
還有那個(gè)raw參數(shù),雖然它這里用的True,但是,一般我們要設(shè)置為False,特別是自己處理數(shù)據(jù)的時(shí)候,因?yàn)樵O(shè)置成True會(huì)導(dǎo)致讀出的數(shù)據(jù)錯(cuò)誤,它之所以有這個(gè)參數(shù),我懷疑是為了兼容一些數(shù)據(jù)格式,而不是為了數(shù)據(jù)準(zhǔn)確性。
6. 數(shù)據(jù)合并
MDF類里有個(gè)靜態(tài)方法concatenate,用于多個(gè)文件的合并。
from asammdf import MDF mdf1 = MDF(r"f1.mf4") mdf2 = MDF(r"f2.mf4") mdf = MDF.concatenate([mdf1,mdf2])
concatenate是一個(gè)靜態(tài)方法,可以用類直接調(diào)用,這個(gè)方法用于兩個(gè)group和channel都一樣的文件進(jìn)行連接,即縱向連接。不支持包含不同信號(hào)的數(shù)據(jù)進(jìn)行橫向拼接 。
7. 數(shù)據(jù)過(guò)濾
mdf.filter(channels),這個(gè)上文已經(jīng)提到,它返回的是包含指定信號(hào)的mdf實(shí)例,而不是信號(hào)列表。
8. 數(shù)據(jù)導(dǎo)出
asammdf支持把mdf轉(zhuǎn)成其它格式,比如csv,hdf5,mat,parquet。
mdf.export(format,filename)
9. 數(shù)據(jù)繪圖
asammdf支持用信號(hào)數(shù)據(jù)繪圖,下面是官方示例
# map signals xs = np.linspace(-1, 1, 50) ys = np.linspace(-1, 1, 50) X, Y = np.meshgrid(xs, ys) vals = np.linspace(0, 180.0 / np.pi, 100) phi = np.ones((len(vals), 50, 50), dtype=np.float64) for i, val in enumerate(vals): phi[i] *= val R = 1 - np.sqrt(X**2 + Y**2) samples = np.cos(2 * np.pi * X + phi) * R timestamps = np.arange(0, 2, 0.02) s_map = Signal( samples=samples, timestamps=timestamps, name="Variable Map Signal", unit="dB" ) s_map.plot()
我們看到plot方法是掛在Signal上的,所以,我們用mdf.get('signame')獲取到一個(gè)信號(hào),也可以直接繪圖,值得注意的是,信號(hào)是支持直接計(jì)算的,再看一個(gè)官方的例子
import numpy as np from asammdf import Signal # create 3 Signal objects with different time stamps # unit8 with 100ms time raster timestamps = np.array([0.1 * t for t in range(5)], dtype=np.float32) s_uint8 = Signal( samples=np.array([t for t in range(5)], dtype=np.uint8), timestamps=timestamps, name="Uint8_Signal", unit="u1", ) # int32 with 50ms time raster timestamps = np.array([0.05 * t for t in range(10)], dtype=np.float32) s_int32 = Signal( samples=np.array(list(range(-500, 500, 100)), dtype=np.int32), timestamps=timestamps, name="Int32_Signal", unit="i4", ) # float64 with 300ms time raster timestamps = np.array([0.3 * t for t in range(3)], dtype=np.float32) s_float64 = Signal( samples=np.array(list(range(2000, -1000, -1000)), dtype=np.int32), timestamps=timestamps, name="Float64_Signal", unit="f8", ) prod = s_float64 * s_uint8 prod.name = "Uint8_Signal * Float64_Signal" prod.unit = "*" prod.plot() pow2 = s_uint8**2 pow2.name = "Uint8_Signal ^ 2" pow2.unit = "u1^2" pow2.plot() allsum = s_uint8 + s_int32 + s_float64 allsum.name = "Uint8_Signal + Int32_Signal + Float64_Signal" allsum.unit = "+" allsum.plot() # inplace operations pow2 *= -1 pow2.name = "- Uint8_Signal ^ 2" pow2.plot() # cut signal s_int32.plot() cut_signal = s_int32.cut(start=0.2, stop=0.35) cut_signal.plot()
我們可以看到,信號(hào)是可以相乘,相加,平方操作的,操作返回應(yīng)該還是信號(hào)類型,因?yàn)楹竺嫖覀兛吹剿{(diào)用了plot方法,這個(gè)方法是掛在Signal上面的。
10. 數(shù)據(jù)重采樣
canape支持不同信號(hào)的采集頻率不同,如果不同信號(hào)數(shù)據(jù)量不同,也就是說(shuō)數(shù)據(jù)不對(duì)齊,這樣在轉(zhuǎn)成dataframe的時(shí)候就會(huì)爆內(nèi)存,再比如頻率如果過(guò)高的話,數(shù)據(jù)量必然特別大,這樣的話也不利于分析,這時(shí)就需要我們對(duì)數(shù)據(jù)進(jìn)行重新采樣。asammdf有個(gè)resample方法。
下面是個(gè)例子:
from asammdf import MDF from utils import get_lines_from_txt f = r"C:\Users\c-master1\Desktop\下載\AnalysisTools\YIQI_2022-08-06_14-37-50.mf4" f1 = r"C:\Users\c-master1\Downloads\2022-11-21_16-59-29.mf4" f2 = r"C:\Users\c-master1\Downloads\Recorder_2022-11-17_14-55-00.MDF" clist_f = r"C:\Users\c-master1\Downloads\數(shù)據(jù)分析用信號(hào)列表(2)(2).txt" channel_list = ['ABV_Ki_Out','ACsurge_Flag_b','csABPV_posVlv_perc'] channel_list = get_lines_from_txt(clist_f) mdf = MDF(f2) mdf = mdf.filter(channel_list) signals = mdf.select(channel_list) max = 0 name = "" for sig in signals: if len(sig.samples) > max: max = len(sig.samples) name = sig.name mdf1 = mdf.resample(raster=name) df1 = mdf1.to_dataframe() df1
resample方法支持按某個(gè)信號(hào)進(jìn)行重采樣,即所有其它信號(hào)按這個(gè)信號(hào)對(duì)齊,也支持手動(dòng)寫個(gè)頻率,比如0.1就是0.1秒一個(gè)數(shù)據(jù),還支持傳一個(gè)數(shù)組,采集指定時(shí)間點(diǎn)的數(shù)據(jù),進(jìn)行重采樣后數(shù)據(jù)就對(duì)齊了,這時(shí)就可以轉(zhuǎn)成dataframe了,對(duì)于升采樣,有個(gè)需要注意的問(wèn)題,一般mf4保存的是數(shù)字,當(dāng)然也可以是字符串,對(duì)于數(shù)值型數(shù)據(jù),主要分兩種,也就是整數(shù)和小數(shù),也叫整型和浮點(diǎn)型,在asammdf中,對(duì)于整數(shù)和小數(shù)的默認(rèn)重采樣策略是不同的,整數(shù)的話默認(rèn)是重復(fù)之前的值,而對(duì)于浮點(diǎn)型也就是小數(shù)的話,默認(rèn)是線性插值,所以,有的時(shí)候你會(huì)發(fā)現(xiàn)重采樣后數(shù)據(jù)好像被改了,其實(shí)就是線性插值導(dǎo)致的,如果想改變默認(rèn)行為,可以用
mdf.configure(float_interpolation=0,integer_interpolation=0)
來(lái)改變,其中mdf是MDF的實(shí)例,針對(duì)浮點(diǎn)型,asammdf支持兩種重采樣策略,一種是重復(fù)之前的值,一種就是線性插值,對(duì)于整型,提供了三種策略,前面兩種和浮點(diǎn)型一樣,還有一種混合策略,就是結(jié)合重復(fù)前值和線性插值的特點(diǎn),具體沒(méi)有試過(guò)。
還有一個(gè)坑需要注意,就是表面上我們看到數(shù)據(jù)里存的是整數(shù),但是它的類型是Double的,這種asammdf也會(huì)把它當(dāng)浮點(diǎn)型處理,因?yàn)樗话磾?shù)據(jù)類型處理,不管你存的具體數(shù)據(jù)到底是整數(shù)還是小數(shù)。
mdf/mf4文件創(chuàng)建
1. mdf支持從零創(chuàng)建mdf/mf4文件
下面是官方示例
# -*- coding: utf-8 -*- """ *asammdf* MDF usage example """ import numpy as np from asammdf import MDF, Signal # create 3 Signal objects timestamps = np.array([0.1, 0.2, 0.3, 0.4, 0.5], dtype=np.float32) # unit8 s_uint8 = Signal( samples=np.array([0, 1, 2, 3, 4], dtype=np.uint8), timestamps=timestamps, name="Uint8_Signal", unit="u1", ) # int32 s_int32 = Signal( samples=np.array([-20, -10, 0, 10, 20], dtype=np.int32), timestamps=timestamps, name="Int32_Signal", unit="i4", ) # float64 s_float64 = Signal( samples=np.array([-20, -10, 0, 10, 20], dtype=np.float64), timestamps=timestamps, name="Float64_Signal", unit="f8", ) # create empty MDf version 4.00 file with MDF(version="4.10") as mdf4: # append the 3 signals to the new file signals = [s_uint8, s_int32, s_float64] mdf4.append(signals, comment="Created by Python") # save new file mdf4.save("my_new_file.mf4", overwrite=True) # convert new file to mdf version 3.10 mdf3 = mdf4.convert(version="3.10") print(mdf3.version) # get the float signal sig = mdf3.get("Float64_Signal") print(sig) # cut measurement from 0.3s to end of measurement mdf4_cut = mdf4.cut(start=0.3) mdf4_cut.get("Float64_Signal").plot() # cut measurement from start of measurement to 0.4s mdf4_cut = mdf4.cut(stop=0.45) mdf4_cut.get("Float64_Signal").plot() # filter some signals from the file mdf4 = mdf4.filter(["Int32_Signal", "Uint8_Signal"]) # save using zipped transpose deflate blocks mdf4.save("out.mf4", compression=2, overwrite=True)
當(dāng)然,從官方的示例中還可以獲得一些其它信息,比如,文件的裁剪,mdf.cut(**args)方法.
2. 也支持從dataframe創(chuàng)建
import pandas as pd import numpy as np df = pd.DataFrame({ 'a': np.arrange(10), 'b': np.arrange(10), 'c': np.arrange(10), }) mdf_f = MDF() mdf_f.append(df) mdf_f.save("xx.mf4",overwrite=True)
最后
asammdf其實(shí)還有一個(gè)gui工具,用pip install asammdf[gui]命令即可安裝,圖形界面實(shí)現(xiàn)了一些canape的功能,感覺還是很強(qiáng)大的,感興趣的可以用下試試。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Python 3.x基于Xml數(shù)據(jù)的Http請(qǐng)求方法
今天小編就為大家分享一篇Python 3.x基于Xml數(shù)據(jù)的Http請(qǐng)求方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-12-12python中dict獲取關(guān)鍵字與值的實(shí)現(xiàn)
這篇文章主要介紹了python中dict獲取關(guān)鍵字與值的實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-05-05Django城市信息查詢功能的實(shí)現(xiàn)步驟
Django中的查詢操作是數(shù)據(jù)庫(kù)操作中一個(gè)非常重要的技術(shù),下面這篇文章主要給大家介紹了關(guān)于Django城市信息查詢功能的實(shí)現(xiàn)步驟,文中通過(guò)實(shí)例代碼和圖文介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07使用python實(shí)現(xiàn)抓取騰訊視頻所有電影的爬蟲
這篇文章主要介紹了使用python實(shí)現(xiàn)抓取騰訊視頻所有電影的爬蟲,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-04-04Python批量實(shí)現(xiàn)橫屏轉(zhuǎn)豎屏的視頻處理工具
這篇文章主要為大家詳細(xì)介紹了如何使用Python和Tkinter框架開發(fā)一個(gè)視頻處理器應(yīng)用,用于批量橫屏轉(zhuǎn)豎屏視頻處理,支持多種視頻格式和編碼選擇,需要的可以了解下2025-02-02Nginx+Uwsgi+Django 項(xiàng)目部署到服務(wù)器的思路詳解
這篇文章主要介紹了Nginx+Uwsgi+Django 項(xiàng)目部署到服務(wù)器的思路,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05Python批量修改xml的坐標(biāo)值全部轉(zhuǎn)為整數(shù)的實(shí)例代碼
這篇文章主要介紹了Python批量修改xml的坐標(biāo)值全部轉(zhuǎn)為整數(shù)的實(shí)例代碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11