import os
import re
import pandas as pd
import sys
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget,
QVBoxLayout, QHBoxLayout, QFormLayout,
QPushButton, QLineEdit, QTextEdit, QLabel,
QFileDialog, QMessageBox, QProgressBar)
from PyQt6.QtCore import Qt, QThread, pyqtSignal
import datetime
class FileProcessorThread(QThread):
progress_updated = pyqtSignal(int, str)
finished_processing = pyqtSignal(dict, list) # 修改信号类型,第一个参数是字典
def __init__(self, input_folder):
super().__init__()
self.input_folder = input_folder
def run(self):
"""在后台线程中处理文件"""
# 按文件夹路径分组存储文件
folder_groups = {}
# 遍历所有txt文件并按文件夹分组
for root, dirs, files in os.walk(self.input_folder):
for file in files:
if file.lower().endswith('.txt'):
full_path = os.path.join(root, file)
relative_path = os.path.relpath(full_path, self.input_folder)
folder_path = os.path.dirname(relative_path) # 获取文件所在子文件夹路径
if folder_path not in folder_groups:
folder_groups[folder_path] = []
folder_groups[folder_path].append({
'full_path': full_path,
'relative_path': relative_path,
'filename': file
})
# 处理每个文件夹的文件
all_results = {}
error_files = []
for folder_path, files_info in folder_groups.items():
folder_data = []
for i, file_info in enumerate(files_info):
try:
data = self.extract_data_from_txt(file_info['full_path'])
data['源文件'] = file_info['filename'] # 只保留文件名
folder_data.append(data)
# 发送进度更新
total_files = sum(len(files) for files in folder_groups.values())
current_file_index = sum(len(v) for k, v in folder_groups.items()
if list(folder_groups.keys()).index(k) < list(folder_groups.keys()).index(folder_path)) + i
progress = int((current_file_index + 1) / total_files * 100)
self.progress_updated.emit(progress, f"处理文件夹 '{folder_path or '根目录'}': {file_info['filename']}")
except Exception as e:
error_files.append((file_info['relative_path'], str(e)))
# 保存此文件夹的数据
all_results[folder_path] = folder_data
self.finished_processing.emit(all_results, error_files)
def extract_data_from_txt(self, file_path):
"""从txt文件中提取所需数据"""
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# 更准确地判断数据格式
# 新格式特征:包含ChipID、sdkVersion、fmVersion、hardVersion等字段
new_format_indicators = ['ChipID', 'sdkVersion', 'fmVersion', 'hardVersion']
old_format_indicators = ['SoftVersion', 'HardwareVersion', 'FirmVersion']
new_format_score = sum(1 for indicator in new_format_indicators if indicator in content)
old_format_score = sum(1 for indicator in old_format_indicators if indicator in content)
is_new_format = new_format_score > old_format_score
if is_new_format:
# 新格式模式
patterns = {
'产品条形码': r'产品条形码[::]\s*([^\n\r]+)',
'测试时间': r'测试时间[::]\s*([^\n\r]+)',
'ChipID': r'ChipID[::]\s*([^\n\r]+)',
'sdkVersion': r'sdkVersion[::]\s*([^\n\r]+)',
'fmVersion': r'fmVersion[::]\s*([^\n\r]+)',
'hardVersion': r'hardVersion[::]\s*([^\n\r]+)',
'GRDSETV2P4Freqcy': r'GRDSETV2P4Freqcy[::]\s*([^\n\r]+)',
'GRDSETV2P4AGCA': r'GRDSETV2P4AGCA[::]\s*([^\n\r]+)',
'GRDSETV2P4Result': r'GRDSETV2P4Result[::]\s*([^\n\r]+)',
'GRDSETV2P4Freqcy2': r'(?!.*GRDSETV2P4Freqcy.*GRDSETV2P4Freqcy)GRDSETV2P4Freqcy[::]\s*([^\n\r]+)', # 第二个频率
'GRDSETV2P4AGCB': r'GRDSETV2P4AGCB[::]\s*([^\n\r]+)',
'GRDSETV2P4Result2': r'(?!.*GRDSETV2P4Result.*GRDSETV2P4Result)GRDSETV2P4Result[::]\s*([^\n\r]+)', # 第二个结果
'GRDSETV5P8Freqcy': r'GRDSETV5P8Freqcy[::]\s*([^\n\r]+)',
'GRDSETV5P8AGCB': r'GRDSETV5P8AGCB[::]\s*([^\n\r]+)',
'GRDSETV5P8Result': r'GRDSETV5P8Result[::]\s*([^\n\r]+)',
'TestResult': r'TestResult[::]\s*([^\n\r]+)'
}
else:
# 原始格式模式
patterns = {
'产品条形码': r'产品条形码[::]\s*([^\n\r]+)',
'测试时间': r'测试时间[::]\s*([^\n\r]+)',
'TestResult': r'TestResult[::]\s*([^\n\r]+)',
'SoftVersion': r'SoftVersion[::]\s*([^\n\r]+)',
'HardwareVersion': r'HardwareVersion[::]\s*([^\n\r]+)',
'FirmVersion': r'FirmVersion[::]\s*([^\n\r]+)',
'PW2P4Freqcy': r'PW2P4Freqcy[::]\s*([^\n\r]+)',
'PWA2P4': r'PWA2P4[::]\s*([^\n\r]+)',
'PWA2P4Result': r'PWA2P4Result[::]\s*([^\n\r]+)',
'2P4CalibValue': r'2P4CalibValue[::]\s*([^\n\r]+)',
'PW5P8Freqcy': r'PW5P8Freqcy[::]\s*([^\n\r]+)',
'PWA5P8': r'PWA5P8[::]\s*([^\n\r]+)',
'PWA5P8Result': r'PWA5P8Result[::]\s*([^\n\r]+)',
'5P8CalibValue': r'5P8CalibValue[::]\s*([^\n\r]+)'
}
extracted_data = {}
for key, pattern in patterns.items():
match = re.search(pattern, content, re.MULTILINE)
extracted_data[key] = match.group(1).strip() if match else ''
return extracted_data
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("POWERTEST 数据提取工具")
self.setGeometry(100, 100, 600, 500)
# 创建中央部件
central_widget = QWidget()
self.setCentralWidget(central_widget)
# 创建主布局
main_layout = QVBoxLayout()
# 输入文件夹选择
input_layout = QHBoxLayout()
self.input_label = QLabel("输入文件夹:")
self.input_line_edit = QLineEdit()
self.input_button = QPushButton("浏览...")
self.input_button.clicked.connect(self.select_input_folder)
input_layout.addWidget(self.input_label)
input_layout.addWidget(self.input_line_edit)
input_layout.addWidget(self.input_button)
# 输出文件夹选择
output_layout = QHBoxLayout()
self.output_label = QLabel("输出文件夹:")
self.output_line_edit = QLineEdit()
self.output_button = QPushButton("浏览...")
self.output_button.clicked.connect(self.select_output_folder)
output_layout.addWidget(self.output_label)
output_layout.addWidget(self.output_line_edit)
output_layout.addWidget(self.output_button)
# 进度条
self.progress_bar = QProgressBar()
self.progress_bar.setVisible(False)
# 日志显示区域
self.log_text_edit = QTextEdit()
self.log_text_edit.setMaximumHeight(150)
self.log_text_edit.setReadOnly(True)
# 控制按钮
button_layout = QHBoxLayout()
self.process_button = QPushButton("开始处理")
self.process_button.clicked.connect(self.start_processing)
self.process_button.setEnabled(False)
self.clear_button = QPushButton("清空日志")
self.clear_button.clicked.connect(self.clear_log)
button_layout.addWidget(self.process_button)
button_layout.addWidget(self.clear_button)
# 添加到主布局
main_layout.addLayout(input_layout)
main_layout.addLayout(output_layout)
main_layout.addWidget(self.progress_bar)
main_layout.addWidget(QLabel("处理日志:"))
main_layout.addWidget(self.log_text_edit)
main_layout.addLayout(button_layout)
central_widget.setLayout(main_layout)
# 初始化变量
self.input_folder = ""
self.output_folder = ""
def select_input_folder(self):
"""选择输入文件夹"""
folder = QFileDialog.getExistingDirectory(self, "选择包含txt文件的文件夹")
if folder:
self.input_folder = folder
self.input_line_edit.setText(folder)
self.log_message(f"已选择输入文件夹: {folder}")
# 检查是否有txt文件
txt_count = self.count_txt_files(folder)
self.log_message(f"在文件夹中找到 {txt_count} 个txt文件")
# 启用处理按钮
self.process_button.setEnabled(txt_count > 0)
def select_output_folder(self):
"""选择输出文件夹"""
folder = QFileDialog.getExistingDirectory(self, "选择保存Excel文件的文件夹")
if folder:
self.output_folder = folder
self.output_line_edit.setText(folder)
self.log_message(f"已选择输出文件夹: {folder}")
def count_txt_files(self, folder):
"""统计文件夹及子文件夹中的txt文件数量"""
count = 0
for root, dirs, files in os.walk(folder):
for file in files:
if file.lower().endswith('.txt'):
count += 1
return count
def start_processing(self):
"""开始处理文件"""
if not self.input_folder:
QMessageBox.warning(self, "警告", "请选择输入文件夹!")
return
if not self.output_folder:
QMessageBox.warning(self, "警告", "请选择输出文件夹!")
return
# 创建处理线程
self.processor_thread = FileProcessorThread(self.input_folder)
self.processor_thread.progress_updated.connect(self.update_progress)
self.processor_thread.finished_processing.connect(self.on_processing_finished)
# 显示进度条并禁用按钮
self.progress_bar.setVisible(True)
self.process_button.setEnabled(False)
self.input_button.setEnabled(False)
self.output_button.setEnabled(False)
self.log_message("开始处理文件...")
self.processor_thread.start()
def update_progress(self, value, message):
"""更新进度条和消息"""
self.progress_bar.setValue(value)
self.log_message(message)
def on_processing_finished(self, all_results, error_files):
"""处理完成后的回调"""
# 隐藏进度条并启用按钮
self.progress_bar.setVisible(False)
self.process_button.setEnabled(True)
self.input_button.setEnabled(True)
self.output_button.setEnabled(True)
if not all_results and not error_files:
self.log_message("没有成功处理任何文件")
return
# 处理每个文件夹的结果
for folder_path, folder_data in all_results.items():
if not folder_data: # 跳过空文件夹
continue
# 拆分逗号分隔的数据
self.split_comma_separated_values(folder_data)
# 检查数据格式以确定列定义
if folder_data and 'ChipID' in folder_data[0]:
# 使用新格式列定义
df_columns = [
'源文件', '产品条形码', '测试时间', 'ChipID', 'sdkVersion',
'fmVersion', 'hardVersion', 'GRDSETV2P4Freqcy', 'GRDSETV2P4AGCA',
'GRDSETV2P4Result', 'GRDSETV2P4Freqcy2', 'GRDSETV2P4AGCB',
'GRDSETV2P4Result2', 'GRDSETV5P8Freqcy', 'GRDSETV5P8AGCB',
'GRDSETV5P8Result', 'TestResult'
]
else:
# 使用原始格式列定义
df_columns = [
'源文件', '产品条形码', '测试时间', 'SoftVersion',
'HardwareVersion', 'FirmVersion',
'PW2P4Freqcy', 'PWA2P4_1', 'PWA2P4_2', 'PWA2P4Result_1', 'PWA2P4Result_2', '2P4CalibValue',
'PW5P8Freqcy', 'PWA5P8_1', 'PWA5P8_2', 'PWA5P8Result_1', 'PWA5P8Result_2', '5P8CalibValue',
'TestResult'
]
# 构建DataFrame
df_data = []
for item in folder_data:
row = {}
for col in df_columns:
row[col] = item.get(col, '')
df_data.append(row)
df = pd.DataFrame(df_data)
# 生成Excel文件名 - 使用文件夹名称
if folder_path:
# 清理文件夹名称中的非法字符
safe_folder_name = re.sub(r'[<>:"/\\|?*]', '_', folder_path.replace('/', '_').replace('\\', '_'))
excel_filename = f"{safe_folder_name}_extracted_data.xlsx"
else:
# 根目录使用主文件夹名
main_folder_name = os.path.basename(self.input_folder)
safe_main_name = re.sub(r'[<>:"/\\|?*]', '_', main_folder_name)
excel_filename = f"{safe_main_name}_root_extracted_data.xlsx"
output_path = os.path.join(self.output_folder, excel_filename)
# 保存到Excel
try:
df.to_excel(output_path, index=False, engine='openpyxl')
self.log_message(f"文件夹 '{folder_path or '根目录'}' 的数据已保存到 {excel_filename}")
self.log_message(f" - 处理了 {len(folder_data)} 个txt文件")
except Exception as e:
self.log_message(f"保存文件夹 '{folder_path or '根目录'}' 的Excel文件时出错: {str(e)}")
# 记录错误文件
if error_files:
self.log_message(f"共有 {len(error_files)} 个文件处理失败:")
for filename, error in error_files:
self.log_message(f" - {filename}: {error}")
self.log_message("所有文件夹处理完成!")
def split_comma_separated_values(self, data_list):
"""将逗号分隔的数据拆分成两个独立的列"""
for item in data_list:
# 对于新格式,不需要拆分
# 对于原始格式,继续拆分
if 'PWA2P4' in item:
# 分割PWA2P4和PWA2P4Result(原始格式)
if item.get('PWA2P4'):
values = str(item['PWA2P4']).split(',')
if len(values) >= 2:
item['PWA2P4_1'] = values[0].strip()
item['PWA2P4_2'] = values[1].strip()
else:
item['PWA2P4_1'] = values[0].strip() if values else ''
item['PWA2P4_2'] = ''
if item.get('PWA2P4Result'):
values = str(item['PWA2P4Result']).split(',')
if len(values) >= 2:
item['PWA2P4Result_1'] = values[0].strip()
item['PWA2P4Result_2'] = values[1].strip()
else:
item['PWA2P4Result_1'] = values[0].strip() if values else ''
item['PWA2P4Result_2'] = ''
# 分割PWA5P8和PWA5P8Result(原始格式)
if item.get('PWA5P8'):
values = str(item['PWA5P8']).split(',')
if len(values) >= 2:
item['PWA5P8_1'] = values[0].strip()
item['PWA5P8_2'] = values[1].strip()
else:
item['PWA5P8_1'] = values[0].strip() if values else ''
item['PWA5P8_2'] = ''
if item.get('PWA5P8Result'):
values = str(item['PWA5P8Result']).split(',')
if len(values) >= 2:
item['PWA5P8Result_1'] = values[0].strip()
item['PWA5P8Result_2'] = values[1].strip()
else:
item['PWA5P8Result_1'] = values[0].strip() if values else ''
item['PWA5P8Result_2'] = ''
def log_message(self, message):
"""在日志区域添加消息"""
timestamp = datetime.datetime.now().strftime("%H:%M:%S")
self.log_text_edit.append(f"[{timestamp}] {message}")
QApplication.processEvents() # 确保界面及时更新
def clear_log(self):
"""清空日志"""
self.log_text_edit.clear()
def main():
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()
功率测试数据解析
最新推荐文章于 2026-07-02 16:02:29 发布
Python3.8
Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本
您可能感兴趣的与本文相关的镜像
Python3.8
Conda
Python
Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本
15

被折叠的 条评论
为什么被折叠?



