diff --git a/doc/需求相关资料/软件开发交互分析功能需求更新版.docx b/doc/需求相关资料/软件开发交互分析功能需求更新版.docx new file mode 100644 index 0000000..30ba86a --- /dev/null +++ b/doc/需求相关资料/软件开发交互分析功能需求更新版.docx @@ -0,0 +1,146 @@ +1.交互分析功能需求: + 粒子数据文件索引及打开功能; + 可对能谱进行重新刻度,或刻度数据导入; + 常用功能:可进行谱放大缩小,自适应屏幕调整能谱大小,常规或对数能谱显示,显示能谱信息、刻度参数等常规信息; + 粒子时间差统计,给出粒子时间差的统计结果,能够根据指定的时间差对符合事件进行筛选(功能5); + 能谱显示:可显示能量计数谱、二维符合谱;在符合时间未给出的情况下不显示符合能谱,只显示能量计数谱; + 能量计数谱可显示原始能谱、符合谱、反符合能谱(可选择探测器,如在信息中没有探测器的信息则此选项为不可选); + 能量技术谱分析可根据选定能峰的信息给出可能的核素信息,通过选择核素可同步显示该核素其他能峰对应能量区域的能谱信息,如果有能峰存在给出该区域的能峰拟合信息; + 时间切片功能,按指定的时间间隔统计特征能峰(或指定区域,即ROI)的计数率,给出时间段内计数率随测量时间的变化; + 多条件粒子筛选、显示及统计功能:通过时间差、能量、符合次数筛选不同类型的符合事件及其粒子信息,指定筛选给出粒子的类型(电子、光子),统计事件次数,实现可视化; + 数据拟合:通过默认或输入的公式对指定(ROI区域)的能峰进行拟合,给出峰位、FWHM、峰面积、本底计数等信息,可在能谱上显示拟合能谱曲线及本底曲线(只在能量计数谱上显示); + 符合能谱数据统计:统计ROI区域的计数,针对二维符合能谱ROI区域可设为圆形或任意角度的长方形,统计区域中不同能量的计数(及其粒子信息)。 + 二维符合谱的显示:模式1,横轴为初级入射粒子的能量keV,纵轴为次级入射粒子的能量keV之和;模式2:横轴为入射γ、X光子的能量keV,纵轴为电子的能量keV; +2.目前使用的峰拟合函数: +光子 + f_peak=f_peak=@(A,delt,H,W,C,P,x)A*exp(-(x-P).^2 ./(2*delt^2))+H./(1+exp((x-P)./W))+C; + 电子 + f_epeak=@(A,Bg,a,m.delt,a)Bg+A*(1+((x)./a).^2).^(-m).*exp(-delt.*atan((x)./a)) + +Matlab峰拟合脚本(γ能谱): +clear; +clc; +close all; +%% 参数设置 +fname = 'SampleA.TKA'; +count = load(fname); % * .TKA 是纯文本列向量 +% load('E:\UnCSSSP\Matspace\electron\si_sp.mat') +% count=data1(:,2); +% 能量刻度: En = a * ch + b +par_encal = [3.509e-1, -1.965e-2]; % [a, b] 单位: keV/channel +a = par_encal(1); +b = par_encal(2); +x_ch=1:length(count); +x_en=a*x_ch+b; +% 峰形刻度: FWHM(keV) = p_form(1)*sqrt(En) + p_form(2) +p_form = [3.968e-2, 3.661e-1]; % [k, c] +% 拟合峰能量(keV)输入峰能量 +E_peak =input('指定全能峰能量keV:'); +% 计算 FWHM 和 高斯标准差 +FWHM = p_form(1) * sqrt(E_peak) + p_form(2); % keV +delt = FWHM / 2.355; % 高斯 σ(keV) +% 转换为道址(用于提取数据) +FWHM_ch = FWHM / a; % 道址宽度 +delt_ch = delt / a; % σ 道址 +p_chn = round((E_peak - b) / a); % 峰位道址 +% 提取数据范围:+-1.5 FWHM,共 p_w 个点 +p_w = 9; +channels = (p_chn - round(6*FWHM_ch)) : (p_chn + round(5 * FWHM_ch)); +% if length(channels) < p_w +% error('提取道址范围过小,请检查 FWHM 或 p_w'); +% end +% channels = channels(1:p_w); % 截取 p_w 个点 +% 提取能量和计数 +en_p = a * channels + b; % 实际能量(keV) +p_count = count(channels)'; % 注意:转置为列向量 +%% 定义拟合函数(所有参数显式列出) +% f(x) = Gaussian + Sigmoid tail + constant +f_peak = @(A, delt, H, W, C, P, x)A*exp(-(x-P).^2 ./(2*delt^2))+H./(1+exp((x-P)./W))+C; +%本底函数 +f_BG=@(A, delt, H, W, C,P , x)H./(1+exp((x-P)./W))+C; %本底函数 +%峰函数 +f_pk_s=@(A, delt, H, W,P , x)A.*exp(-(x-P).^2/(2.*delt.^2)); %计算得出的峰函数 +% 初始猜测 +startA = max(p_count) - min(p_count); % 峰高 +startC = min(p_count); % 基线 +startH = 0.1 * startA; % 拖尾幅度(经验) +startW = delt * 2; % 拖尾尺度(经验) +startP = E_peak; % 峰位 +opts = fitoptions('Method', 'NonlinearLeastSquares'); +opts.StartPoint = [startA, delt, startH, startW, startC, startP]; % A, delt, H, W, C +opts.Robust = 'LAR'; % 抗异常值 +opts.Display = 'off'; +opts.TolFun = 1e-10; +opts.TolX = 1e-10; +% 定义 fittype +ft = fittype(f_peak,'independent','x','dependent', 'y','coefficients', {'A', 'delt', 'H', 'W', 'C','P'}); +%% 拟合 +[info_pfit, gof, output] = fit(en_p', p_count', ft, opts); +% 提取结果 +A = info_pfit.A; +delt_fitted = info_pfit.delt; % 可更新 +H = info_pfit.H; +W = info_pfit.W; +C = info_pfit.C; +P = info_pfit.P; +%% 输出结果 +fprintf(' 全能峰拟合完成(%g keV):\n', E_peak); +fprintf(' A = %.2f\n', A); +fprintf(' H = %.2f\n', H); +fprintf(' W = %.2f keV\n', W); +fprintf(' P = %.2f keV\n',P); +fprintf(' C = %.2f (基线)\n', C); +fprintf(' R^2 = %.6f\n', gof.rsquare); +fprintf(' RMSE = %.4f\n', gof.rmse); +%% 计算峰计数 +peak_only = @(x) f_pk_s(A, delt_fitted, H, W, P, x); +background = @(x) f_BG(A, delt_fitted, H, W, C, P, x); +full_peak = @(x) f_peak(A, delt_fitted, H, W, C, P, x); +% 积分范围:+-3σ(能量空间) +x_low = E_peak - 3 * delt_fitted; +x_high = E_peak + 3 * delt_fitted; +% 积分求解峰面积 +Area_total = integral(full_peak, x_low, x_high, 'ArrayValued', true)/a; +BG_area = integral(background, x_low, x_high, 'ArrayValued', true)/a; +Peak_area = integral(peak_only, x_low, x_high, 'ArrayValued', true)/a; +fprintf('\n %.2f keV全能峰面积计算:\n',E_peak); +fprintf(' 全峰面积 = %.2f \n', Area_total); +fprintf(' 本底面积 = %.2f \n', BG_area); +fprintf(' 净峰面积 = %.2f \n', Peak_area); +%% 绘图 +monitorPositions = get(0, 'MonitorPositions'); +[~, numMonitors] = size(monitorPositions); +if numMonitors < 2 + error('仅检测到一个显示器,无法在副屏显示。'); +end +% 副屏位置 +secondaryMonitor = monitorPositions(2, :); % [left, bottom, width, height] +fig_pos=secondaryMonitor; +fig_pos(1) = fig_pos(1) + 8 ; +fig_pos(3) = fig_pos(3) - 8 ; +fig_pos(4) = fig_pos(4) - 85; +figure;%figure('WindowState','fullscreen') +set(gcf, 'Units', 'pixels', 'Position', fig_pos); +subplot(1,3,[1,2]); +plot(x_en,count); +xlabel('能量 (keV)'); +ylabel('计数'); +grid on; +subplot(1,3,3); +plot(en_p, p_count, 'bo', 'DisplayName', '数据点'); +hold on; +x_fine = linspace(min(en_p), max(en_p), 100); +plot(x_fine, f_peak(A, delt_fitted, H, W, C, P, x_fine), 'r-', 'LineWidth', 2, 'DisplayName', '拟合曲线'); +plot(x_fine, f_pk_s(A, delt_fitted, H, W, P, x_fine), 'g--', 'LineWidth', 2, 'DisplayName', '单峰曲线') +plot(x_fine, f_BG(A, delt_fitted, H, W, C, P, x_fine), 'c--', 'LineWidth', 1, 'DisplayName', '本底曲线') +plot(x_fine, f_pk_s(A, delt_fitted, H, W, P, x_fine)+f_BG(A, delt_fitted, H, W, C, P, x_fine), 'b--', 'LineWidth', 1, 'DisplayName', '单峰曲线') +xlabel('能量 (keV)'); +ylabel('计数'); +title(sprintf('%.1f keV 峰拟合', P)); +legend('Location', 'best'); +grid on; +hold off; +%% 添加文本框显示结果 +annotation('textbox', [0.898, 0.81, 0.1, 0.1], 'String',{sprintf('修正后峰位:%.3f', P),sprintf('修正后方差:%.3f keV', delt_fitted), ... +sprintf('拟合优度:%.5f',gof.rsquare),sprintf('净峰面积:%.0f', Peak_area)}, 'BackgroundColor', 'white', 'EdgeColor', 'k', 'FontSize', 10); + diff --git a/src/MeasureAnalysisHistoryForm/MeasureAnalysisHistoryForm.cpp b/src/MeasureAnalysisHistoryForm/MeasureAnalysisHistoryForm.cpp index 38d5891..61fc3f0 100644 --- a/src/MeasureAnalysisHistoryForm/MeasureAnalysisHistoryForm.cpp +++ b/src/MeasureAnalysisHistoryForm/MeasureAnalysisHistoryForm.cpp @@ -35,7 +35,7 @@ MeasureAnalysisHistoryForm::~MeasureAnalysisHistoryForm() delete ui; } -void MeasureAnalysisHistoryForm::loadHistoryInfo() +void MeasureAnalysisHistoryForm::loadHistoryInfo(const QString &history_name) { auto row_count = ui->tablew_history->rowCount(); for (int row = row_count - 1; row >= 0; --row) { @@ -57,9 +57,11 @@ void MeasureAnalysisHistoryForm::loadHistoryInfo() const QString& project_filename = project_dir_path.filePath(msproject_files.first()); MeasureAnalysisProjectModel project_model; project_model.LoadProjectModel(project_filename); + const QString& project_name = project_model.GetProjectName(); + int row = ui->tablew_history->rowCount(); ui->tablew_history->insertRow(row); - ui->tablew_history->setItem(row, 0, new QTableWidgetItem(project_model.GetProjectName())); + ui->tablew_history->setItem(row, 0, new QTableWidgetItem(project_name)); ui->tablew_history->item(row, 0)->setCheckState(Qt::Unchecked); ui->tablew_history->item(row, 0)->setData(Qt::UserRole, project_filename); const QString& is_measure_complete = project_model.GetIsMeasureComplete() ? QStringLiteral(u"是") : QStringLiteral(u"否"); @@ -73,6 +75,15 @@ void MeasureAnalysisHistoryForm::loadHistoryInfo() ui->tablew_history->setItem(row, 5, new QTableWidgetItem()); ui->tablew_history->setItem(row, 6, new QTableWidgetItem()); ui->tablew_history->setItem(row, 7, new QTableWidgetItem(project_model.GetDescriptionInfo())); + + if (history_name == project_name) { + for (int col = 0; col < ui->tablew_history->columnCount(); ++col) { + QTableWidgetItem* other_item = ui->tablew_history->item(row, col); + if (other_item) { + other_item->setFlags(other_item->flags() & ~(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable)); + } + } + } } } } @@ -126,12 +137,13 @@ void MeasureAnalysisHistoryForm::onReverseSelect() void MeasureAnalysisHistoryForm::onRemoveSelectedProject() { - QMap selected_project_list; + QMap > selected_project_list; auto row_count = ui->tablew_history->rowCount(); for (int row = row_count - 1; row >= 0; --row) { if (Qt::Checked == ui->tablew_history->item(row, 0)->checkState()) { + const QString& project_name = ui->tablew_history->item(row, 0)->text(); const QString& project_filename = ui->tablew_history->item(row, 0)->data(Qt::UserRole).toString(); - selected_project_list[row] = project_filename; + selected_project_list[row] = qMakePair(project_name, project_filename); } } if (selected_project_list.isEmpty()) { @@ -142,12 +154,15 @@ void MeasureAnalysisHistoryForm::onRemoveSelectedProject() return; } for (int row : selected_project_list.keys()) { - QFileInfo project_file_info(selected_project_list.value(row)); + const QString& project_name = selected_project_list.value(row).first; + ProjectList::Instance()->RmProjectModel(project_name); + const QString& project_filename = selected_project_list.value(row).second; + QFileInfo project_file_info(project_filename); const QString& project_dir_path = project_file_info.absoluteDir().absolutePath(); QDir(project_dir_path).removeRecursively(); QDir(project_dir_path).rmdir(project_dir_path); ui->tablew_history->removeRow(row); - LOG_INFO(QStringLiteral(u"测量分析项目\"%1\"已经删除.").arg(project_file_info.baseName())); + LOG_INFO(QStringLiteral(u"测量分析项目\"%1\"已经删除.").arg(project_name)); } } @@ -225,6 +240,5 @@ void MeasureAnalysisHistoryForm::onUpdateCloseHistoryProject(const QString &proj void MeasureAnalysisHistoryForm::onUpdateNewHistoryProject(const QString &project_name) { - Q_UNUSED(project_name); - loadHistoryInfo(); + loadHistoryInfo(project_name); } diff --git a/src/MeasureAnalysisHistoryForm/MeasureAnalysisHistoryForm.h b/src/MeasureAnalysisHistoryForm/MeasureAnalysisHistoryForm.h index b7f1fdf..de08f30 100644 --- a/src/MeasureAnalysisHistoryForm/MeasureAnalysisHistoryForm.h +++ b/src/MeasureAnalysisHistoryForm/MeasureAnalysisHistoryForm.h @@ -16,7 +16,7 @@ public: ~MeasureAnalysisHistoryForm(); private: - void loadHistoryInfo(); + void loadHistoryInfo(const QString& history_name = QString()); private slots: void onQueryHistory(); void onAllSelect(); diff --git a/src/main.cpp b/src/main.cpp index ed3fcc8..f391c5c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,6 +5,7 @@ #include #include #include "QsLogManage.h" +#include int main(int argc, char *argv[]) { @@ -61,5 +62,7 @@ int main(int argc, char *argv[]) MainWindow w; w.showMaximized(); - return app.exec(); + int ret = app.exec(); + QThreadPool::globalInstance()->waitForDone(); + return ret; }