CF_127E reader Display

本文探讨了一种策略,通过最小化操作次数来翻转二维矩阵中的像素,以达到特定图案的显示效果。重点在于理解操作原理、发现规律并实现算法优化,最终目标是高效地完成矩阵翻转,适用于电子阅读器等设备的软件编程。

这道题其实找到规律之后其实不难,和破损的键盘一样,都有点递推转移的感觉


题意:

你可以进行这样一次操作:选一个点,然后把这个点横切竖切直到正对角线上面的点,全部翻转过来,问你要进行多少次操作才能把所有的点都翻转过来

思路:

多次模拟之后可以发现最右上方的点仅能是选择自己才能翻转,之后与这个相邻的点变成了两个最右上方,而这两个点和刚才那个点一样仅能由自己翻转

还有一个要注意的这个只能影响对角线一侧的

所以只要在对角线一侧一直从最右边上到下遍历翻转就好了

Description

After years of hard work scientists invented an absolutely new e-reader display. The new display has a larger resolution, consumes less energy and its production is cheaper. And besides, one can bend it. The only inconvenience is highly unusual management. For that very reason the developers decided to leave the e-readers' software to programmers.

The display is represented by n × n square of pixels, each of which can be either black or white. The display rows are numbered with integers from 1 to n upside down, the columns are numbered with integers from 1 to n from the left to the right. The display can perform commands like "x, y". When a traditional display fulfills such command, it simply inverts a color of (x, y), where x is the row number and y is the column number. But in our new display every pixel that belongs to at least one of the segments (x, x) - (x, y)and (y, y) - (x, y) (both ends of both segments are included) inverts a color.

For example, if initially a display 5 × 5 in size is absolutely white, then the sequence of commands (1, 4)(3, 5)(5, 1)(3, 3)leads to the following changes:

You are an e-reader software programmer and you should calculate minimal number of commands needed to display the picture. You can regard all display pixels as initially white.

Input

The first line contains number n (1 ≤ n ≤ 2000).

Next n lines contain n characters each: the description of the picture that needs to be shown. "0" represents the white color and "1" represents the black color.

Output

Print one integer z — the least number of commands needed to display the picture.

Sample Input

Input
5
01110
10010
10001
10011
11110
Output
4
<pre name="code" class="cpp">#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<string>
#include<queue>
#include<cstdlib>
#include<algorithm>
#include<stack>
#include<map>
#include<queue>
#include<vector>
using namespace std;
char mp[2048][2048];
int flagr[2048],flagc[2048];
int n,cnt;
void print(){
    for(int i=0;i<n;i++)
    cout<<mp[i]<<endl;
    cout<<"*****************"<<endl;
    cout<<endl;
}
void search(){

  //  print();
    for(int i=0;i<n;i++){
        for(int j=n-1;j>i;j--){
            if((mp[i][j]-'0'+flagr[i]+flagc[j])&1){
                mp[i][j]='0';
                flagc[j]++,flagr[i]++,cnt++;
            }
            mp[i][j]='0';
        }
    }

    
   // 0 -> 1, 1 -> 0, '0' = 48;
    //  (mp[i][i] - '0' + flagr[i] +flagc[i] )%2 
    for(int i=0;i<n;i++){
        mp[i][i]=(mp[i][i] - '0' + flagr[i] +flagc[i] )%2 +48;
    }
    memset(flagr,0,sizeof flagr);
    memset(flagc,0,sizeof flagc);
    for(int i=n-1;i>=0;i--){
        for(int j=0;j<i;j++){
            if((mp[i][j]-'0'+flagr[i]+flagc[j])&1){
             //   print();
                flagc[j]++,flagr[i]++,cnt++;
            }
            mp[i][j]='0';
        }
    }
    for(int i=0;i<n;i++)
        if((mp[i][i] - '0' + flagr[i] +flagc[i] )%2) cnt++;
}
int main(){
    //freopen("in.txt","r",stdin);
   // freopen("out.txt","w",stdout);
    cin>>n;
    for(int i = 0;i < n;i++){
        scanf("%s",mp[i]);
    }
    search();
    printf("%d\n",cnt);
    return 0;
}



JsonReaderException: Additional text encountered after finished reading JSON content: |. Path '', line 1, position 58920. Newtonsoft.Json.JsonTextReader.Read () (at <97722d3abc9f4cf69f9e21e6770081b3>:0) Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize (Newtonsoft.Json.JsonReader reader, System.Type objectType, System.Boolean checkAdditionalContent) (at <97722d3abc9f4cf69f9e21e6770081b3>:0) Newtonsoft.Json.JsonSerializer.DeserializeInternal (Newtonsoft.Json.JsonReader reader, System.Type objectType) (at <97722d3abc9f4cf69f9e21e6770081b3>:0) Newtonsoft.Json.JsonSerializer.Deserialize (Newtonsoft.Json.JsonReader reader, System.Type objectType) (at <97722d3abc9f4cf69f9e21e6770081b3>:0) Newtonsoft.Json.JsonConvert.DeserializeObject (System.String value, System.Type type, Newtonsoft.Json.JsonSerializerSettings settings) (at <97722d3abc9f4cf69f9e21e6770081b3>:0) Newtonsoft.Json.JsonConvert.DeserializeObject[T] (System.String value, Newtonsoft.Json.JsonSerializerSettings settings) (at <97722d3abc9f4cf69f9e21e6770081b3>:0) Newtonsoft.Json.JsonConvert.DeserializeObject[T] (System.String value) (at <97722d3abc9f4cf69f9e21e6770081b3>:0) Siccity.GLTFUtility.Importer.ImportGLB (System.Byte[] bytes, Siccity.GLTFUtility.ImportSettings importSettings, UnityEngine.AnimationClip[]& animations) (at Assets/GLTFUtility-master/Scripts/Importer.cs:78) Siccity.GLTFUtility.Importer.LoadFromBytes (System.Byte[] bytes, Siccity.GLTFUtility.ImportSettings importSettings) (at Assets/GLTFUtility-master/Scripts/Importer.cs:47) LoadModel.<ImportGLBAsync>b__15_0 (System.Byte[] data) (at Assets/Scripts/LoadModel.cs:76) LoadModel+<>c__DisplayClass19_0.<Load>b__0 (System.Byte[] data) (at Assets/Scripts/LoadModel.cs:121) LoadBytes+<LoadBYTE>d__6.MoveNext () (at Assets/Scripts/LoadBytes.cs:48) UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) (at <27467129168e4121a08fe0095397099f>:0)
06-24
1.下载300595的季度财务报表csv到D:\tdxbase\Quarterly Reports\SZ_300595 2.时间范围从2020年到现在。 3.编写规则参考示例: pytdx.crawler crawler 其实本来想叫做downloader或者fetcher, 专门来处理http 协议的数据的下载和解析,分为两个阶段,下载阶段我们会使用urllib来下载数据,数据可以下载到临时文件(不传入path_to_download参数)或者下载到指定的位置(提供path_to_download参数),也支持指定chunk的分段下载进度的提示(使用reporthook传入处理函数), 下面是一个reporthook函数的例子 def demo_reporthook(downloaded, total_size): print("Downloaded {}, Total is {}".format(downloaded, total_size)) 获取历史专业财务数据列表 pytdx.crawler.HistoryFinancialListCrawler 实现了历史财务数据列表的读取,使用方式 from pytdx.crawler.history_financial_crawler import HistoryFinancialListCrawler crawler = HistoryFinancialListCrawler() list_data = crawler.fetch_and_parse() print(pd.DataFrame(data=list_data)) 结果 In [8]: print(pd.DataFrame(data=list_data)) filename filesize hash 0 gpcw20171231.zip 49250 0370b2703a0e23b4f9d87587f4a844cf 1 gpcw20170930.zip 2535402 780bc7c649cdce35567a44dc3700f4ce 2 gpcw20170630.zip 2739127 5fef91471e01ebf9b5d3628a87d1e73d 3 gpcw20170331.zip 2325626 a9bcebff37dd1d647f3159596bc2f312 4 gpcw20161231.zip 2749415 3fb3018c235f6c9d7a1448bdbe72281a 5 gpcw20160930.zip 2262567 8b629231ee9fad7e7c86f1e683cfb489 .. ... ... ... 75 gpcw19971231.zip 434680 316ce733f2a4f6b21c7865f94eee01c8 76 gpcw19970630.zip 196525 6eb5d8e5f43f7b19d756f0a2d91371f5 77 gpcw19961231.zip 363568 bfd59d42f9b6651861e84c483edb499b 78 gpcw19960630.zip 122145 18023e9f84565323874e8e1dbdfb2adb [79 rows x 3 columns] 其中,filename 字段为具体的财务数据文件地址, 后面的分别是哈希值和文件大小,在同步到本地时,可以作为是否需要更新本地数据的参考 获取历史专业财务数据内容 pytdx.crawler.HistoryFinancialCrawler 获取历史专业财务数据内容 使用上面返回的filename字段作为参数即可 from pytdx.crawler.base_crawler import demo_reporthook from pytdx.crawler.history_financial_crawler import HistoryFinancialCrawler datacrawler = HistoryFinancialCrawler() pd.set_option('display.max_columns', None) result = datacrawler.fetch_and_parse(reporthook=demo_reporthook, filename='gpcw19971231.zip', path_to_download="/tmp/tmpfile.zip") print(datacrawler.to_df(data=result)) 通过reader 读取数据 如果您自己管理文件的下载或者本地已经有对应的数据文件,可以使用我们的 HistoryFinancialReader来读取本地数据,使用方法和其它的Reader是类似的, 我们的reader同时支持.zip和解压后的.dat文件 from pytdx.reader import HistoryFinancialReader # print(HistoryFinancialReader().get_df('/tmp/tmpfile.zip')) print(HistoryFinancialReader().get_df('/tmp/gpcw20170930.dat')) 通过命令行工具hq_reader读取并保存到csv文件 -->rainx@JingdeMacBook-Pro:/tmp$ hqreader -d hf -o /tmp/gpcw20170930.csv /tmp/gpcw20170930.dat 写入到文件 : /tmp/gpcw20170930.csv
07-22
import csv import os import traceback import numpy as np import matplotlib.pyplot as plt from decimal import Decimal, getcontext from PyQt5.QtWidgets import (QApplication, QMainWindow, QFileDialog, QPushButton, QListWidget, QVBoxLayout, QHBoxLayout, QWidget, QLabel, QTextEdit, QProgressBar, QMessageBox, QTabWidget, QGroupBox, QSplitter, QAction, QMenuBar, QStatusBar, QDoubleSpinBox) from PyQt5.QtCore import Qt, QThread, pyqtSignal from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.figure import Figure import matplotlib import pywt from scipy import signal as sp_signal # 中文字体配置 matplotlib.use('Qt5Agg') plt.rcParams["font.family"] = ["Microsoft YaHei", "SimHei", "SimSun"] plt.rcParams["axes.unicode_minus"] = False # 正确显示负号 # Excel支持配置 EXCEL_SUPPORT = False Workbook = None Image = None Font = None Alignment = None Border = None Side = None PatternFill = None get_column_letter = None try: from openpyxl import Workbook from openpyxl.drawing.image import Image from openpyxl.styles import Font, Alignment, Border, Side, PatternFill from openpyxl.utils import get_column_letter EXCEL_SUPPORT = True except ImportError: EXCEL_SUPPORT = False class MplCanvas(FigureCanvas): """Matplotlib画布:适配Qt界面""" def __init__(self, parent=None, width=5, height=4, dpi=100): self.fig = Figure(figsize=(width, height), dpi=dpi) self.axes = self.fig.add_subplot(111) super(MplCanvas, self).__init__(self.fig) self.fig.tight_layout() class WorkerThread(QThread): """后台处理线程:保留原始计算逻辑""" progress_updated = pyqtSignal(int) status_updated = pyqtSignal(str) result_ready = pyqtSignal(object) finished = pyqtSignal() error_occurred = pyqtSignal(str) def __init__(self, file_paths, target_freq_range=[100, 5000]): super().__init__() self.file_paths = file_paths self.results = [] self.running = True self.target_freq_min, self.target_freq_max = target_freq_range def run(self): total_files = len(self.file_paths) for i, file_path in enumerate(self.file_paths): if not self.running: break try: filename = os.path.basename(file_path) self.status_updated.emit(f"正在处理: {filename}") # 读取CSV数据(使用原始程序的读取逻辑) time, input_sig, output_sig = self.read_csv_data(file_path) # 数据长度校验 if len(time) < 200: raise ValueError(f"有效数据行数不足(仅{len(time)}行),至少需要200行") # 计算增益和相位(核心逻辑与原始程序一致) gain, phase_shift, output_fundamental, freq = self.compute_gain_and_phase_shift( time, input_sig, time, output_sig ) # 保存完整结果 result = { 'file_path': file_path, 'file_name': filename, 'time': time, 'input_signal': input_sig, 'output_signal': output_sig, 'output_fundamental': output_fundamental, 'frequency': freq, 'gain': gain, 'phase_shift': phase_shift, # 补充原始程序没有但实用的信息 'sampling_rate': 1 / np.mean(np.diff(time)) if len(time) > 1 else 0 } self.results.append(result) self.result_ready.emit(result) self.status_updated.emit(f"处理完成: {filename}") except Exception as e: error_type = type(e).__name__ error_msg = f"处理 {os.path.basename(file_path)} 失败: {error_type} - {str(e)}" self.status_updated.emit(error_msg) self.error_occurred.emit(error_msg) print(f"\n{error_msg}") traceback.print_exc() progress = int((i + 1) / total_files * 100) self.progress_updated.emit(progress) if self.running: success_count = len(self.results) fail_count = total_files - success_count status_msg = f"全部处理完成,成功 {success_count}/{total_files} 个文件" self.status_updated.emit(status_msg) else: self.status_updated.emit("处理已取消") self.finished.emit() def stop(self): self.running = False if self.isRunning(): self.wait(2000) def read_csv_data(self, filename): """读取CSV文件(与原始程序逻辑一致)""" time, input_sig, output_sig = [], [], [] try: with open(filename, 'r', encoding='utf-8') as f: reader = csv.reader(f) # 原始程序逻辑:读取标题行后开始读取数据 header = next(reader) # 假设第一行为标题 for row in reader: if len(row) < 3: continue # 跳过不完整的行 try: t = float(row[0]) x = float(row[1]) y = float(row[2]) time.append(t) input_sig.append(x) output_sig.append(y) except ValueError: continue # 跳过格式错误的行 if not time: raise ValueError("未找到有效数据") return np.array(time), np.array(input_sig), np.array(output_sig) except Exception as e: raise type(e)(f"读取CSV失败: {str(e)}") def estimate_frequency(self, time, signal): """频率估计(完全保留原始程序逻辑)""" # 原始程序的采样率计算方式 fs = 1 / np.mean(np.diff(time)) N = len(signal) fft_vals = np.fft.rfft(signal) freqs = np.fft.rfftfreq(N, d=1 / fs) # 找到最大幅值对应的频率(排除0频) peak_idx = np.argmax(np.abs(fft_vals[1:])) + 1 dominant_freq = freqs[peak_idx] # 频率范围检查(使用可配置的范围) if not (self.target_freq_min <= dominant_freq <= self.target_freq_max): raise ValueError( f"检测到的频率 {dominant_freq:.2f} Hz 不在有效范围内 ({self.target_freq_min}-{self.target_freq_max} Hz)") return dominant_freq, fs def extract_fundamental_component(self, time, signal, target_freq): """基波提取(完全保留原始程序逻辑)""" # 构造设计矩阵 [sin, cos] design_matrix = np.column_stack([ np.sin(2 * np.pi * target_freq * time), np.cos(2 * np.pi * target_freq * time) ]) # 最小二乘求解系数 [I, Q] try: coeffs, residuals, rank, s = np.linalg.lstsq(design_matrix, signal, rcond=None) except: raise RuntimeError("最小二乘求解失败") I, Q = coeffs[0], coeffs[1] # 重建信号 fundamental = I * np.sin(2 * np.pi * target_freq * time) + \ Q * np.cos(2 * np.pi * target_freq * time) # 幅度和相位 amplitude = np.sqrt(I ** 2 + Q ** 2) phase_rad = np.arctan2(Q, I) # 与原始程序一致的相位计算 return fundamental, amplitude, phase_rad def compute_gain_and_phase_shift(self, input_time, input_signal, output_time, output_signal): """核心计算逻辑(完全保留原始程序)""" # 步骤1:估计输入信号频率 freq, fs = self.estimate_frequency(input_time, input_signal) print(f"检测到信号频率: {freq:.2f} Hz") # 确保时间轴一致(原始程序的校验逻辑) if not np.allclose(input_time, output_time, atol=1e-6): raise ValueError("输入和输出的时间轴不一致") time = input_time # 步骤2:提取基波(与原始程序一致) _, A_in, phi_in = self.extract_fundamental_component(time, input_signal, freq) output_fundamental, A_out, phi_out = self.extract_fundamental_component(time, output_signal, freq) # 步骤3:计算增益和相位差(原始程序逻辑) gain = A_out / A_in phase_shift_rad = phi_out - phi_in phase_shift_deg = np.rad2deg(phase_shift_rad) # 相位归一化(原始程序处理) phase_shift_deg = (phase_shift_deg + 180) % 360 - 180 print(f"增益(放大系数): {gain:.4f}") print(f"相位偏移: {phase_shift_deg:.2f}°") return gain, phase_shift_deg, output_fundamental, freq class WaveformAnalyzer(QMainWindow): """主窗口:还原所有功能""" def __init__(self): super().__init__() self.setWindowTitle("波形分析工具(完整功能版)") self.setGeometry(100, 100, 1200, 800) # 初始化变量 self.file_paths = [] self.results = [] self.current_result_idx = -1 self.worker = None self.temp_image_dir = os.path.join(os.path.dirname(__file__), "temp_images") if not os.path.exists(self.temp_image_dir): os.makedirs(self.temp_image_dir) # 初始化UI和菜单 self.init_ui() self.init_menu() def init_menu(self): """初始化菜单栏(还原完整菜单)""" menubar = self.menuBar() # 文件菜单 file_menu = menubar.addMenu("文件") add_file_action = QAction("添加CSV文件", self) add_file_action.triggered.connect(self.add_files) file_menu.addAction(add_file_action) save_csv_action = QAction("保存CSV汇总", self) save_csv_action.triggered.connect(self.save_all_results) file_menu.addAction(save_csv_action) export_excel_action = QAction("导出Excel报告", self) export_excel_action.triggered.connect(self.export_excel_report) file_menu.addAction(export_excel_action) exit_action = QAction("退出", self) exit_action.triggered.connect(self.close) file_menu.addAction(exit_action) # 帮助菜单 help_menu = menubar.addMenu("帮助") about_action = QAction("关于", self) about_action.triggered.connect(self.show_about) help_menu.addAction(about_action) help_action = QAction("使用帮助", self) help_action.triggered.connect(self.show_help) help_menu.addAction(help_action) # 状态栏 self.status_bar = QStatusBar() self.setStatusBar(self.status_bar) self.status_bar.showMessage("就绪") def init_ui(self): """初始化UI组件(还原完整功能)""" # 主布局 main_widget = QWidget() main_layout = QHBoxLayout(main_widget) self.setCentralWidget(main_widget) # 左侧面板 left_panel = QWidget() left_layout = QVBoxLayout(left_panel) left_layout.setContentsMargins(10, 10, 10, 10) left_layout.setSpacing(10) # 1. 待处理文件列表 file_group = QGroupBox("待处理文件(CSV格式)") file_layout = QVBoxLayout(file_group) self.file_list = QListWidget() self.file_list.setSelectionMode(QListWidget.ExtendedSelection) file_layout.addWidget(self.file_list) file_btn_layout = QHBoxLayout() self.add_btn = QPushButton("添加文件") self.remove_btn = QPushButton("移除选中") self.clear_btn = QPushButton("清空列表") self.add_btn.clicked.connect(self.add_files) self.remove_btn.clicked.connect(self.remove_files) self.clear_btn.clicked.connect(self.clear_files) file_btn_layout.addWidget(self.add_btn) file_btn_layout.addWidget(self.remove_btn) file_btn_layout.addWidget(self.clear_btn) file_layout.addLayout(file_btn_layout) left_layout.addWidget(file_group) # 2. 处理控制(还原频率范围设置) process_group = QGroupBox("处理控制") process_layout = QVBoxLayout(process_group) range_layout = QHBoxLayout() range_layout.addWidget(QLabel("目标频率区间(Hz):")) self.freq_min_input = QDoubleSpinBox() self.freq_min_input.setRange(1, 10000) self.freq_min_input.setSingleStep(10) self.freq_min_input.setValue(100) self.freq_min_input.setDecimals(0) range_layout.addWidget(self.freq_min_input) range_layout.addWidget(QLabel("-")) self.freq_max_input = QDoubleSpinBox() self.freq_max_input.setRange(1, 10000) self.freq_max_input.setSingleStep(10) self.freq_max_input.setValue(5000) self.freq_max_input.setDecimals(0) range_layout.addWidget(self.freq_max_input) process_layout.addLayout(range_layout) self.process_btn = QPushButton("开始处理") self.retry_btn = QPushButton("重试处理") self.process_btn.clicked.connect(self.start_processing) self.retry_btn.clicked.connect(self.start_processing) self.retry_btn.setEnabled(False) process_layout.addWidget(self.process_btn) process_layout.addWidget(self.retry_btn) self.progress_bar = QProgressBar() self.progress_bar.setValue(0) process_layout.addWidget(self.progress_bar) self.status_label = QLabel("就绪") self.status_label.setAlignment(Qt.AlignCenter) self.status_label.setStyleSheet("color: #2E86AB; font-weight: bold;") process_layout.addWidget(self.status_label) self.error_label = QLabel("") self.error_label.setAlignment(Qt.AlignCenter) self.error_label.setStyleSheet("color: #A23B72;") self.error_label.setWordWrap(True) process_layout.addWidget(self.error_label) left_layout.addWidget(process_group) # 3. 处理结果列表 result_group = QGroupBox("处理结果(双击失败项看详情)") result_layout = QVBoxLayout(result_group) self.result_list = QListWidget() self.result_list.itemClicked.connect(self.on_result_selected) self.result_list.itemDoubleClicked.connect(self.show_result_error) result_layout.addWidget(self.result_list) left_layout.addWidget(result_group) # 右侧面板 right_panel = QWidget() right_layout = QVBoxLayout(right_panel) right_layout.setContentsMargins(10, 10, 10, 10) right_layout.setSpacing(10) # 标签页控件(还原所有标签页) self.tabs = QTabWidget() self.waveform_tab = QWidget() waveform_layout = QVBoxLayout(self.waveform_tab) self.waveform_canvas = MplCanvas(self, width=8, height=5, dpi=100) waveform_layout.addWidget(self.waveform_canvas) self.tabs.addTab(self.waveform_tab, "波形图(输入+输出+基波)") self.data_tab = QWidget() data_layout = QVBoxLayout(self.data_tab) self.data_display = QTextEdit() self.data_display.setReadOnly(True) data_layout.addWidget(self.data_display) self.tabs.addTab(self.data_tab, "分析数据") self.fft_tab = QWidget() fft_layout = QVBoxLayout(self.fft_tab) self.fft_canvas = MplCanvas(self, width=8, height=5, dpi=100) fft_layout.addWidget(self.fft_canvas) self.tabs.addTab(self.fft_tab, "FFT分析") self.error_tab = QWidget() error_layout = QVBoxLayout(self.error_tab) self.error_display = QTextEdit() self.error_display.setReadOnly(True) error_layout.addWidget(self.error_display) self.tabs.addTab(self.error_tab, "错误日志") right_layout.addWidget(self.tabs) # 结果操作按钮(还原所有功能按钮) result_btn_layout = QHBoxLayout() self.save_plot_btn = QPushButton("保存当前图表") self.save_data_btn = QPushButton("保存当前数据") self.save_all_btn = QPushButton("保存CSV汇总") self.export_excel_btn = QPushButton("导出Excel报告") self.save_plot_btn.clicked.connect(self.save_current_plot) self.save_data_btn.clicked.connect(self.save_current_data) self.save_all_btn.clicked.connect(self.save_all_results) self.export_excel_btn.clicked.connect(self.export_excel_report) result_btn_layout.addWidget(self.save_plot_btn) result_btn_layout.addWidget(self.save_data_btn) result_btn_layout.addWidget(self.save_all_btn) result_btn_layout.addWidget(self.export_excel_btn) right_layout.addLayout(result_btn_layout) # 水平分割器 splitter = QSplitter(Qt.Horizontal) splitter.addWidget(left_panel) splitter.addWidget(right_panel) splitter.setSizes([350, 800]) main_layout.addWidget(splitter) def add_files(self): """添加CSV文件到列表""" file_paths, _ = QFileDialog.getOpenFileNames( self, "选择CSV文件", "", "CSV文件 (*.csv);;所有文件 (*.*)" ) if file_paths: added_count = 0 for path in file_paths: if path not in self.file_paths: if os.path.exists(path) and os.path.getsize(path) > 0: self.file_paths.append(path) self.file_list.addItem(os.path.basename(path)) added_count += 1 else: QMessageBox.warning(self, "文件无效", f"文件「{os.path.basename(path)}」不存在或为空") self.status_bar.showMessage(f"已添加 {added_count} 个有效CSV文件") def remove_files(self): """移除选中的文件""" selected_items = self.file_list.selectedItems() if not selected_items: QMessageBox.warning(self, "无选中文件", "请先选中要移除的文件") return for item in selected_items: idx = self.file_list.row(item) self.file_list.takeItem(idx) del self.file_paths[idx] self.status_bar.showMessage(f"已移除 {len(selected_items)} 个文件") def clear_files(self): """清空文件列表和结果""" self.file_list.clear() self.result_list.clear() self.file_paths = [] self.results = [] self.current_result_idx = -1 self.error_display.clear() self.error_label.setText("") self.status_bar.showMessage("文件列表已清空") def start_processing(self): """启动处理线程""" if not self.file_paths: QMessageBox.warning(self, "无待处理文件", "请先添加CSV文件") return if self.worker and self.worker.isRunning(): QMessageBox.information(self, "处理中", "当前已有文件在处理,请稍候") return freq_min = self.freq_min_input.value() freq_max = self.freq_max_input.value() if freq_min >= freq_max: QMessageBox.warning(self, "参数错误", "频率最小值不能大于或等于最大值") return # 重置状态 self.results = [] self.result_list.clear() self.current_result_idx = -1 self.progress_bar.setValue(0) self.status_label.setText("准备处理...") self.error_label.setText("") self.error_display.clear() # 创建并启动工作线程(使用原始计算逻辑) self.worker = WorkerThread( self.file_paths, target_freq_range=[freq_min, freq_max] ) self.worker.progress_updated.connect(self.update_progress) self.worker.status_updated.connect(self.update_status) self.worker.result_ready.connect(self.add_result) self.worker.finished.connect(self.process_finished) self.worker.error_occurred.connect(self.log_error) self.worker.start() # 更新按钮状态 self.process_btn.setText("取消处理") self.process_btn.clicked.disconnect() self.process_btn.clicked.connect(self.cancel_processing) self.retry_btn.setEnabled(False) self.add_btn.setEnabled(False) self.remove_btn.setEnabled(False) self.clear_btn.setEnabled(False) self.status_bar.showMessage(f"开始处理(目标区间:{freq_min:.0f}-{freq_max:.0f}Hz)") def cancel_processing(self): """取消正在进行的处理""" reply = QMessageBox.question( self, "确认取消", "确定要取消处理吗?当前文件可能处理不完整", QMessageBox.Yes | QMessageBox.No, QMessageBox.No ) if reply == QMessageBox.Yes and self.worker and self.worker.isRunning(): self.worker.stop() self.status_label.setText("正在取消处理...") self.status_bar.showMessage("正在取消处理") def update_progress(self, value): """更新进度条""" self.progress_bar.setValue(value) def update_status(self, text): """更新状态文本""" self.status_label.setText(text) self.status_bar.showMessage(text) def log_error(self, error_msg): """记录错误信息到日志""" self.error_label.setText("存在处理失败的文件,详见「错误日志」标签页") self.error_display.append(f"[{self.get_current_time()}] {error_msg}") filename = error_msg.split("处理 ")[1].split(" 失败")[0] self.result_list.addItem(f"❌ {filename}(处理失败)") def add_result(self, result): """添加成功处理的结果""" self.results.append(result) self.result_list.addItem( f"✅ {result['file_name']} - 频率: {result['frequency']:.2f}Hz | " f"增益: {result['gain']:.4f} | 相位偏移: {result['phase_shift']:.2f}°" ) def process_finished(self): """处理完成后恢复状态""" self.process_btn.setText("开始处理") self.process_btn.clicked.disconnect() self.process_btn.clicked.connect(self.start_processing) self.add_btn.setEnabled(True) self.remove_btn.setEnabled(True) self.clear_btn.setEnabled(True) self.retry_btn.setEnabled(True) success_count = len(self.results) total_count = len(self.file_paths) if success_count == 0: QMessageBox.warning(self, "处理结果", "所有文件处理失败,请查看错误日志") elif success_count < total_count: QMessageBox.information(self, "处理结果", f"部分文件处理成功:\n成功 {success_count} 个,失败 {total_count - success_count} 个\n" "双击失败项可查看详细错误信息") else: QMessageBox.information(self, "处理结果", "所有文件处理成功!") def on_result_selected(self, item): """选中结果时显示详情""" idx = self.result_list.row(item) if 0 <= idx < len(self.results): if item.text().startswith("✅"): self.current_result_idx = idx self.display_result(self.results[idx]) else: filename = item.text()[2:].split("(")[0] error_log = self.error_display.toPlainText() error_detail = "" for line in error_log.split("\n"): if f"处理 {filename} 失败:" in line: error_detail = line.split("失败: ")[1] break if not error_detail: error_detail = "未找到详细错误信息" self.data_display.setText(f"文件处理失败:\n文件名:{filename}\n错误原因:{error_detail}") def show_result_error(self, item): """双击失败项显示详细错误""" if item.text().startswith("❌"): filename = item.text()[2:].split("(")[0] error_log = self.error_display.toPlainText() error_detail = "未找到对应错误信息" for line in error_log.split("\n"): if f"处理 {filename} 失败:" in line: error_detail = line.split("失败: ")[1] break QMessageBox.warning(self, f"处理错误 - {filename}", f"错误原因:{error_detail}") def display_result(self, result): """显示结果数据和波形图""" # 1. 显示分析数据 data_text = f"=== 波形分析结果 ===\n" data_text += f"文件名:{result['file_name']}\n" data_text += f"频率:{result['frequency']:.6f} Hz\n" data_text += f"采样率:{result['sampling_rate']:.0f} Hz\n" data_text += f"增益(输出/输入):{result['gain']:.6f}\n" data_text += f"相位偏移(输出-输入):{result['phase_shift']:.2f} °\n" data_text += f"\n=== 数据维度 ===\n" data_text += f"时间点数量:{len(result['time'])} 个\n" data_text += f"信号时长:{result['time'][-1] - result['time'][0]:.6f} 秒\n" self.data_display.setText(data_text) # 2. 绘制波形图(与原始程序的绘图逻辑一致) self.waveform_canvas.axes.clear() self.waveform_canvas.axes.plot( result['time'], result['input_signal'], label=f'输入信号 ({result["frequency"]:.0f} Hz)', color='blue', linewidth=1.5 ) self.waveform_canvas.axes.plot( result['time'], result['output_signal'], label=f'输出信号 ({result["frequency"]:.0f} Hz)', color='green', linewidth=1.5 ) self.waveform_canvas.axes.plot( result['time'], result['output_fundamental'], label='提取的基波', color='red', linestyle='--', linewidth=2 ) self.waveform_canvas.axes.set_xlabel('时间 (s)') self.waveform_canvas.axes.set_ylabel('幅度') self.waveform_canvas.axes.set_title(f'{result["file_name"]} 波形分析') self.waveform_canvas.axes.legend() self.waveform_canvas.axes.grid(True, alpha=0.3) self.waveform_canvas.fig.tight_layout() self.waveform_canvas.draw() # 3. 绘制FFT图 self.plot_fft(result) def plot_fft(self, result): """绘制FFT频谱图""" self.fft_canvas.axes.clear() time = result['time'] signal = result['input_signal'] freq = result['frequency'] # 计算采样率(与原始程序一致) fs = 1 / np.mean(np.diff(time)) N = len(signal) # 计算FFT(与原始程序一致) fft_vals = np.fft.rfft(signal) freqs = np.fft.rfftfreq(N, d=1 / fs) fft_abs = np.abs(fft_vals) # 绘制FFT self.fft_canvas.axes.plot(freqs, fft_abs, color='blue', linewidth=1.0) self.fft_canvas.axes.axvline(x=freq, color='red', linestyle='--', label=f'主频: {freq:.2f} Hz') self.fft_canvas.axes.set_xlabel('频率 (Hz)') self.fft_canvas.axes.set_ylabel('幅度') self.fft_canvas.axes.set_title(f'{result["file_name"]} FFT分析') self.fft_canvas.axes.legend() self.fft_canvas.axes.grid(True, alpha=0.3) # 限制频率范围以便更好地查看 self.fft_canvas.axes.set_xlim(0, self.freq_max_input.value() + 1000) self.fft_canvas.fig.tight_layout() self.fft_canvas.draw() def save_current_plot(self): """保存当前显示的图表""" if self.current_result_idx == -1 or self.current_result_idx >= len(self.results): QMessageBox.warning(self, "无选中结果", "请先选中一个处理成功的结果") return result = self.results[self.current_result_idx] plot_type = "波形图" if self.tabs.currentIndex() == 0 else "FFT图" default_filename = f"{os.path.splitext(result['file_name'])[0]}_{plot_type}.png" file_path, _ = QFileDialog.getSaveFileName( self, f"保存{plot_type}", default_filename, "PNG文件 (*.png);;PDF文件 (*.pdf);;所有文件 (*.*)" ) if file_path: try: if self.tabs.currentIndex() == 0: self.waveform_canvas.fig.savefig(file_path, dpi=300, bbox_inches='tight') elif self.tabs.currentIndex() == 2: self.fft_canvas.fig.savefig(file_path, dpi=300, bbox_inches='tight') self.status_bar.showMessage(f"{plot_type}已保存至:{os.path.basename(file_path)}") except Exception as e: QMessageBox.critical(self, "保存失败", f"保存{plot_type}时出错:{str(e)}") def save_current_data(self): """保存当前选中结果的详细数据""" if self.current_result_idx == -1 or self.current_result_idx >= len(self.results): QMessageBox.warning(self, "无选中结果", "请先选中一个处理成功的结果") return result = self.results[self.current_result_idx] default_filename = f"{os.path.splitext(result['file_name'])[0]}_分析数据.csv" file_path, _ = QFileDialog.getSaveFileName( self, "保存分析数据", default_filename, "CSV文件 (*.csv);;文本文件 (*.txt);;所有文件 (*.*)" ) if file_path: try: with open(file_path, 'w', newline='', encoding='utf-8') as f: writer = csv.writer(f) writer.writerow(["时间(s)", "输入信号", "输出信号", "输出基波"]) for t, in_sig, out_sig, out_fund in zip( result['time'], result['input_signal'], result['output_signal'], result['output_fundamental']): writer.writerow([t, in_sig, out_sig, out_fund]) # 保存计算结果摘要 with open(file_path.replace('.csv', '_结果摘要.txt'), 'w', encoding='utf-8') as f: f.write(f"文件名: {result['file_name']}\n") f.write(f"频率: {result['frequency']:.6f} Hz\n") f.write(f"采样率: {result['sampling_rate']:.0f} Hz\n") f.write(f"增益: {result['gain']:.6f}\n") f.write(f"相位偏移: {result['phase_shift']:.2f}°\n") self.status_bar.showMessage(f"分析数据已保存至:{os.path.basename(file_path)}") except Exception as e: QMessageBox.critical(self, "保存失败", f"保存分析数据时出错:{str(e)}") def save_all_results(self): """保存所有成功结果的汇总CSV""" if not self.results: QMessageBox.warning(self, "无结果可保存", "暂无处理成功的结果") return default_filename = "波形分析结果汇总.csv" file_path, _ = QFileDialog.getSaveFileName( self, "保存所有结果汇总", default_filename, "CSV文件 (*.csv);;文本文件 (*.txt);;所有文件 (*.*)" ) if file_path: try: with open(file_path, 'w', newline='', encoding='utf-8') as f: writer = csv.writer(f) writer.writerow([ "序号", "文件名", "频率(Hz)", "采样率(Hz)", "增益", "相位偏移(°)", "信号时长(s)", "数据点数" ]) for i, result in enumerate(self.results, 1): duration = result['time'][-1] - result['time'][0] writer.writerow([ i, result['file_name'], f"{result['frequency']:.6f}", f"{result['sampling_rate']:.0f}", f"{result['gain']:.6f}", f"{result['phase_shift']:.2f}", f"{duration:.6f}", len(result['time']) ]) self.status_bar.showMessage(f"所有结果汇总已保存至:{os.path.basename(file_path)}") except Exception as e: QMessageBox.critical(self, "保存失败", f"保存结果汇总时出错:{str(e)}") def export_excel_report(self): """导出Excel报告(还原此功能)""" global EXCEL_SUPPORT if not self.results: QMessageBox.warning(self, "无结果可导出", "暂无处理成功的结果") return if not EXCEL_SUPPORT: reply = QMessageBox.question( self, "缺少依赖库", "导出Excel报告需要安装openpyxl库,是否现在安装?", QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes ) if reply == QMessageBox.Yes: try: import subprocess import sys subprocess.check_call([sys.executable, "-m", "pip", "install", "openpyxl"]) global Workbook, Image, Font, Alignment, Border, Side, PatternFill, get_column_letter from openpyxl import Workbook from openpyxl.drawing.image import Image from openpyxl.styles import Font, Alignment, Border, Side, PatternFill from openpyxl.utils import get_column_letter EXCEL_SUPPORT = True QMessageBox.information(self, "安装成功", "openpyxl已安装完成,请重新尝试导出") except Exception as e: QMessageBox.critical(self, "安装失败", f"安装openpyxl时出错:{str(e)}") return default_filename = "波形分析报告.xlsx" file_path, _ = QFileDialog.getSaveFileName( self, "导出Excel报告", default_filename, "Excel文件 (*.xlsx);;所有文件 (*.*)" ) if file_path: try: wb = Workbook() summary_ws = wb.active summary_ws.title = "结果汇总" header_font = Font(bold=True, color="FFFFFF") header_fill = PatternFill(start_color="366092", end_color="366092", fill_type="solid") border = Border( left=Side(style='thin'), right=Side(style='thin'), top=Side(style='thin'), bottom=Side(style='thin') ) center_align = Alignment(horizontal="center", vertical="center") headers = ["序号", "文件名", "频率(Hz)", "采样率(Hz)", "增益", "相位偏移(°)", "信号时长(s)", "数据点数"] for col, header in enumerate(headers, 1): cell = summary_ws.cell(row=1, column=col) cell.value = header cell.font = header_font cell.fill = header_fill cell.border = border cell.alignment = center_align for row, result in enumerate(self.results, 2): duration = result['time'][-1] - result['time'][0] summary_ws.cell(row=row, column=1, value=row - 1).border = border summary_ws.cell(row=row, column=1).alignment = center_align summary_ws.cell(row=row, column=2, value=result['file_name']).border = border summary_ws.cell(row=row, column=3, value=result['frequency']).border = border summary_ws.cell(row=row, column=3).alignment = center_align summary_ws.cell(row=row, column=4, value=result['sampling_rate']).border = border summary_ws.cell(row=row, column=4).alignment = center_align summary_ws.cell(row=row, column=5, value=result['gain']).border = border summary_ws.cell(row=row, column=5).alignment = center_align summary_ws.cell(row=row, column=6, value=result['phase_shift']).border = border summary_ws.cell(row=row, column=6).alignment = center_align summary_ws.cell(row=row, column=7, value=duration).border = border summary_ws.cell(row=row, column=7).alignment = center_align summary_ws.cell(row=row, column=8, value=len(result['time'])).border = border summary_ws.cell(row=row, column=8).alignment = center_align col_widths = [8, 30, 15, 12, 12, 15, 15, 12] for col, width in enumerate(col_widths, 1): summary_ws.column_dimensions[get_column_letter(col)].width = width temp_image_paths = [] for idx, result in enumerate(self.results, 1): ws_name = f"详情_{idx}_{os.path.splitext(result['file_name'])[0]}" if len(ws_name) > 31: ws_name = ws_name[:28] + "..." ws = wb.create_sheet(title=ws_name) params = [ ["文件名", result['file_name']], ["频率", f"{result['frequency']:.6f} Hz"], ["采样率", f"{result['sampling_rate']:.0f} Hz"], ["增益", f"{result['gain']:.6f}"], ["相位偏移", f"{result['phase_shift']:.2f} °"], ["信号时长", f"{result['time'][-1] - result['time'][0]:.6f} s"], ["数据点数", f"{len(result['time'])} 个"] ] ws.cell(row=1, column=1, value="=== 分析参数 ===").font = Font(bold=True, size=12) for row, (name, value) in enumerate(params, 3): ws.cell(row=row, column=1, value=name).font = Font(bold=True) ws.cell(row=row, column=2, value=value) temp_img_path = self._save_temp_waveform(result, idx) temp_image_paths.append(temp_img_path) img = Image(temp_img_path) img.width = 800 img.height = 500 ws.add_image(img, 'D3') ws.cell(row=20, column=1, value="=== 抽样数据 ===").font = Font(bold=True, size=12) data_headers = ["时间(s)", "输入信号", "输出信号", "输出基波"] for col, header in enumerate(data_headers, 1): ws.cell(row=22, column=col, value=header).font = Font(bold=True) step = max(1, len(result['time']) // 100) for row, i in enumerate(range(0, len(result['time']), step), 23): ws.cell(row=row, column=1, value=result['time'][i]) ws.cell(row=row, column=2, value=result['input_signal'][i]) ws.cell(row=row, column=3, value=result['output_signal'][i]) ws.cell(row=row, column=4, value=result['output_fundamental'][i]) ws.column_dimensions['A'].width = 15 ws.column_dimensions['B'].width = 30 ws.column_dimensions['D'].width = 5 wb.save(file_path) for img_path in temp_image_paths: if os.path.exists(img_path): os.remove(img_path) self.status_bar.showMessage(f"Excel报告已导出至:{os.path.basename(file_path)}") QMessageBox.information(self, "导出成功", f"Excel报告已保存至:\n{file_path}") except Exception as e: QMessageBox.critical(self, "导出失败", f"导出Excel报告时出错:{str(e)}") if 'temp_image_paths' in locals(): for img_path in temp_image_paths: if os.path.exists(img_path): try: os.remove(img_path) except: pass def _save_temp_waveform(self, result, index): """生成临时波形图用于Excel导出""" safe_filename = os.path.splitext(result['file_name'])[0].replace('/', '_').replace('\\', '_') temp_image_path = os.path.join(self.temp_image_dir, f"temp_waveform_{index}_{safe_filename}.png") fig, ax = plt.subplots(figsize=(12, 8), dpi=100) ax.plot(result['time'], result['input_signal'], label=f'输入信号 ({result["frequency"]:.0f} Hz)', color='blue', linewidth=1.2) ax.plot(result['time'], result['output_signal'], label=f'输出信号 ({result["frequency"]:.0f} Hz)', color='green', linewidth=1.2) ax.plot(result['time'], result['output_fundamental'], label='输出基波', color='red', linestyle='--', linewidth=1.5) ax.set_xlabel('时间 (s)', fontsize=11) ax.set_ylabel('幅度', fontsize=11) ax.set_title(f'波形分析 - {result["file_name"]}', fontsize=12, fontweight='bold') ax.legend(fontsize=10) ax.grid(True, alpha=0.3) plt.tight_layout() fig.savefig(temp_image_path, dpi=100, bbox_inches='tight') plt.close(fig) return temp_image_path def show_about(self): """显示关于对话框""" QMessageBox.about(self, "关于波形分析工具", "波形分析工具 v1.0\n\n" "功能:分析信号频率、增益和相位偏移\n" "特点:\n" "- 保留原始计算逻辑,确保结果准确\n" "- 支持批量处理和结果导出\n" "- 提供波形图和FFT频谱分析\n\n" "依赖库:PyQt5、numpy、matplotlib、scipy、openpyxl") def show_help(self): """显示使用帮助""" help_text = """ <h3>波形分析工具使用帮助</h3> <p><b>一、数据准备</b><br> 1. CSV文件格式:第一行为标题,后续行为数据<br> 2. 数据列:时间(秒)、输入信号、输出信号<br> 3. 编码格式:UTF-8</p> <p><b>二、操作步骤</b><br> 1. 点击「添加文件」选择CSV文件<br> 2. 设置「目标频率区间」(默认100-5000Hz)<br> 3. 点击「开始处理」启动分析<br> 4. 在「处理结果」列表查看详情</p> <p><b>三、结果导出</b><br> 1. 保存当前图表:保存当前显示的波形图或FFT图<br> 2. 保存当前数据:保存单个文件的详细数据<br> 3. 保存CSV汇总:导出所有结果的汇总表格<br> 4. 导出Excel报告:生成包含图表和数据的完整报告</p> """ QMessageBox.information(self, "使用帮助", help_text) def get_current_time(self): """获取当前时间(用于错误日志)""" from datetime import datetime return datetime.now().strftime("%Y-%m-%d %H:%M:%S") def closeEvent(self, event): """关闭窗口时清理资源""" if self.worker and self.worker.isRunning(): reply = QMessageBox.question( self, "确认退出", "当前有文件正在处理,确定要退出吗?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No ) if reply == QMessageBox.Yes: self.worker.stop() self.worker.wait() event.accept() else: event.ignore() return # 清理临时图片文件夹 if os.path.exists(self.temp_image_dir): try: for file in os.listdir(self.temp_image_dir): file_path = os.path.join(self.temp_image_dir, file) os.remove(file_path) os.rmdir(self.temp_image_dir) except: pass event.accept() if __name__ == "__main__": import sys app = QApplication(sys.argv) window = WaveformAnalyzer() window.show() sys.exit(app.exec_())这个程序对相位偏移的误差计算还是有点大,大概在1-2°,我需要的数据是在0.2°左右的精确数据,怎么样让数据受干扰少一些准确一些
最新发布
10-29
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值