import sys
import os
import re
import pandas as pd
import numpy as np
from pathlib import Path
from PySide6.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QPushButton, QLabel, QFileDialog, QTextEdit, QProgressBar,
QLineEdit, QGroupBox, QFormLayout, QMessageBox, QTabWidget,
QScrollArea, QFrame, QComboBox
)
from PySide6.QtCore import QThread, Signal, Qt
from PySide6.QtGui import QFont
import matplotlib
matplotlib.use('Agg') # 使用非GUI后端
import matplotlib.pyplot as plt
from scipy import stats
from openpyxl.drawing.image import Image as OpenpyxlImage
import tempfile
class DataProcessor:
"""数据处理核心类 - 飞机射频耦合测试数据处理"""
def split_column_data_inplace(self, df, column_name):
"""对指定列的数据进行拆分处理,并直接替换原数据"""
# 预设规则:按列名预期格式
split_rules = {
'频段': r'freq_band\s*:\s*([^\s,,\n\r\t\'\"]+)',
'frequency': r'frequency\s*:\s*([-\d.]+)',
'rc_gain_a均值': r'rc_gain_a均值:([-\d.]+)',
'rc_gain_b均值': r'rc_gain_b均值:([-\d.]+)',
'rc_snr均值': r'rc_snr均值:([-\d.]+)',
'fc_gain_a均值': r'fc_gain_a均值:([-\d.]+)',
'fc_gain_b均值': r'fc_gain_b均值:([-\d.]+)',
'fc_snr均值': r'fc_snr均值:([-\d.]+)',
'tlv_err均值': r'tlv_err均值:([-\d.]+)',
'num_err均值': r'num_err均值:([-\d.]+)',
'错误信息': r'错误信息:(.*)'
}
if column_name in df.columns:
for idx, row in df.iterrows():
cell_value = str(row[column_name]) if pd.notna(row[column_name]) else ""
# 首先尝试按照预设规则匹配
if column_name in split_rules:
match = re.search(split_rules[column_name], cell_value)
if match:
extracted_value = match.group(1)
df.at[idx, column_name] = extracted_value
continue
# 如果预设规则不匹配,尝试通用识别
content_patterns = [
(r'freq_band\s*:\s*([^\s,,\n\r\t\'\"]+)', 'freq_band识别'),
(r'frequency\s*:\s*([-\d.]+)', 'frequency识别'),
(r'rc_gain_a均值:([-\d.]+)', 'rc_gain_a均值识别'),
(r'rc_gain_b均值:([-\d.]+)', 'rc_gain_b均值识别'),
(r'rc_snr均值:([-\d.]+)', 'rc_snr均值识别'),
(r'fc_gain_a均值:([-\d.]+)', 'fc_gain_a均值识别'),
(r'fc_gain_b均值:([-\d.]+)', 'fc_gain_b均值识别'),
(r'fc_snr均值:([-\d.]+)', 'fc_snr均值识别'),
(r'tlv_err均值:([-\d.]+)', 'tlv_err均值识别'),
(r'num_err均值:([-\d.]+)', 'num_err均值识别'),
(r'错误信息:(.*)', '错误信息识别'),
]
matched = False
for pattern, desc in content_patterns:
match = re.search(pattern, cell_value)
if match:
extracted_value = match.group(1)
# 如果是错误信息,即使在错误的列中也要提取出来
if '错误信息' in desc:
df.at[idx, column_name] = extracted_value
else:
df.at[idx, column_name] = extracted_value
matched = True
break
if not matched:
# 如果内容以"错误信息:"开头但后面没有内容,也应处理
if cell_value.startswith('错误信息:'):
if len(cell_value) > 5: # "错误信息:"长度为5
extracted_value = cell_value[5:] # 去除"错误信息:"前缀
df.at[idx, column_name] = extracted_value
else:
# 空的错误信息,设为空字符串
df.at[idx, column_name] = ""
return df
def remove_duplicates_and_write_to_new_sheet(self, df, output_file_path,
original_sheet_name='原始处理数据',
deduplicated_sheet_name='去重后数据',
filter_2g_sheet='2G数据',
filter_5g_sheet='5G数据'):
"""将处理完成的数据去重,取最后一次测试数据,并按频段筛选,写入到Excel的不同工作表中"""
try:
# 定义可能用于去重的关键列
key_columns = []
possible_key_columns = ['飞机SN', 'freq_band', '频段']
# 检查哪些关键列存在于数据中
for col in possible_key_columns:
if col in df.columns:
key_columns.append(col)
# 如果找到了关键列,基于这些列去重;否则基于所有列去重
if key_columns:
# 基于关键列去重,保留最后一条记录(最后一次测试数据)
deduplicated_df = df.drop_duplicates(subset=key_columns, keep='last')
print(f"基于列 {key_columns} 进行去重,原始数据: {len(df)} 行,去重后: {len(deduplicated_df)} 行")
else:
# 如果没有找到关键列,基于所有列进行去重
deduplicated_df = df.drop_duplicates(keep='last')
print(f"基于所有列进行去重,原始数据: {len(df)} 行,去重后: {len(deduplicated_df)} 行")
# 进行频段筛选
freq_band_col = None
# 查找频段相关的列
for col in ['freq_band', '频段', 'frequency']:
if col in deduplicated_df.columns:
freq_band_col = col
break
if freq_band_col:
# 提取2G和5G数据
filtered_2g = deduplicated_df[
deduplicated_df[freq_band_col].astype(str).str.contains(r'2g|2G|2\.4g|2\.4G', case=False, na=False)
]
filtered_5g = deduplicated_df[
deduplicated_df[freq_band_col].astype(str).str.contains(r'5g|5G|5\.8g|5\.8G', case=False, na=False)
]
print(f"2G数据: {len(filtered_2g)} 行")
print(f"5G数据: {len(filtered_5g)} 行")
else:
# 如果没有找到频段列,则创建空的筛选数据
filtered_2g = pd.DataFrame(columns=deduplicated_df.columns)
filtered_5g = pd.DataFrame(columns=deduplicated_df.columns)
print("未找到频段相关列,跳过频段筛选")
# 将所有数据写入不同的工作表
with pd.ExcelWriter(output_file_path, engine='openpyxl') as writer:
# 写入原始处理后的数据
df.to_excel(writer, sheet_name=original_sheet_name, index=False)
# 写入去重后的数据
deduplicated_df.to_excel(writer, sheet_name=deduplicated_sheet_name, index=False)
# 写入2G频段数据
filtered_2g.to_excel(writer, sheet_name=filter_2g_sheet, index=False)
# 写入5G频段数据
filtered_5g.to_excel(writer, sheet_name=filter_5g_sheet, index=False)
print(f"数据已保存到 {output_file_path}")
print(f"- 工作表 '{original_sheet_name}': 包含原始处理后的数据")
print(f"- 工作表 '{deduplicated_sheet_name}': 包含去重后的数据(最后一次测试)")
print(f"- 工作表 '{filter_2g_sheet}': 包含2G频段数据")
print(f"- 工作表 '{filter_5g_sheet}': 包含5G频段数据")
return deduplicated_df, filtered_2g, filtered_5g
except Exception as e:
print(f"去重、筛选和写入过程中发生错误: {str(e)}")
# 如果出现错误,仍保存原始数据
df.to_excel(output_file_path, index=False)
print(f"由于错误,仅保存了原始数据到 {output_file_path}")
return df, pd.DataFrame(), pd.DataFrame()
def analyze_pass_rate_by_aircraft_sn(self, df, output_file_path, pass_rate_sheet_name='通过率统计'):
"""
基于飞机SN和频率分析一次性通过率和复测通过率
"""
try:
# 确定飞机SN列
aircraft_sn_col = None
possible_sn_cols = ['飞机SN', 'aircraft_sn', 'SN', '遥控器SN', '测试SN', '遥控器SN']
for col in possible_sn_cols:
if col in df.columns:
aircraft_sn_col = col
break
if not aircraft_sn_col:
print("警告:未找到飞机SN相关列,跳过通过率分析")
return pd.DataFrame(), pd.DataFrame()
# 确定频率列
freq_col = None
possible_freq_cols = ['freq_band', '频段', 'frequency']
for col in possible_freq_cols:
if col in df.columns:
freq_col = col
break
# 确定通过标准列
pass_col = None
possible_pass_cols = [col for col in df.columns if any(keyword in col.lower() for keyword in ['pass', 'fail', 'status', 'result', '测试结果', '状态'])]
if possible_pass_cols:
pass_col = possible_pass_cols[0]
else:
# 查找可能表示通过/失败的列
numeric_cols = df.select_dtypes(include=['number']).columns.tolist()
for col in ['rc_snr均值', 'fc_snr均值', 'rc_gain_a均值', 'rc_gain_b均值', 'fc_gain_a均值', 'fc_gain_b均值']:
if col in numeric_cols:
pass_col = col
break
if not pass_col:
print("警告:未找到合适的通过/失败判断列,使用默认判断逻辑")
# 按飞机SN和频率分组
if freq_col:
# 按飞机SN和频率分组
grouped = df.groupby([aircraft_sn_col, freq_col])
else:
# 只按飞机SN分组
grouped = df.groupby([aircraft_sn_col])
# 初始化统计结果 - 每个SN+频率组合只保留一条记录
summary_data = {
'飞机SN': [],
'频率': [],
'测试次数': [],
'最终结果': [],
'首次测试结果': [],
'是否复测': [],
'测试时间': [] # 添加测试时间
}
for group_key, group in grouped:
test_count = len(group)
first_result = group.iloc[0] # 第一次测试
last_result = group.iloc[-1] # 最后一次测试结果
# 解析分组键
if isinstance(group_key, tuple): # 多列分组
if freq_col:
aircraft_sn = str(group_key[0])
freq_band = str(group_key[1])
else:
aircraft_sn = str(group_key[0])
freq_band = '未知'
else: # 单列分组
aircraft_sn = str(group_key)
freq_band = '未知'
# 判断通过/失败
if pass_col and pass_col in group.columns:
if group[pass_col].dtype == 'object': # 文本类型
final_value = str(last_result[pass_col]).lower().strip()
first_value = str(first_result[pass_col]).lower().strip()
final_pass = final_value in ['pass', '通过', 'success', 'ok', '1', 'true', 'yes', 'y']
first_pass = first_value in ['pass', '通过', 'success', 'ok', '1', 'true', 'yes', 'y']
else: # 数值类型
threshold = 0 # 可根据具体业务逻辑调整
final_pass = pd.to_numeric(last_result[pass_col], errors='coerce') >= threshold
first_pass = pd.to_numeric(first_result[pass_col], errors='coerce') >= threshold
else:
final_pass = True
first_pass = True
# 添加记录(每个SN+频率组合只保留一条)
summary_data['飞机SN'].append(aircraft_sn)
summary_data['频率'].append(freq_band)
summary_data['测试次数'].append(test_count) # 实际统计的测试次数
summary_data['最终结果'].append('PASS' if final_pass else 'FAIL')
summary_data['首次测试结果'].append('PASS' if first_pass else 'FAIL')
summary_data['是否复测'].append('是' if test_count > 1 else '否')
# 如果有时间列,也添加进去
time_cols = [col for col in group.columns if any(time_keyword in col.lower() for time_keyword in ['time', 'date', '日期', '时间'])]
if time_cols:
summary_data['测试时间'].append(str(group.iloc[-1][time_cols[0]]) if not pd.isna(group.iloc[-1][time_cols[0]]) else '未知')
else:
summary_data['测试时间'].append('未知')
# 创建汇总DataFrame
summary_df = pd.DataFrame(summary_data)
# 计算整体统计
unique_aircraft_count = summary_df['飞机SN'].nunique() # 飞机SN数量
total_tests = len(summary_df)
# 统计2G和5G测试数量
if '频率' in summary_df.columns:
g2_tests = len(summary_df[summary_df['频率'].str.contains('2G|2g', case=False, na=False)])
g5_tests = len(summary_df[summary_df['频率'].str.contains('5G|5g', case=False, na=False)])
else:
g2_tests = 0
g5_tests = 0
# 计算一次通过数:所有飞机SN的2G+5G首次测试通过总数
# 对于每个飞机SN,检查其所有频率的首次测试结果
sn_freq_first_tests = summary_df.groupby(['飞机SN', '频率']).first().reset_index()
# 按飞机SN聚合,判断该SN在所有频率下是否首次测试都通过
sn_first_pass_status = sn_freq_first_tests.groupby('飞机SN').agg({
'首次测试结果': lambda x: 'PASS' if all(result == 'PASS' for result in x) else 'FAIL'
}).reset_index()
# 计算所有频率首次测试都通过的飞机SN数量
first_pass_total = len(sn_first_pass_status[sn_first_pass_status['首次测试结果'] == 'PASS'])
first_pass_rate = (first_pass_total / unique_aircraft_count * 100) if unique_aircraft_count > 0 else 0
# 计算复测情况:统计有复测的飞机SN数量
# 对每个飞机SN,检查其任一频率是否有复测
sn_has_retest = summary_df[summary_df['是否复测'] == '是']['飞机SN'].unique()
retested_count = len(sn_has_retest)
retest_rate = (retested_count / unique_aircraft_count * 100) if unique_aircraft_count > 0 else 0
# 修正复测通过数:只有当飞机SN在所有频率(2G和5G)下复测后都通过时才计入
# 先找出有复测的飞机SN
retest_sn_list = summary_df[summary_df['是否复测'] == '是']['飞机SN'].unique()
# 计算每个有复测的飞机SN的最终结果
retest_sn_results = []
for sn in retest_sn_list:
sn_data = summary_df[summary_df['飞机SN'] == sn]
# 检查该SN的所有频率的最终结果
all_pass = all(result == 'PASS' for result in sn_data['最终结果'])
if all_pass: # 只有该SN的所有频率都通过时才计入复测通过数
retest_sn_results.append(sn)
retest_success_count = len(retest_sn_results)
# 创建总体统计摘要
rate_summary = {
'统计项目': [
'SN数量',
'2G测试总数量',
'5G测试总数量',
'总测试数量',
'一次通过数(所有SN首次测试均通过的数量)',
'一次通过率(首次测试均通过的SN比例)',
'复测飞机SN数量(有复测的SN数量)',
'复测通过数(复测后均通过的SN数量)',
'复测率(有复测的SN比例)'
],
'数值': [
unique_aircraft_count,
g2_tests,
g5_tests,
total_tests,
first_pass_total,
f"{first_pass_rate:.2f}%",
retested_count,
retest_success_count,
f"{retest_rate:.2f}%"
]
}
rate_summary_df = pd.DataFrame(rate_summary)
# 将结果写入Excel文件
import os
# 检查文件是否存在,如果存在则追加,否则创建
if os.path.exists(output_file_path):
with pd.ExcelWriter(output_file_path, mode='a', engine='openpyxl', if_sheet_exists='replace') as writer:
summary_df.to_excel(writer, sheet_name='各产品测试详情', index=False)
rate_summary_df.to_excel(writer, sheet_name=pass_rate_sheet_name, index=False)
else:
with pd.ExcelWriter(output_file_path, engine='openpyxl') as writer:
summary_df.to_excel(writer, sheet_name='各产品测试详情', index=False)
rate_summary_df.to_excel(writer, sheet_name=pass_rate_sheet_name, index=False)
print(f"通过率分析完成:")
print(f"- 飞机SN数量: {unique_aircraft_count}")
print(f"- 2G测试总数量: {g2_tests}")
print(f"- 5G测试总数量: {g5_tests}")
print(f"- 总测试数量: {total_tests}")
print(f"- 一次通过数(所有飞机SN首次测试均通过的数量): {first_pass_total}")
print(f"- 一次通过率(首次测试均通过的飞机SN比例): {first_pass_rate:.2f}%")
print(f"- 复测飞机SN数量: {retested_count}")
print(f"- 复测通过数(复测后所有频率均通过的飞机SN数量): {retest_success_count}")
print(f"- 复测率(有复测的飞机SN比例): {retest_rate:.2f}%")
print(f"- 通过率统计已保存到 {output_file_path} 的 '{pass_rate_sheet_name}' 工作表")
return summary_df, rate_summary_df
except Exception as e:
print(f"通过率分析过程中发生错误: {str(e)}")
import traceback
traceback.print_exc()
return pd.DataFrame(), pd.DataFrame()
def process_remote_sn_data(self, df):
"""处理遥控器SN列的数据,对以'1910'开头的数据行进行特定列移位"""
if '遥控器SN' not in df.columns:
print("警告:未找到'遥控器SN'列")
return df
# 复制数据框避免修改原始数据
processed_df = df.copy()
# 获取遥控器SN列的所有值
sn_series = processed_df['遥控器SN']
# 找出以'1910'开头的行(排除表头)
mask_1910 = sn_series.astype(str).str.startswith('1910')
# 获取遥控器SN列的索引位置
sn_col_index = df.columns.get_loc('遥控器SN') if '遥控器SN' in df.columns else 0
# 对于以'1910'开头的数据行,将从遥控器SN列开始的所有列向后移动一列
for idx in processed_df[mask_1910].index:
# 获取当前行的数据
row_values = processed_df.loc[idx].values
# 创建新行数据:从遥控器SN列开始的数据后移,前面的数据保持不变
new_row_values = []
# 添加遥控器SN列之前的数据(不改变)
for i in range(sn_col_index):
new_row_values.append(row_values[i])
# 在遥控器SN列位置插入NaN
new_row_values.append(None) # 插入None到遥控器SN列位置
# 将从遥控器SN列开始的原始数据追加到新行
for i in range(sn_col_index, len(row_values)):
new_row_values.append(row_values[i])
# 更新当前行的数据
for col_idx, col_name in enumerate(processed_df.columns):
if col_idx < len(new_row_values):
processed_df.at[idx, col_name] = new_row_values[col_idx]
# 如果还有剩余的值,需要添加新列
if len(new_row_values) > len(processed_df.columns):
for extra_idx in range(len(processed_df.columns), len(new_row_values)):
new_col_name = f"额外列_{extra_idx - len(processed_df.columns) + 1}"
if new_col_name not in processed_df.columns:
processed_df[new_col_name] = None
processed_df.at[idx, new_col_name] = new_row_values[extra_idx]
# 检查以'R'开头的数据(不需要操作)
mask_R = sn_series.astype(str).str.startswith('R')
r_count = len(processed_df[mask_R])
print(f"发现 {r_count} 行以'R'开头的数据(无需处理)")
return processed_df
def find_coupling_test_folder(self, base_path):
"""递归搜索包含"CouplingTest"的文件夹"""
for root, dirs, files in os.walk(base_path):
for d in dirs:
if "CouplingTest" in d:
return os.path.join(root, d)
return None
def process_folder(self, input_folder, output_folder, progress_callback=None, log_callback=None):
"""处理指定文件夹中的所有Excel文件"""
# 创建输出文件夹
os.makedirs(output_folder, exist_ok=True)
# 获取输入文件夹中所有的Excel文件
excel_files = [f for f in os.listdir(input_folder) if f.endswith(('.xlsx', '.xls', '.xlsm'))]
if not excel_files:
log_callback(f"在 {input_folder} 中未找到Excel文件")
return
total_files = len(excel_files)
for idx, excel_file in enumerate(excel_files):
file_path = os.path.join(input_folder, excel_file)
log_callback(f"正在处理文件: {excel_file}")
try:
# 读取Excel文件
df = pd.read_excel(file_path, dtype=str) # 强制将所有数据读取为字符串类型
# 首先处理遥控器SN列
df = self.process_remote_sn_data(df)
# 对所有相关列进行拆分处理
columns_to_process = [
'freq_band', # 添加频率带宽列
'frequency', # 添加频率列
'频段',
'rc_gain_a均值', 'rc_gain_b均值', 'rc_snr均值',
'fc_gain_a均值', 'fc_gain_b均值', 'fc_snr均值',
'tlv_err均值', 'num_err均值', '错误信息'
]
for column_name in columns_to_process:
if column_name in df.columns:
df = self.split_column_data_inplace(df, column_name)
# 生成输出文件名(原文件名+拆分数据)
base_name = os.path.splitext(excel_file)[0]
ext = os.path.splitext(excel_file)[1]
output_file_name = f"{base_name}拆分数据{ext}"
output_file_path = os.path.join(output_folder, output_file_name)
# 使用修正后的函数,包含去重、频段筛选
deduplicated_df, filtered_2g, filtered_5g = self.remove_duplicates_and_write_to_new_sheet(
df,
output_file_path,
original_sheet_name='原始处理数据',
deduplicated_sheet_name='去重后数据',
filter_2g_sheet='2G数据',
filter_5g_sheet='5G数据'
)
# 添加通过率分析(注意:需要确保有飞机SN列)
self.analyze_pass_rate_by_aircraft_sn(df, output_file_path)
# 更新进度
if progress_callback:
progress_callback(int((idx + 1) / total_files * 100))
except Exception as e:
log_callback(f"处理文件 {excel_file} 时出错: {str(e)}")
class RemoteDataProcessor:
"""遥控器数据处理核心类 - 遥控器射频耦合测试数据处理"""
def extract_data_from_txt_files(self, folder_path):
"""
从指定文件夹及其子文件夹中的txt文件中提取数据
验证路径中是否包含"Coupling"文件夹
"""
# 检查整个路径中是否包含"Coupling"文件夹
has_coupling = False
for root, dirs, files in os.walk(folder_path):
if "Coupling" in dirs or "coupling" in dirs: # 不区分大小写
has_coupling = True
break
# 如果路径中没有Coupling文件夹,给出警告但仍允许处理
if not has_coupling:
print(f"警告: 未在路径 {folder_path} 中找到 'Coupling' 文件夹")
# 修改正则表达式,将 device_id 替换为 RC_SN
pattern = r'(\d{4}-\d{2}-\d{2}-\d{2}:\d{2}:\d{2}),\s*freq_band:([25]G)\s*(NG|PASS)\s*([A-Z0-9]+)\s*rc_gain_a均值:(\d+)\s*rc_gain_b均值:(\d+)\s*rc_snr均值:(\d+)\s*fc_gain_a均值:(\d+)\s*fc_gain_b均值:(\d+)\s*fc_snr均值:(\d+)\s*tlv_err均值:(\d+)\s*num_err均值:(\d+)\s*错误信息:(.*)'
extracted_data = []
# 遍历所有文件,不限制是否在Coupling文件夹中
for root, dirs, files in os.walk(folder_path):
for filename in files:
if filename.endswith('.txt'):
file_path = os.path.join(root, filename)
with open(file_path, 'r', encoding='gbk') as file:
for line_num, line in enumerate(file, 1):
line = line.strip()
if not line:
continue
match = re.search(pattern, line)
if match:
date_part = match.group(1).split('-')[0:3]
date_str = '-'.join(date_part)
data = {
'source_file': file_path,
'line_number': line_num,
'date_part': date_str,
'timestamp': match.group(1),
'freq_band': match.group(2),
'status': match.group(3),
'RC_SN': match.group(4), # 将 device_id 替换为 RC_SN
'rc_gain_a_mean': int(match.group(5)),
'rc_gain_b_mean': int(match.group(6)),
'rc_snr_mean': int(match.group(7)),
'fc_gain_a_mean': int(match.group(8)),
'fc_gain_b_mean': int(match.group(9)),
'fc_snr_mean': int(match.group(10)),
'tlv_err_mean': int(match.group(11)),
'num_err_mean': int(match.group(12)),
'error_info': match.group(13).strip()
}
extracted_data.append(data)
return extracted_data
def remove_duplicate_sn_by_latest(self, data_list):
"""
按RC_SN和freq_band去重,保留每个组合的最新测试结果
"""
# 使用 NumPy 2.x 兼容的排序方法
sorted_data = sorted(data_list, key=lambda x: (x['RC_SN'], x['freq_band'], x['timestamp']))
latest_data = {}
for item in sorted_data:
key = f"{item['RC_SN']}_{item['freq_band']}" # 使用 RC_SN 替代 device_id
latest_data[key] = item
return list(latest_data.values())
def process_data_with_statistics(self, data, output_path, status_callback=None):
"""
处理数据并生成统计信息
将所有数据输出到一个Excel文件的不同sheet中:
- 原始数据
- 去重数据
- 2G数据
- 5G数据
- 详细统计
- 汇总统计
"""
if not data:
return []
# 按日期分组数据
date_groups = {}
for item in data:
date = item['date_part']
if date not in date_groups:
date_groups[date] = []
date_groups[date].append(item)
created_files = []
for date, date_data in date_groups.items():
date_folder = os.path.join(output_path, date)
os.makedirs(date_folder, exist_ok=True)
# 1. 准备原始数据
raw_df_data = []
for item in date_data:
row = item.copy()
del row['date_part']
raw_df_data.append(row)
raw_df = pd.DataFrame(raw_df_data)
# 2. 准备去重数据
unique_date_data = self.remove_duplicate_sn_by_latest(date_data)
unique_df_data = []
for item in unique_date_data:
row = item.copy()
del row['date_part']
unique_df_data.append(row)
unique_df = pd.DataFrame(unique_df_data)
# 3. 准备2G和5G数据
unique_2g_df = unique_df[unique_df['freq_band'] == '2G'].copy()
unique_5g_df = unique_df[unique_df['freq_band'] == '5G'].copy()
# 4. 计算统计信息
# 按RC_SN分组数据,每个SN的所有测试(2G+5G)
rc_groups = {}
for item in date_data:
rc_sn = item['RC_SN']
if rc_sn not in rc_groups:
rc_groups[rc_sn] = []
rc_groups[rc_sn].append(item)
# 构建每个SN的测试记录
sn_test_records = {}
for rc_sn, items in rc_groups.items():
# 按时间排序
sorted_items = sorted(items, key=lambda x: x['timestamp'])
# 按频段分组
freq_data = {}
for item in sorted_items:
freq_band = item['freq_band']
if freq_band not in freq_data:
freq_data[freq_band] = []
freq_data[freq_band].append(item)
# 为每个频段计算测试次数和最终结果
sn_test_records[rc_sn] = {}
for freq_band, freq_items in freq_data.items():
sn_test_records[rc_sn][freq_band] = {
'test_count': len(freq_items),
'latest_result': freq_items[-1]['status'],
'all_results': [item['status'] for item in freq_items],
'timestamps': [item['timestamp'] for item in freq_items]
}
# 计算统计信息
daily_stats = []
total_sn_count = len(sn_test_records) # 总SN数量
# 按SN整体统计
passed_once_count = 0 # 一次通过的SN数量(2G和5G均一次通过)
retry_sn_count = 0 # 需要复测的SN数量(至少一个频段测试次数>1)
retry_test_count = 0 # 复测次数(所有需要复测的SN的复测次数之和)
failed_sn_count = 0 # 最终失败的SN数量
both_freq_sn_count = 0 # 同时包含2G和5G数据的SN数量
# 测试总次数 = SN数量 + 2G测试数据总和 + 5G测试数据总和
total_2g_tests = 0
total_5g_tests = 0
for rc_sn, freq_data in sn_test_records.items():
# 检查该SN是否同时包含2G和5G数据
has_2g = '2G' in freq_data
has_5g = '5G' in freq_data
has_both_freq = has_2g and has_5g
if has_both_freq:
both_freq_sn_count += 1
# 统计2G和5G测试次数
if has_2g:
total_2g_tests += freq_data['2G']['test_count']
if has_5g:
total_5g_tests += freq_data['5G']['test_count']
# 检查所有频段是否都通过
all_freq_passed = all(
info['latest_result'] == 'PASS'
for info in freq_data.values()
)
# 检查所有频段是否都是一次通过(测试次数=1且结果为PASS)
all_freq_once_passed = all(
info['test_count'] == 1 and info['latest_result'] == 'PASS'
for info in freq_data.values()
)
# 检查是否有频段需要复测
sn_needs_retry = any(
info['test_count'] > 1
for info in freq_data.values()
)
# 统计该SN的复测次数
sn_retry_count = sum(
max(0, info['test_count'] - 1) # 每个频段超过1次的部分为复测次数
for info in freq_data.values()
)
# 判断是否为"一次通过":2G和5G都存在且都是一次通过
is_once_passed = has_both_freq and all_freq_once_passed
# 判断是否为"需要复测":至少一个频段测试次数>1
is_retry_needed = sn_needs_retry
# 判断是否为"最终失败":至少一个频段最终结果不是PASS
is_final_failed = not all_freq_passed
if is_once_passed:
passed_once_count += 1
if is_retry_needed:
retry_sn_count += 1 # 需要复测的SN数量
retry_test_count += sn_retry_count # 总复测次数
if is_final_failed:
failed_sn_count += 1
# 记录每个SN的详细信息
daily_stats.append({
'RC_SN': rc_sn,
'2G测试次数': freq_data.get('2G', {}).get('test_count', 0),
'2G最终结果': freq_data.get('2G', {}).get('latest_result', 'N/A'),
'2G首次结果': freq_data.get('2G', {}).get('all_results', [None])[0] if freq_data.get('2G', {}).get('all_results') else 'N/A',
'5G测试次数': freq_data.get('5G', {}).get('test_count', 0),
'5G最终结果': freq_data.get('5G', {}).get('latest_result', 'N/A'),
'5G首次结果': freq_data.get('5G', {}).get('all_results', [None])[0] if freq_data.get('5G', {}).get('all_results') else 'N/A',
'复测次数': sn_retry_count,
'是否同时有2G和5G': has_both_freq,
'是否一次通过': is_once_passed,
'是否最终通过': not is_final_failed,
'是否需要复测': is_retry_needed
})
# 计算测试总次数 = SN数量 + 2G测试数据总和 + 5G测试数据总和
total_test_count = total_sn_count + total_2g_tests + total_5g_tests
# 计算各种通过率
once_pass_rate = (passed_once_count / both_freq_sn_count * 100) if both_freq_sn_count > 0 else 0
retry_rate = (retry_sn_count / total_sn_count * 100) if total_sn_count > 0 else 0
final_pass_rate = ((total_sn_count - failed_sn_count) / total_sn_count * 100) if total_sn_count > 0 else 0
# 创建统计DataFrame
stats_df = pd.DataFrame(daily_stats)
# 创建汇总统计
summary_data = {
'统计项目': [
'当天测试SN总数',
'同时包含2G和5G数据的SN数量',
'一次通过SN数量(2G和5G均一次通过)',
'一次通过率',
'最终通过SN数量',
'最终通过率',
'失败SN数量',
'失败率',
'需要复测的SN数量',
'复测率',
'复测次数',
'2G测试数据总和',
'5G测试数据总和',
'测试总次数(SN+2G+5G测试数据总和)'
],
'数值': [
total_sn_count,
both_freq_sn_count,
passed_once_count,
f"{once_pass_rate:.2f}%",
total_sn_count - failed_sn_count,
f"{final_pass_rate:.2f}%",
failed_sn_count,
f"{100-final_pass_rate:.2f}%",
retry_sn_count,
f"{retry_rate:.2f}%",
retry_test_count,
total_2g_tests,
total_5g_tests,
total_test_count
]
}
summary_df = pd.DataFrame(summary_data)
# 5. 将所有数据写入一个Excel文件的不同sheet中
output_file = os.path.join(date_folder, f"{date}_数据分析报告.xlsx")
with pd.ExcelWriter(output_file, engine='openpyxl') as writer:
# Sheet 1: 汇总统计(放在最前面,方便查看)
summary_df.to_excel(writer, sheet_name='汇总统计', index=False)
# Sheet 2: 详细统计
stats_df.to_excel(writer, sheet_name='详细统计', index=False)
# Sheet 3: 原始数据
raw_df.to_excel(writer, sheet_name='原始数据', index=False)
# Sheet 4: 去重数据
unique_df.to_excel(writer, sheet_name='去重数据', index=False)
# Sheet 5: 2G数据
if not unique_2g_df.empty:
unique_2g_df.to_excel(writer, sheet_name='2G数据', index=False)
# Sheet 6: 5G数据
if not unique_5g_df.empty:
unique_5g_df.to_excel(writer, sheet_name='5G数据', index=False)
# 通过回调函数发送日志信息到UI
log_message = f"数据分析报告已保存到 {output_file}"
if status_callback:
status_callback(log_message)
else:
print(log_message)
created_files.append(output_file)
return created_files
class AircraftProcessingThread(QThread):
"""飞机数据处理线程"""
progress_signal = Signal(int)
log_signal = Signal(str)
finished_signal = Signal()
def __init__(self, processor, input_folder, output_folder):
super().__init__()
self.processor = processor
self.input_folder = input_folder
self.output_folder = output_folder
def run(self):
try:
# 自动搜索CouplingTest文件夹
coupling_folder = self.processor.find_coupling_test_folder(self.input_folder)
if coupling_folder:
self.log_signal.emit(f"找到CouplingTest文件夹: {coupling_folder}")
actual_input_folder = coupling_folder
else:
self.log_signal.emit(f"未找到包含'CouplingTest'的文件夹,使用指定的输入文件夹: {self.input_folder}")
actual_input_folder = self.input_folder
self.processor.process_folder(
actual_input_folder,
self.output_folder,
progress_callback=self.progress_signal.emit,
log_callback=self.log_signal.emit
)
self.log_signal.emit("处理完成!")
except Exception as e:
self.log_signal.emit(f"处理过程中发生错误: {str(e)}")
finally:
self.finished_signal.emit()
class RemoteProcessingThread(QThread):
"""遥控器数据处理线程"""
progress_signal = Signal(int)
log_signal = Signal(str)
finished_signal = Signal(list)
def __init__(self, processor, input_folder, output_folder):
super().__init__()
self.processor = processor
self.input_folder = input_folder
self.output_folder = output_folder
def run(self):
try:
# 提取数据
self.log_signal.emit("正在读取txt文件并提取数据...")
extracted_data = self.processor.extract_data_from_txt_files(self.input_folder)
if not extracted_data:
self.log_signal.emit("未找到匹配的数据")
self.finished_signal.emit([])
return
# 处理数据 - 包含通过率统计
self.log_signal.emit("正在处理数据并计算通过率...")
created_files = self.processor.process_data_with_statistics(
extracted_data,
self.output_folder,
status_callback=lambda msg: self.log_signal.emit(msg)
)
self.log_signal.emit(f"处理完成!共创建了 {len(created_files)} 个文件")
self.finished_signal.emit(created_files)
except Exception as e:
self.log_signal.emit(f"处理过程中发生错误: {str(e)}")
self.finished_signal.emit([])
class MainWindow(QMainWindow):
"""主窗口"""
def __init__(self):
super().__init__()
self.setWindowTitle("射频数据处理器")
self.setGeometry(100, 100, 800, 600)
# 创建数据处理器实例
self.aircraft_processor = DataProcessor()
self.remote_processor = RemoteDataProcessor()
# 创建中央部件
central_widget = QWidget()
self.setCentralWidget(central_widget)
# 创建布局
layout = QVBoxLayout(central_widget)
# 创建标签页
tab_widget = QTabWidget()
layout.addWidget(tab_widget)
# 飞机数据处理标签页
self.aircraft_tab = QWidget()
self.setup_aircraft_tab()
tab_widget.addTab(self.aircraft_tab, "飞机数据处理")
# 遥控器数据处理标签页
self.remote_tab = QWidget()
self.setup_remote_tab()
tab_widget.addTab(self.remote_tab, "遥控器数据处理")
# 关于标签页
about_tab = QWidget()
about_layout = QVBoxLayout(about_tab)
about_label = QLabel(
"<h2>射频数据处理器</h2>"
"<p>本工具用于处理射频测试数据,支持:</p>"
"<ul>"
"<li>飞机射频耦合测试数据处理</li>"
"<li>遥控器射频耦合测试数据处理</li>"
"<li>数据拆分和格式化</li>"
"<li>去重和频段筛选</li>"
"<li>通过率统计分析</li>"
"</ul>"
"<p>开发者: 射频数据处理团队</p>"
)
about_label.setWordWrap(True)
about_layout.addWidget(about_label)
tab_widget.addTab(about_tab, "关于")
def setup_aircraft_tab(self):
"""设置飞机数据处理标签页"""
layout = QVBoxLayout(self.aircraft_tab)
# 输入文件夹选择
input_group = QGroupBox("输入设置")
input_layout = QFormLayout(input_group)
input_hbox = QHBoxLayout()
self.aircraft_input_folder_line = QLineEdit()
self.aircraft_input_folder_btn = QPushButton("浏览")
self.aircraft_input_folder_btn.clicked.connect(self.browse_aircraft_input_folder)
input_hbox.addWidget(self.aircraft_input_folder_line)
input_hbox.addWidget(self.aircraft_input_folder_btn)
input_layout.addRow("输入文件夹:", input_hbox)
# 说明文字
info_label = QLabel("注意:程序会自动搜索当前文件夹下的CouplingTest文件夹进行处理")
info_label.setStyleSheet("color: blue; font-size: 12px; margin: 5px;")
info_label.setWordWrap(True)
input_layout.addRow("", info_label)
layout.addWidget(input_group)
# 输出文件夹选择
output_group = QGroupBox("输出设置")
output_layout = QFormLayout(output_group)
output_hbox = QHBoxLayout()
self.aircraft_output_folder_line = QLineEdit()
self.aircraft_output_folder_btn = QPushButton("浏览")
self.aircraft_output_folder_btn.clicked.connect(self.browse_aircraft_output_folder)
output_hbox.addWidget(self.aircraft_output_folder_line)
output_hbox.addWidget(self.aircraft_output_folder_btn)
output_layout.addRow("输出文件夹:", output_hbox)
layout.addWidget(output_group)
# 控制按钮
button_layout = QHBoxLayout()
self.aircraft_process_btn = QPushButton("开始处理")
self.aircraft_process_btn.clicked.connect(self.start_aircraft_processing)
self.aircraft_process_btn.setEnabled(False) # 初始禁用
button_layout.addWidget(self.aircraft_process_btn)
layout.addLayout(button_layout)
# 进度条
self.aircraft_progress_bar = QProgressBar()
self.aircraft_progress_bar.setRange(0, 100)
self.aircraft_progress_bar.setValue(0)
layout.addWidget(self.aircraft_progress_bar)
# 日志区域
log_group = QGroupBox("处理日志")
log_layout = QVBoxLayout(log_group)
self.aircraft_log_text = QTextEdit()
self.aircraft_log_text.setMaximumHeight(200)
log_layout.addWidget(self.aircraft_log_text)
layout.addWidget(log_group)
def setup_remote_tab(self):
"""设置遥控器数据处理标签页"""
layout = QVBoxLayout(self.remote_tab)
# 数据类型说明
info_label = QLabel("注意:请确保输入文件夹路径包含 'Coupling' 文件夹,这是遥控器射频耦合测试数据所在的位置")
info_label.setStyleSheet("color: blue; font-size: 12px; margin: 5px;")
info_label.setWordWrap(True)
layout.addWidget(info_label)
# 输入文件夹选择
input_group = QGroupBox("输入文件夹 (应包含 Coupling 子文件夹)")
input_layout = QHBoxLayout()
self.remote_input_label = QLabel("未选择")
self.remote_input_button = QPushButton("选择输入文件夹")
self.remote_input_button.clicked.connect(self.select_remote_input_folder)
input_layout.addWidget(self.remote_input_label)
input_layout.addWidget(self.remote_input_button)
input_group.setLayout(input_layout)
layout.addWidget(input_group)
# 输出文件夹选择
output_group = QGroupBox("输出文件夹")
output_layout = QHBoxLayout()
self.remote_output_label = QLabel("未选择")
self.remote_output_button = QPushButton("选择输出文件夹")
self.remote_output_button.clicked.connect(self.select_remote_output_folder)
output_layout.addWidget(self.remote_output_label)
output_layout.addWidget(self.remote_output_button)
output_group.setLayout(output_layout)
layout.addWidget(output_group)
# 处理按钮
self.remote_process_button = QPushButton("开始处理遥控器耦合数据")
self.remote_process_button.clicked.connect(self.start_remote_processing)
self.remote_process_button.setEnabled(False)
layout.addWidget(self.remote_process_button)
# 进度条
self.remote_progress_bar = QProgressBar()
self.remote_progress_bar.setVisible(False)
layout.addWidget(self.remote_progress_bar)
# 状态显示
self.remote_status_text = QTextEdit()
self.remote_status_text.setMaximumHeight(150)
layout.addWidget(self.remote_status_text)
# 结果显示
self.remote_result_text = QTextEdit()
self.remote_result_text.setReadOnly(True)
layout.addWidget(self.remote_result_text)
def browse_aircraft_input_folder(self):
"""浏览飞机数据输入文件夹"""
folder = QFileDialog.getExistingDirectory(self, "选择输入文件夹")
if folder:
self.aircraft_input_folder_line.setText(folder)
# 启用处理按钮
self.check_aircraft_enable_process_btn()
def browse_aircraft_output_folder(self):
"""浏览飞机数据输出文件夹"""
folder = QFileDialog.getExistingDirectory(self, "选择输出文件夹")
if folder:
self.aircraft_output_folder_line.setText(folder)
# 启用处理按钮
self.check_aircraft_enable_process_btn()
def check_aircraft_enable_process_btn(self):
"""检查是否启用飞机数据处理按钮"""
input_path = self.aircraft_input_folder_line.text()
output_path = self.aircraft_output_folder_line.text()
self.aircraft_process_btn.setEnabled(bool(input_path and output_path))
def start_aircraft_processing(self):
"""开始处理飞机数据"""
input_path = self.aircraft_input_folder_line.text()
output_path = self.aircraft_output_folder_line.text()
if not input_path or not output_path:
QMessageBox.warning(self, "警告", "请输入文件夹和输出文件夹")
return
# 禁用按钮
self.aircraft_process_btn.setEnabled(False)
# 创建处理线程
self.aircraft_thread = AircraftProcessingThread(self.aircraft_processor, input_path, output_path)
self.aircraft_thread.progress_signal.connect(self.update_aircraft_progress)
self.aircraft_thread.log_signal.connect(self.log_aircraft_message)
self.aircraft_thread.finished_signal.connect(self.aircraft_processing_finished)
self.aircraft_thread.start()
def update_aircraft_progress(self, value):
"""更新飞机数据处理进度条"""
self.aircraft_progress_bar.setValue(value)
def log_aircraft_message(self, message):
"""添加飞机数据处理日志消息"""
self.aircraft_log_text.append(message)
def aircraft_processing_finished(self):
"""飞机数据处理完成"""
self.aircraft_process_btn.setEnabled(True)
self.aircraft_progress_bar.setValue(0)
def select_remote_input_folder(self):
"""选择遥控器数据输入文件夹"""
folder = QFileDialog.getExistingDirectory(self, "选择包含 Coupling 文件夹的输入路径")
if folder:
# 检查是否包含Coupling文件夹
has_coupling = False
for root, dirs, files in os.walk(folder):
if any("Coupling" in d or "coupling" in d for d in dirs):
has_coupling = True
break
# 显示警告信息但允许继续
if not has_coupling:
QMessageBox.warning(
self,
"警告",
f"选定的文件夹 '{folder}' 中未发现 'Coupling' 子文件夹。\n\n"
"此工具专门用于处理遥控器射频耦合测试数据,通常存储在 'Coupling' 文件夹中。\n\n"
"如果数据在其他文件夹中,仍可继续处理。"
)
self.remote_input_label.setText(folder)
self.check_remote_ready_to_process()
def select_remote_output_folder(self):
"""选择遥控器数据输出文件夹"""
folder = QFileDialog.getExistingDirectory(self, "选择输出文件夹")
if folder:
self.remote_output_label.setText(folder)
self.check_remote_ready_to_process()
def check_remote_ready_to_process(self):
"""检查是否准备好处理遥控器数据"""
if self.remote_input_label.text() != "未选择" and self.remote_output_label.text() != "未选择":
self.remote_process_button.setEnabled(True)
else:
self.remote_process_button.setEnabled(False)
def start_remote_processing(self):
"""开始处理遥控器数据"""
input_folder = self.remote_input_label.text()
output_folder = self.remote_output_label.text()
if not os.path.exists(input_folder):
QMessageBox.warning(self, "警告", "输入文件夹不存在!")
return
if not os.path.exists(output_folder):
QMessageBox.warning(self, "警告", "输出文件夹不存在!")
return
# 验证是否包含Coupling文件夹
has_coupling = False
for root, dirs, files in os.walk(input_folder):
if "Coupling" in dirs:
has_coupling = True
break
if not has_coupling:
reply = QMessageBox.question(
self,
"确认",
"选定的输入文件夹中未发现 'Coupling' 子文件夹。\n\n"
"此工具专门用于处理遥控器射频耦合测试数据,通常存储在 'Coupling' 文件夹中。\n\n"
"是否仍要继续处理?",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No
)
if reply == QMessageBox.No:
return
self.remote_process_button.setEnabled(False)
self.remote_progress_bar.setVisible(True)
self.remote_status_text.append("开始处理遥控器射频耦合测试数据...")
self.remote_thread = RemoteProcessingThread(self.remote_processor, input_folder, output_folder)
self.remote_thread.progress_signal.connect(self.update_remote_progress)
self.remote_thread.log_signal.connect(self.update_remote_status)
self.remote_thread.finished_signal.connect(self.remote_processing_finished)
self.remote_thread.start()
def update_remote_progress(self, value):
"""更新遥控器数据处理进度"""
self.remote_progress_bar.setValue(value)
def update_remote_status(self, message):
"""更新遥控器数据处理状态"""
self.remote_status_text.append(message)
def remote_processing_finished(self, created_files):
"""遥控器数据处理完成"""
self.remote_progress_bar.setVisible(False)
self.remote_process_button.setEnabled(True)
if created_files:
result_text = f"处理完成!共创建了 {len(created_files)} 个文件:\n"
for file_path in created_files:
result_text += f"- {file_path}\n"
self.remote_result_text.setPlainText(result_text)
else:
self.remote_result_text.setPlainText("处理完成,但没有创建任何文件。")
def main():
app = QApplication(sys.argv)
# 设置应用样式
app.setStyle('Fusion')
window = MainWindow()
window.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()
射频测试带界面
luobinrobin2026-01-19 10:21
相关推荐
不脱发的程序猿2 小时前
使用Python高效对比多个相似的CAN DBC数据winfredzhang2 小时前
构建自动化 Node.js 项目管理工具:从文件夹监控到一键联动运行AI_56782 小时前
Airflow“3分钟上手”教程:用Python定义定时数据清洗任务Aurora-Borealis.2 小时前
Day 38 GPU训练和call方法Ulyanov2 小时前
PyVista三维战场仿真实战深蓝电商API2 小时前
Scrapy爬虫部署到Scrapyd服务端详解无垠的广袤2 小时前
【工业树莓派 CM0 NANO 单板计算机】YOLO26 部署方案STLearner2 小时前
AAAI 2026 | 时间序列(Time Series) 论文总结[下] (分类,异常检测,基础模型,表示学习,生成)科研鬼才(bushi3 小时前
项目文件夹规范