利用Python处理TouchStone(snp)文件

这个Python类用于读取SNP文件,包含文件验证、元信息提取(如VNA型号、序列号、测试日期)、数据格式解析和转换(如从DB或MA到RI)。类还提供了数据导出到Excel和CSV的功能,并能根据指定端口生成新的SNP文件。支持的频率单位包括HZ、KHZ、MHZ、GHZ,数据格式包括SDBR、MA。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

读取文件信息

import re, os.path
import numpy as np
import pandas as pd

'''
[HZ/KHZ/MHZ/GHZ] [S/Y/Z/G/H] [MA/DB/RI] [R n] 
'''

class ReadSNP():
    __UNIT = {"HZ": 1, "KHZ": 1e3, "MHZ": 1e6, "GHZ": 1e9, "THZ": 1e12} # 频率单位
    def __init__(self,filename:str):
        self.file = filename
        self.VNA = ""
        self.VNA_SN = ""
        self.testDate = ""
        self.FormatInfo = ""
        self.Date_info = ""
        self.VNA_info = ""
        # 匹配是否为snp文件
        self.result = re.match(r".+\.s(\d{1,2})p$", os.path.basename(self.file),re.IGNORECASE)
        if self.result:
            self.PORTS = int(self.result.group(1))
        else:
            raise "文件不是合法的snp文件"
        self.ValidRowNo = 0

        try:
            # 找到前面文件的说明行的最后一行的位置。  说明行是以! 或 # 开头
            with open(self.file, "r") as f:
                content = f.readline()
                while content:
                    # 首先正则获得仪器的型号和序列号
                    # "!Keysight Technologies,M9801A,MY58100347,A.15.20.05"
                    # "! Rohde-Schwarz,ZNB8-4Port,1311601044102903,2.92"
                    re_VAN = re.match(r"^!\s?[a-zA-Z]{4,}.+,(.+),(.+),.+", content)
                    if re_VAN:
                        self.VNA_info = content
                        self.VNA = re_VAN.group(1)
                        self.VNA_SN = re_VAN.group(2)

                    # 然后正则匹配日期
                    # "!Date: Thursday, April 28, 2022 05:05:35"
                    # "! Created: UTC 4/30/2022, 1:02:06 AM"
                    re_date = re.match(r"^! Created:(.+)$|^!Date:(.+)$", content)
                    if re_date:
                        s = re_date.group(1) if re_date.group(1) else re_date.group(2)
                        s = s.strip()  # 去掉字符串首尾空格
                        from datetime import datetime
                        try:
                            datestr = datetime.strptime(s, '%A, %B %d, %Y %H:%M:%S')
                            self.Date_info = content
                            self.testDate = datestr
                        except:
                            pass
                        try:
                            datestr = datetime.strptime(s, '%a %B %d %H:%M:%S %Y')
                            self.Date_info = content
                            self.testDate = datestr
                        except:
                            pass
                        try:
                            datestr = datetime.strptime(s, "UTC %m/%d/%Y, %I:%M:%S %p")
                            self.Date_info = content
                            self.testDate =  datestr
                        except:
                            pass
                        if not self.testDate:
                            print("日期<{}>格式匹配错误".format(s))


                    if content.startswith("!"):
                        self.ValidRowNo += 1
                    elif content.startswith("#"):
                        self.ValidRowNo += 1
                        self.FormatInfo = content
                        # self.FormatInfo 典型内容 "# Hz S DB R 50" 。通过解析获取数据格式及频率单位
                        _,self.fre_unit, _, self.datamode,*a = self.FormatInfo.split() # a的内容可能是["R" ,"50"]
                    else:
                        break
                    content = f.readline()

        except:
            raise "snp读取失败!"

        with open(self.file, "r") as f:
            data = f.readlines()[self.ValidRowNo:]
        # data = " ".join(data).split("!")[0]  # 如果文件中含有噪声数据,则在数据段的后面可能还会有“!”开始的内容
        data = [float(x) for i in range(len(data)) for x in data[i].split()]
        # snp格式的文件,一个频点的数据量为n*n*2个
        data = np.array(data).reshape(len(data) // (2 * self.PORTS * self.PORTS + 1), 2 * self.PORTS * self.PORTS + 1)

        self.freq = data[...,0]*self.__UNIT.get(self.fre_unit.upper(),1)  # 按频率单位进行数据换算,统一转换为以HZ为单位。
        self.oddData = data[..., 1::2]
        self.evenData = data[..., 2::2]
        self.data = self.oddData + self.evenData * 1j

        #开始进行数据格式归一,统一转化为RI格式
        if self.datamode.upper() == "DB":
            print("将数据格式由DB转化为RI")
            self.data = self.transDataFromDB2RI(self.data)
        elif self.datamode.upper() == "MA":
            self.data = self.transDataFromMA2RI(self.data)
        self.oddData = self.data.real
        self.evenData = self.data.imag

    def transDataFromDB2RI(self,data):
        # 如果数据格式是DB,则将其转换为RI格式
        assert self.datamode.upper() == "DB","数据不是DB格式"
        r = np.power(10, data.real / 20)
        i = data.imag / 180 * np.pi
        data = r * np.cos(i) + r * np.sin(i) * 1j

        return data

    def transDataFromMA2RI(self,data):
        # 如果数据格式是MA,则将其转换为RI格式
        assert self.datamode.upper() == "MA", "数据不是MA格式"
        data = data.real*np.cos(data.imag) + data.real*np.sin(data.imag) * 1j
        return data

    def getSparameters(self,a:int=None,b:int=None,mode="RL")->np.array:
        assert isinstance(a, int) or a == None, "端口号必须是整数"
        assert isinstance(b, int) or b == None, "端口号必须是整数"

        if a == None and b == None:
            s = np.abs(self.data)
        elif (a == None) ^ (b == None):
            raise "S参数不完整,"
        else:
            index = (a-1)*self.PORTS+(b-1)
            s = np.abs(self.data)[...,index]  # 取模
        if mode.upper() in ["VSWR"]:
            if isinstance(a,int) and isinstance(b,int):
                return (1+s)/(1-s)
            for i in range(self.PORTS):
                for j in range(self.PORTS):
                    if i == j:  # 反射参数
                        s[..., i * self.PORTS + j] = (1+s[...,i*self.PORTS+j])/(1-s[...,i*self.PORTS+j])
                    else:  # 传输参数
                        s[..., i * self.PORTS + j] = 20*np.log10(s[..., i * self.PORTS + j])
            return s
        else :
            return 20 * np.log10(s)




    def getTrace(self,S,mode = "RL"):
        a = S[1:len(S) // 2 + 1]
        b = S[len(S) // 2 + 1:]
        # print(a," ",b)
        return self.getSparameters(int(a),int(b),mode=mode)

    def _to_excel(self,mode="RL")->"excel":
        # columns = ["S%d_%d"%(a+1,b+1) for a in range(self.PORTS) for b in range(self.PORTS)]
        columns = ["S%d%d" % (a + 1, b + 1) if a < 9 and b < 9
                   else "S%02d%02d" % (a + 1, b + 1)  for a in range(self.PORTS)  for b in range(self.PORTS)]

        filename = os.path.basename(self.file)
        excel_file = os.path.splitext(filename)[0] + ".xls"

        df = pd.DataFrame(self.getSparameters(mode=mode),columns=columns,index=self.freq)
        date = self.testDate.strftime("%Y%m%d %H:%M:%S") if self.testDate else ""
        df.index.name = "频率 仪器型号:{} 仪器序列号:{} 测试日期:{}".format(self.VNA,self.VNA_SN,date)
        try:
            df.to_excel(excel_file,sheet_name="{} {}".format(self.VNA,self.testDate),encoding="utf_8_sig")
            print(">>> {}文件已经生成".format(excel_file))
        except:
            excel_file = os.path.splitext(filename)[0] + ".csv"
            df.to_csv(excel_file, encoding="utf_8_sig")
            print(">>> 由于excel文件格式限制,使用{}文件代替输出".format(excel_file))

    def _to_csv(self,filepath = None,mode="RL",ports = None)->"csv":

        columns = ["S%d_%d" % (a + 1, b + 1) for a in range(self.PORTS) for b in range(self.PORTS)]
        filename = os.path.basename(self.file)
        csv_file = os.path.splitext(filename)[0]

        df = pd.DataFrame(self.getSparameters(mode=mode), columns=columns, index=self.freq)

        if not ports:
            date = self.testDate.strftime("%Y%m%d %H:%M:%S") if self.testDate else ""
            df.index.name = "频率 仪器型号:{} 仪器序列号:{} 测试日期:{}".format(self.VNA,self.VNA_SN,date)
            if filepath:
                csv_file = os.path.join(filepath,csv_file+".csv")
            else:
                csv_file += ".csv"
            df.to_csv(csv_file,encoding="utf_8_sig")
            print(">>> {}文件已经生成".format(csv_file))
        else:
            column_index = ["S%d_%d" % (a, b) for a in ports for b in ports]
            date = self.testDate.strftime("%Y%m%d %H:%M:%S") if self.testDate else ""
            df = df[column_index]  # 截取指定列
            df.index.name = "频率 仪器型号:{} 仪器序列号:{} 测试日期:{}".format(self.VNA, self.VNA_SN, date)
            if filepath:
                str_ports = [str(x) for x in ports]
                csv_file = os.path.join(filepath,csv_file+'_' +"_".join(str_ports)+ ".csv")
            else:
                csv_file = csv_file +'_'+"_".join([str(x) for x in ports])+ ".csv"
            df.to_csv(csv_file, encoding="utf_8_sig")
            print(">>> {}文件已经生成".format(csv_file))

    def _to_snp(self,filepath = None,ports=None,quickMode = False):
        '''ports为空时,表示整个都复制'''
        if not ports:
            # 先读取旧文件的头部,内容存在列表中
            HeadList = []
            with open(self.file, "r") as f:
                for i in range(self.ValidRowNo):
                    content = f.readline()
                    HeadList.append(content)

            # 将复数拆分成两列实数
            row, col = self.oddData.shape
            datas = np.zeros(shape=(row, 2 * col + 1), dtype=float)
            datas[..., 0] = self.freq
            for i in range(col):
                datas[..., 2 * i + 1] = self.data[...,i].real
                datas[..., 2 * i + 2] = self.data[...,i].imag

            # 开始数据拼接
            bigContent = ""
            if quickMode:
                for index_ in range(row):
                    temparray = datas[index_, ...]
                    # 获取频率数据,目前的频率最高40GHz,10GHz大概为10位有效位数
                    fre_str = "{:.10e}".format(temparray[0])
                    bigContent += fre_str + " "
                    i = 0
                    for item in temparray[1:]:
                        i += 1
                        bigContent += "{:.7e}".format(item) + " "
                        if i % 8 == 0:
                            bigContent += "\n"
                    bigContent += "\n"
            else:
                justSize = 17 # 用来对齐的数据的位数
                for index_ in range(row):
                    temparray = datas[index_,...]
                    # 获取频率数据,目前的频率最高40GHz,10GHz大概为10位有效位数
                    fre_str = "{:.10e}".format(temparray[0]).ljust(justSize)
                    bigContent += fre_str + " "
                    i = 0
                    for item in temparray[1:]:
                        i += 1
                        bigContent += "{:.7e}".format(item).rjust(justSize) + " "
                        if i % 8 == 0:
                            bigContent += "\n" + "".rjust(len(fre_str)) + " "
                    bigContent += "\n"

            if filepath:
                raw_file = os.path.basename(self.file).split('.')
                raw_file.pop()  # 抛却文件后缀
                ports = [str(x) for x in ports]
                file_out = '.'.join(raw_file) + '_' + '_'.join(ports) + '.s{}p'.format(len(ports))
                file_out = os.path.join(filepath, file_out)
            else:
                raw_file = self.file.split('.')
                raw_file.pop()  # 抛却文件后缀
                ports = [str(x) for x in ports]
                file_out = '.'.join(raw_file) + '_' + '_'.join(ports) + '.s{}p'.format(len(ports))
            # 开始写文件
            with open(file_out,"w") as w:
                # 先写头部数据
                for line in HeadList:
                    w.write(line)
                # 再写实际数据
                w.write(bigContent)

        else :
            # 生成要获取的列表。
            column_index = ["S%d_%d" % (a, b) for a in ports for b in ports]

            assert isinstance(ports,list),"必须是端口列表参数!"
            HeadList = []
            #先写入仪器信息
            HeadList.append(self.VNA_info)
            #再写入日期信息
            HeadList.append(self.Date_info)
            #写入S参数信息
            v = len(ports)         #等效端口数
            first_start = f"!S{v}P File: Measurements: "
            start = "!"
            for i in range(v):
                if i == 0:
                    begin = first_start
                else:
                    begin = start
                t = ["S%d%d" % (i + 1, b + 1) if i + 1 < 9 and b + 1 < 9
                     else "S%02d%02d" % (i + 1, b + 1) for b in range(v)]
                HeadList.append(begin + "<" + ", ".join(t) + '>\n')
            #写入数据格式信息
            HeadList.append(self.FormatInfo)

            #将原始数据的列也有个索引,类似S11、S12等
            columns = ["S%d_%d" % (a + 1, b + 1) for a in range(self.PORTS) for b in range(self.PORTS)]
            column_index_list = [columns.index(i) for i in column_index]  #找到要切片的数据在整个数据中的列的位置
            tempData = self.data[...,column_index_list]

            # 将复数拆分成两列实数
            row,col = tempData.shape
            datas = np.zeros(shape=(row, 2 * col + 1), dtype=float)
            datas[..., 0] = self.freq
            for i in range(col):
                datas[..., 2 * i + 1] = tempData[..., i].real

                datas[..., 2 * i + 2] = tempData[..., i].imag

            # 开始数据拼接
            bigContent = ""
            justSize = 17  # 用来对齐的数据的位数
            for index_ in range(row):
                temparray = datas[index_, ...]
                # 获取频率数据,目前的频率最高40GHz,10GHz大概为10位有效位数
                fre_str = "{:.10e}".format(temparray[0]).ljust(justSize)
                bigContent += fre_str + " "
                i = 0
                for item in temparray[1:]:
                    i += 1
                    bigContent += "{:.7e}".format(item).rjust(justSize) + " "
                    if i % 8 == 0:
                        bigContent += "\n" + "".rjust(len(fre_str)) + " "
                bigContent += "\n"

            # 开始写文件

            if filepath:
                raw_file = os.path.basename(self.file).split('.')
                raw_file.pop()  # 抛却文件后缀
                ports = [str(x) for x in ports]
                file_out = '.'.join(raw_file) + '_' + '_'.join(ports) + '.s{}p'.format(len(ports))
                file_out = os.path.join(filepath,file_out)
            else:
                raw_file = self.file.split('.')
                raw_file.pop()  # 抛却文件后缀
                ports = [str(x) for x in ports]
                file_out = '.'.join(raw_file) + '_' + '_'.join(ports) + '.s{}p'.format(len(ports))

            with open(file_out, "w") as w:
                # 先写头部数据
                for line in HeadList:
                    w.write(line)
                # 再写实际数据
                w.write(bigContent)

定义获取数据格式

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值