857 lines
30 KiB
C++
857 lines
30 KiB
C++
#include "EfficiencyScale.h"
|
||
#include "ui_EfficiencyScale.h"
|
||
#include <QDir>
|
||
#include <QwtPlotCanvas>
|
||
#include <QwtText>
|
||
#include <QwtSymbol>
|
||
#include <QMessageBox>
|
||
#include "CustomQwtPlot.h"
|
||
#include <QFileDialog>
|
||
#include <QFile>
|
||
#include <QJsonDocument>
|
||
#include <QJsonObject>
|
||
#include <QJsonArray>
|
||
#include <QDateTime>
|
||
#include <QHeaderView>
|
||
#include <cmath>
|
||
#include <stdexcept>
|
||
#include <armadillo>
|
||
#include "DataCalcProcess/EnergyEfficiencyFit.h"
|
||
|
||
|
||
EfficiencyScale::EfficiencyScale(QWidget *parent)
|
||
: QDialog(parent)
|
||
, ui(new Ui::EfficiencyScale)
|
||
{
|
||
ui->setupUi(this);
|
||
|
||
QHBoxLayout* inflectionLayout = new QHBoxLayout();
|
||
QLabel* inflectionLabel = new QLabel(QStringLiteral(u"拐点能量(keV):"), this);
|
||
QLineEdit* inflectionEdit = new QLineEdit(this);
|
||
inflectionEdit->setObjectName("lineEdit_inflection_energy");
|
||
inflectionEdit->setPlaceholderText("0 = 全段统一拟合");
|
||
inflectionLayout->addWidget(inflectionLabel);
|
||
inflectionLayout->addWidget(inflectionEdit);
|
||
ui->verticalLayout_16->insertLayout(2, inflectionLayout);
|
||
|
||
this->_plot = new CustomQwtPlot(this);
|
||
ui->layout_fittingCurve->addWidget(this->_plot);
|
||
setupPlot();
|
||
|
||
_rawDataCurve = new QwtPlotCurve(QStringLiteral(u"原始数据"));
|
||
_rawDataCurve->setStyle(QwtPlotCurve::NoCurve);
|
||
_rawDataCurve->setSymbol(new QwtSymbol(QwtSymbol::Ellipse,
|
||
QBrush(Qt::red), QPen(Qt::red), QSize(8, 8)));
|
||
_rawDataCurve->attach(_plot);
|
||
|
||
_fitCurve = new QwtPlotCurve(QStringLiteral(u"拟合曲线"));
|
||
_fitCurve->setPen(QPen(Qt::blue, 2));
|
||
_fitCurve->attach(_plot);
|
||
ui->table_scale_data->verticalHeader()->hide();
|
||
initComboBoxUi();
|
||
// 初始化32个通道数据
|
||
for (int i = 1; i <= 32; ++i) {
|
||
QString channelName = QStringLiteral(u"通道%1").arg(i);
|
||
m_channelDataMap.insert(channelName, ChannelEfficiencyData());
|
||
}
|
||
m_currentChannel = QStringLiteral(u"通道1");
|
||
|
||
// 连接通道切换信号
|
||
connect(ui->comboBox_channel, SIGNAL(currentIndexChanged(int)),
|
||
this, SLOT(on_comboBox_channel_currentIndexChanged(int)));
|
||
|
||
// 表格设置
|
||
ui->table_scale_data->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
|
||
ui->table_scale_data->setEditTriggers(QAbstractItemView::DoubleClicked
|
||
| QAbstractItemView::SelectedClicked);
|
||
|
||
// 连接表格编辑信号
|
||
connect(ui->table_scale_data, SIGNAL(itemChanged(QTableWidgetItem*)),
|
||
this, SLOT(on_table_scale_data_itemChanged(QTableWidgetItem*)));
|
||
|
||
// 初始化显示
|
||
refreshTable();
|
||
refreshPlot();
|
||
|
||
// 加载文件列表
|
||
loadAllFilesInTheFolder();
|
||
}
|
||
|
||
EfficiencyScale::~EfficiencyScale()
|
||
{
|
||
delete ui;
|
||
}
|
||
|
||
void EfficiencyScale::setupPlot()
|
||
{
|
||
_plot->setCanvasBackground(Qt::white);
|
||
QwtPlotCanvas* canvas = qobject_cast<QwtPlotCanvas*>(_plot->canvas());
|
||
canvas->setFrameStyle(QFrame::NoFrame);
|
||
QFont font = this->font();
|
||
font.setBold(false);
|
||
QwtText energy_label = QStringLiteral(u"能量");
|
||
energy_label.setFont(font);
|
||
QwtText count_label = QStringLiteral(u"探测效率");
|
||
count_label.setFont(font);
|
||
_plot->setAxisTitle(QwtPlot::xBottom, energy_label);
|
||
_plot->setAxisTitle(QwtPlot::yLeft, count_label);
|
||
// 设置轴自动缩放
|
||
_plot->setAxisAutoScale(QwtPlot::xBottom, true);
|
||
_plot->setAxisAutoScale(QwtPlot::yLeft, true);
|
||
_plot->enableAxis(QwtPlot::xBottom);
|
||
_plot->enableAxis(QwtPlot::yLeft);
|
||
_plot->SetAxisDragScale(QwtPlot::xBottom, true);
|
||
// 启用鼠标追踪
|
||
_plot->canvas()->setMouseTracking(true);
|
||
}
|
||
|
||
void EfficiencyScale::loadAllFilesInTheFolder()
|
||
{
|
||
const QString& energy_scale_dir_path = QDir(qApp->applicationDirPath()).filePath("configure/EfficiencyScale");
|
||
QDir dir(energy_scale_dir_path);
|
||
// 确保目录存在
|
||
if (!dir.exists()) {
|
||
dir.mkpath(energy_scale_dir_path);
|
||
}
|
||
QFileInfoList infoList = dir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
|
||
ui->listWidget->clear();
|
||
for (const QFileInfo &info : infoList) {
|
||
QString displayName;
|
||
if (info.isFile()) {
|
||
displayName = info.baseName();
|
||
} else {
|
||
displayName = info.fileName();
|
||
}
|
||
QListWidgetItem *item = new QListWidgetItem(displayName);
|
||
item->setData(Qt::UserRole, info.absoluteFilePath());
|
||
ui->listWidget->addItem(item);
|
||
}
|
||
connect(ui->listWidget, &QListWidget::itemDoubleClicked, this, &EfficiencyScale::onItemDoubleClicked);
|
||
}
|
||
|
||
void EfficiencyScale::initComboBoxUi()
|
||
{
|
||
ui->comboBox_fit_type->addItem(QStringLiteral(u"一次拟合"));
|
||
ui->comboBox_fit_type->addItem(QStringLiteral(u"二次拟合"));
|
||
ui->comboBox_fit_type_1->addItem(QStringLiteral(u"一次拟合"));
|
||
ui->comboBox_fit_type_1->addItem(QStringLiteral(u"二次拟合"));
|
||
|
||
for(int i = 1;i <= 32; ++i)
|
||
{
|
||
QString channelName = QStringLiteral(u"通道%1").arg(i);
|
||
ui->comboBox_channel->addItem(channelName);
|
||
}
|
||
}
|
||
|
||
void EfficiencyScale::clearPlot()
|
||
{
|
||
_rawDataCurve->detach();
|
||
_fitCurve->detach();
|
||
_plot->detachItems(QwtPlotItem::Rtti_PlotCurve, false);
|
||
_plot->replot();
|
||
}
|
||
|
||
|
||
double EfficiencyScale::calculateCountRate(double netCount, double liveTime) const
|
||
{
|
||
if (liveTime <= 0.0) return 0.0;
|
||
return netCount / liveTime; // cps: 每秒计数个数
|
||
}
|
||
|
||
double EfficiencyScale::calculateEfficiency(double netCount, double liveTime,
|
||
double activity, double branchRatio) const
|
||
{
|
||
// 探测效率 ε = N / (t * A * Pγ)
|
||
// N: 净计数, t: 活时间, A: 活度, Pγ: 分支比
|
||
double denominator = liveTime * activity * branchRatio;
|
||
if (denominator <= 0.0) return 0.0;
|
||
return netCount / denominator;
|
||
}
|
||
|
||
void EfficiencyScale::recalculateAllPoints(QVector<EfficiencyPoint>& points) const
|
||
{
|
||
for (auto& pt : points) {
|
||
pt.countRate = calculateCountRate(pt.netCount, pt.liveTime);
|
||
pt.efficiency = calculateEfficiency(pt.netCount, pt.liveTime,
|
||
pt.activity, pt.branchRatio);
|
||
}
|
||
}
|
||
|
||
FitResult EfficiencyScale::polynomialFit(const QVector<EfficiencyPoint>& points,
|
||
int degree, double minEnergy, double maxEnergy) const
|
||
{
|
||
FitResult result;
|
||
result.degree = degree;
|
||
|
||
// 筛选能量范围内的有效点
|
||
QVector<double> eData, epsData;
|
||
for (const auto& pt : points) {
|
||
if (pt.energy >= minEnergy && pt.energy <= maxEnergy
|
||
&& pt.efficiency > 0.0 && pt.energy > 0.0) {
|
||
eData.append(pt.energy);
|
||
epsData.append(pt.efficiency);
|
||
}
|
||
}
|
||
|
||
int n = eData.size();
|
||
if (n <= degree) {
|
||
result.valid = false;
|
||
return result;
|
||
}
|
||
|
||
// 转换为 Armadillo 向量
|
||
arma::vec E(n), eps(n);
|
||
for (int i = 0; i < n; ++i) {
|
||
E(i) = eData[i];
|
||
eps(i) = epsData[i];
|
||
}
|
||
|
||
// 调用半经验拟合
|
||
try {
|
||
arma::vec coeffs = EnergyEfficiencyFit::SemiEmpiricalFit(E, eps, degree);
|
||
// 只取有效阶数的系数(0~degree,共degree+1个)
|
||
result.coeffs.clear();
|
||
for (int i = 0; i <= degree; ++i) {
|
||
result.coeffs.append(coeffs(i));
|
||
}
|
||
result.valid = true;
|
||
} catch (...) {
|
||
result.valid = false;
|
||
}
|
||
return result;
|
||
}
|
||
|
||
|
||
double EfficiencyScale::calculateFitEfficiency(double energy, const FitResult& fit) const
|
||
{
|
||
if (!fit.valid || energy <= 0.0) return 0.0;
|
||
double lnE = std::log(energy);
|
||
double lnEff = 0.0;
|
||
double xPow = 1.0;
|
||
for (int i = 0; i < fit.coeffs.size(); ++i) {
|
||
lnEff += fit.coeffs[i] * xPow;
|
||
xPow *= lnE;
|
||
}
|
||
return std::exp(lnEff);
|
||
}
|
||
|
||
double EfficiencyScale::calculateTotalEfficiency(double energy, double totalNetCount,
|
||
double liveTime, double activity,
|
||
double branchRatio) const
|
||
{
|
||
// 谱仪总效率:各通道能峰计数率相加后,按相同公式计算
|
||
Q_UNUSED(energy);
|
||
return calculateEfficiency(totalNetCount, liveTime, activity, branchRatio);
|
||
}
|
||
|
||
|
||
ChannelEfficiencyData& EfficiencyScale::currentChannelData()
|
||
{
|
||
return m_channelDataMap[m_currentChannel];
|
||
}
|
||
|
||
const ChannelEfficiencyData& EfficiencyScale::currentChannelData() const
|
||
{
|
||
return m_channelDataMap[m_currentChannel];
|
||
}
|
||
|
||
void EfficiencyScale::refreshTable()
|
||
{
|
||
const ChannelEfficiencyData& chData = currentChannelData();
|
||
const QVector<EfficiencyPoint>& points = chData.points;
|
||
|
||
// 临时断开信号避免触发itemChanged
|
||
disconnect(ui->table_scale_data, SIGNAL(itemChanged(QTableWidgetItem*)),
|
||
this, SLOT(on_table_scale_data_itemChanged(QTableWidgetItem*)));
|
||
|
||
ui->table_scale_data->setRowCount(points.size());
|
||
|
||
for (int row = 0; row < points.size(); ++row) {
|
||
const EfficiencyPoint& pt = points[row];
|
||
// 0: 序号(只读)
|
||
QTableWidgetItem* item0 = new QTableWidgetItem(QString::number(row + 1));
|
||
item0->setFlags(item0->flags() & ~Qt::ItemIsEditable);
|
||
ui->table_scale_data->setItem(row, 0, item0);
|
||
// 1: 能量(keV) —— 可编辑输入
|
||
ui->table_scale_data->setItem(row, 1, new QTableWidgetItem(QString::number(pt.energy, 'f', 2)));
|
||
// 2: 净计数N —— 可编辑输入
|
||
ui->table_scale_data->setItem(row, 2, new QTableWidgetItem(QString::number(pt.netCount, 'f', 0)));
|
||
// 3: 活时间t(s) —— 可编辑输入
|
||
ui->table_scale_data->setItem(row, 3, new QTableWidgetItem(QString::number(pt.liveTime, 'f', 2)));
|
||
// 4: 活度A(Bq) —— 可编辑输入
|
||
ui->table_scale_data->setItem(row, 4, new QTableWidgetItem(QString::number(pt.activity, 'f', 2)));
|
||
// 5: 分支比Pγ —— 可编辑输入
|
||
ui->table_scale_data->setItem(row, 5, new QTableWidgetItem(QString::number(pt.branchRatio, 'f', 4)));
|
||
// 6: 净计数率(cps) —— 计算值
|
||
QTableWidgetItem* item6 = new QTableWidgetItem(QString::number(pt.countRate, 'f', 4));
|
||
item6->setFlags(item6->flags() & ~Qt::ItemIsEditable);
|
||
ui->table_scale_data->setItem(row, 6, item6);
|
||
// 7: 探测效率ε —— 计算值
|
||
QTableWidgetItem* item7 = new QTableWidgetItem(QString::number(pt.efficiency, 'f', 8));
|
||
item7->setFlags(item7->flags() & ~Qt::ItemIsEditable);
|
||
ui->table_scale_data->setItem(row, 7, item7);
|
||
// 8: 拟合效率 —— 拟合后计算
|
||
QTableWidgetItem* item8;
|
||
if (pt.fitEfficiency > 0.0) {
|
||
item8 = new QTableWidgetItem(QString::number(pt.fitEfficiency, 'f', 8));
|
||
} else {
|
||
item8 = new QTableWidgetItem("-");
|
||
}
|
||
item8->setFlags(item8->flags() & ~Qt::ItemIsEditable);
|
||
ui->table_scale_data->setItem(row, 8, item8);
|
||
// 9: 误差(%) —— 拟合后计算
|
||
QTableWidgetItem* item9;
|
||
if (pt.error > 0.0) {
|
||
item9 = new QTableWidgetItem(QString::number(pt.error, 'f', 4) + "%");
|
||
} else {
|
||
item9 = new QTableWidgetItem("-");
|
||
}
|
||
item9->setFlags(item9->flags() & ~Qt::ItemIsEditable);
|
||
ui->table_scale_data->setItem(row, 9, item9);
|
||
// 10: 刻度日期 —— 可编辑
|
||
ui->table_scale_data->setItem(row, 10, new QTableWidgetItem(pt.scaleDate));
|
||
}
|
||
|
||
// 重新连接信号
|
||
connect(ui->table_scale_data, SIGNAL(itemChanged(QTableWidgetItem*)),
|
||
this, SLOT(on_table_scale_data_itemChanged(QTableWidgetItem*)));
|
||
}
|
||
|
||
// 表格单元格编辑后更新数据并重算
|
||
void EfficiencyScale::on_table_scale_data_itemChanged(QTableWidgetItem* item)
|
||
{
|
||
int row = item->row();
|
||
int col = item->column();
|
||
ChannelEfficiencyData& chData = currentChannelData();
|
||
if (row >= chData.points.size()) return;
|
||
|
||
EfficiencyPoint& pt = chData.points[row];
|
||
bool ok = false;
|
||
double value = item->text().toDouble(&ok);
|
||
|
||
switch (col) {
|
||
case 1: // 能量
|
||
if (ok) pt.energy = value;
|
||
break;
|
||
case 2: // 净计数N
|
||
if (ok) pt.netCount = value;
|
||
break;
|
||
case 3: // 活时间t
|
||
if (ok) pt.liveTime = value;
|
||
break;
|
||
case 4: // 活度A
|
||
if (ok) pt.activity = value;
|
||
break;
|
||
case 5: // 分支比Pγ
|
||
if (ok) pt.branchRatio = value;
|
||
break;
|
||
case 10: // 刻度日期
|
||
pt.scaleDate = item->text();
|
||
return; // 日期不需要重算
|
||
default:
|
||
return; // 其他列是只读计算列,不处理
|
||
}
|
||
|
||
// 修改了输入参数,重新计算计数率和效率
|
||
recalculateAllPoints(chData.points);
|
||
// 刷新表格的计算列(避免递归,直接更新指定单元格)
|
||
disconnect(ui->table_scale_data, SIGNAL(itemChanged(QTableWidgetItem*)),
|
||
this, SLOT(on_table_scale_data_itemChanged(QTableWidgetItem*)));
|
||
ui->table_scale_data->item(row, 6)->setText(QString::number(pt.countRate, 'f', 4));
|
||
ui->table_scale_data->item(row, 7)->setText(QString::number(pt.efficiency, 'e', 6));
|
||
connect(ui->table_scale_data, SIGNAL(itemChanged(QTableWidgetItem*)),
|
||
this, SLOT(on_table_scale_data_itemChanged(QTableWidgetItem*)));
|
||
|
||
refreshPlot();
|
||
}
|
||
|
||
QVector<QPointF> EfficiencyScale::generateFitCurvePoints(const FitResult& highFit,
|
||
const FitResult& lowFit,
|
||
double inflectionEnergy,
|
||
int sampleCount) const
|
||
{
|
||
QVector<QPointF> curvePoints;
|
||
const ChannelEfficiencyData& chData = currentChannelData();
|
||
if (chData.points.isEmpty()) return curvePoints;
|
||
|
||
// 找能量范围
|
||
double minE = chData.points.first().energy;
|
||
double maxE = chData.points.first().energy;
|
||
for (const auto& pt : chData.points) {
|
||
minE = qMin(minE, pt.energy);
|
||
maxE = qMax(maxE, pt.energy);
|
||
}
|
||
if (minE <= 0 || maxE <= 0) return curvePoints;
|
||
|
||
// 采样
|
||
double logMin = std::log(minE);
|
||
double logMax = std::log(maxE);
|
||
double step = (logMax - logMin) / sampleCount;
|
||
|
||
for (int i = 0; i <= sampleCount; ++i) {
|
||
double logE = logMin + i * step;
|
||
double energy = std::exp(logE);
|
||
double eff = 0.0;
|
||
|
||
if (inflectionEnergy > 0 && energy < inflectionEnergy) {
|
||
// 拐点以下用低能拟合
|
||
if (lowFit.valid) {
|
||
eff = calculateFitEfficiency(energy, lowFit);
|
||
}
|
||
} else {
|
||
// 拐点以上用高能拟合
|
||
if (highFit.valid) {
|
||
eff = calculateFitEfficiency(energy, highFit);
|
||
} else if (lowFit.valid) {
|
||
eff = calculateFitEfficiency(energy, lowFit);
|
||
}
|
||
}
|
||
|
||
if (eff > 0.0) {
|
||
curvePoints.append(QPointF(energy, eff));
|
||
}
|
||
}
|
||
return curvePoints;
|
||
}
|
||
|
||
void EfficiencyScale::refreshPlot()
|
||
{
|
||
if (!_plot || !_rawDataCurve || !_fitCurve) return;
|
||
|
||
const ChannelEfficiencyData& chData = currentChannelData();
|
||
|
||
// 原始数据散点
|
||
QVector<QPointF> rawPoints;
|
||
for (const auto& pt : chData.points) {
|
||
if (pt.efficiency > 0.0) {
|
||
rawPoints.append(QPointF(pt.energy, pt.efficiency));
|
||
}
|
||
}
|
||
_rawDataCurve->setSamples(rawPoints);
|
||
|
||
// 拟合曲线
|
||
QVector<QPointF> fitPoints = generateFitCurvePoints(
|
||
chData.highFit, chData.lowFit, chData.inflectionEnergy);
|
||
_fitCurve->setSamples(fitPoints);
|
||
|
||
_plot->replot();
|
||
}
|
||
|
||
|
||
void EfficiencyScale::on_pBtn_Add_clicked()
|
||
{
|
||
ChannelEfficiencyData& chData = currentChannelData();
|
||
EfficiencyPoint newPoint;
|
||
newPoint.scaleDate = QDateTime::currentDateTime().toString("yyyy-MM-dd");
|
||
// 默认值
|
||
newPoint.energy = 0.0;
|
||
newPoint.netCount = 0.0;
|
||
newPoint.liveTime = 1.0;
|
||
newPoint.activity = 1.0;
|
||
newPoint.branchRatio = 1.0;
|
||
chData.points.append(newPoint);
|
||
|
||
recalculateAllPoints(chData.points);
|
||
refreshTable();
|
||
refreshPlot();
|
||
}
|
||
|
||
void EfficiencyScale::on_pBtn_Delete_clicked()
|
||
{
|
||
int currentRow = ui->table_scale_data->currentRow();
|
||
if (currentRow < 0) {
|
||
QMessageBox::warning(this, QStringLiteral(u"提示"),
|
||
QStringLiteral(u"请先选中要删除的刻度点"));
|
||
return;
|
||
}
|
||
|
||
ChannelEfficiencyData& chData = currentChannelData();
|
||
if (currentRow >= chData.points.size()) return;
|
||
|
||
chData.points.removeAt(currentRow);
|
||
recalculateAllPoints(chData.points);
|
||
refreshTable();
|
||
refreshPlot();
|
||
}
|
||
|
||
void EfficiencyScale::on_pBtn_fitting_4_clicked()
|
||
{
|
||
|
||
|
||
// 拐点以上拟合
|
||
ChannelEfficiencyData& chData = currentChannelData();
|
||
|
||
QLineEdit* inflectionEdit = findChild<QLineEdit*>("lineEdit_inflection_energy");
|
||
if (inflectionEdit) {
|
||
bool ok = false;
|
||
double val = inflectionEdit->text().toDouble(&ok);
|
||
chData.inflectionEnergy = (ok && val > 0.0) ? val : 0.0;
|
||
}
|
||
|
||
if (chData.points.size() < 2) {
|
||
QMessageBox::warning(this, QStringLiteral(u"提示"),
|
||
QStringLiteral(u"至少需要2个刻度点才能进行拟合"));
|
||
return;
|
||
}
|
||
|
||
int degree = ui->comboBox_fit_type->currentIndex() + 1; // 1或2
|
||
double inflectionE = chData.inflectionEnergy;
|
||
|
||
// 如果没有设置拐点,默认全部用高能拟合
|
||
double minE = inflectionE > 0 ? inflectionE : 0.0;
|
||
double maxE = 1e9; // 足够大
|
||
|
||
chData.highFit = polynomialFit(chData.points, degree, minE, maxE);
|
||
|
||
if (!chData.highFit.valid) {
|
||
QMessageBox::critical(this, QStringLiteral(u"错误"),
|
||
QStringLiteral(u"拟合失败,请检查数据"));
|
||
return;
|
||
}
|
||
|
||
// 更新各点的拟合效率和误差
|
||
for (auto& pt : chData.points) {
|
||
if (inflectionE <= 0 || pt.energy >= inflectionE) {
|
||
pt.fitEfficiency = calculateFitEfficiency(pt.energy, chData.highFit);
|
||
if (pt.efficiency > 0.0) {
|
||
pt.error = std::abs(pt.fitEfficiency - pt.efficiency) / pt.efficiency * 100.0;
|
||
}
|
||
}
|
||
}
|
||
|
||
refreshTable();
|
||
refreshPlot();
|
||
}
|
||
|
||
void EfficiencyScale::on_pBtn_fitting_3_clicked()
|
||
{
|
||
// 拐点以下拟合
|
||
ChannelEfficiencyData& chData = currentChannelData();
|
||
|
||
QLineEdit* inflectionEdit = findChild<QLineEdit*>("lineEdit_inflection_energy");
|
||
if (inflectionEdit) {
|
||
bool ok = false;
|
||
double val = inflectionEdit->text().toDouble(&ok);
|
||
chData.inflectionEnergy = (ok && val > 0.0) ? val : 0.0;
|
||
}
|
||
|
||
if (chData.points.size() < 2) {
|
||
QMessageBox::warning(this, QStringLiteral(u"提示"),
|
||
QStringLiteral(u"至少需要2个刻度点才能进行拟合"));
|
||
return;
|
||
}
|
||
|
||
int degree = ui->comboBox_fit_type_1->currentIndex() + 1; // 1或2
|
||
double inflectionE = chData.inflectionEnergy;
|
||
|
||
double minE = 0.0;
|
||
double maxE = inflectionE > 0 ? inflectionE : 1e9;
|
||
|
||
chData.lowFit = polynomialFit(chData.points, degree, minE, maxE);
|
||
|
||
if (!chData.lowFit.valid) {
|
||
QMessageBox::critical(this, QStringLiteral(u"错误"),
|
||
QStringLiteral(u"拟合失败,请检查数据"));
|
||
return;
|
||
}
|
||
|
||
// 更新各点的拟合效率和误差
|
||
for (auto& pt : chData.points) {
|
||
if (inflectionE <= 0 || pt.energy < inflectionE) {
|
||
pt.fitEfficiency = calculateFitEfficiency(pt.energy, chData.lowFit);
|
||
if (pt.efficiency > 0.0) {
|
||
pt.error = std::abs(pt.fitEfficiency - pt.efficiency) / pt.efficiency * 100.0;
|
||
}
|
||
}
|
||
}
|
||
|
||
refreshTable();
|
||
refreshPlot();
|
||
}
|
||
|
||
void EfficiencyScale::on_comboBox_channel_currentIndexChanged(int index)
|
||
{
|
||
Q_UNUSED(index);
|
||
QLineEdit* inflectionEdit = findChild<QLineEdit*>("lineEdit_inflection_energy");
|
||
if (inflectionEdit) {
|
||
const ChannelEfficiencyData& chData = currentChannelData();
|
||
if (chData.inflectionEnergy > 0.0) {
|
||
inflectionEdit->setText(QString::number(chData.inflectionEnergy, 'f', 2));
|
||
} else {
|
||
inflectionEdit->clear();
|
||
}
|
||
}
|
||
m_currentChannel = ui->comboBox_channel->currentText();
|
||
refreshTable();
|
||
refreshPlot();
|
||
}
|
||
|
||
|
||
bool EfficiencyScale::saveToJsonFile(const QString& filePath) const
|
||
{
|
||
QJsonObject rootObj;
|
||
rootObj["scaleName"] = ui->lineEdit_name->text();
|
||
rootObj["description"] = ui->plainTextEdit_description_3->toPlainText();
|
||
rootObj["currentChannel"] = m_currentChannel;
|
||
|
||
QJsonArray channelsArray;
|
||
for (auto it = m_channelDataMap.constBegin(); it != m_channelDataMap.constEnd(); ++it) {
|
||
QJsonObject chObj;
|
||
chObj["channelName"] = it.key();
|
||
chObj["inflectionEnergy"] = it.value().inflectionEnergy;
|
||
|
||
// 刻度点
|
||
QJsonArray pointsArray;
|
||
for (const auto& pt : it.value().points) {
|
||
QJsonObject ptObj;
|
||
ptObj["energy"] = pt.energy;
|
||
ptObj["netCount"] = pt.netCount;
|
||
ptObj["liveTime"] = pt.liveTime;
|
||
ptObj["activity"] = pt.activity;
|
||
ptObj["branchRatio"] = pt.branchRatio;
|
||
ptObj["countRate"] = pt.countRate;
|
||
ptObj["efficiency"] = pt.efficiency;
|
||
ptObj["fitEfficiency"] = pt.fitEfficiency;
|
||
ptObj["error"] = pt.error;
|
||
ptObj["scaleDate"] = pt.scaleDate;
|
||
pointsArray.append(ptObj);
|
||
}
|
||
chObj["points"] = pointsArray;
|
||
|
||
// 高能拟合
|
||
QJsonObject highFitObj;
|
||
highFitObj["valid"] = it.value().highFit.valid;
|
||
highFitObj["degree"] = it.value().highFit.degree;
|
||
QJsonArray highCoeffs;
|
||
for (double c : it.value().highFit.coeffs) highCoeffs.append(c);
|
||
highFitObj["coeffs"] = highCoeffs;
|
||
chObj["highFit"] = highFitObj;
|
||
|
||
// 低能拟合
|
||
QJsonObject lowFitObj;
|
||
lowFitObj["valid"] = it.value().lowFit.valid;
|
||
lowFitObj["degree"] = it.value().lowFit.degree;
|
||
QJsonArray lowCoeffs;
|
||
for (double c : it.value().lowFit.coeffs) lowCoeffs.append(c);
|
||
lowFitObj["coeffs"] = lowCoeffs;
|
||
chObj["lowFit"] = lowFitObj;
|
||
|
||
channelsArray.append(chObj);
|
||
}
|
||
rootObj["channels"] = channelsArray;
|
||
|
||
QJsonDocument doc(rootObj);
|
||
QFile file(filePath);
|
||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||
return false;
|
||
}
|
||
file.write(doc.toJson());
|
||
file.close();
|
||
return true;
|
||
}
|
||
|
||
bool EfficiencyScale::loadFromJsonFile(const QString& filePath)
|
||
{
|
||
QFile file(filePath);
|
||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||
return false;
|
||
}
|
||
QByteArray data = file.readAll();
|
||
file.close();
|
||
|
||
QJsonDocument doc = QJsonDocument::fromJson(data);
|
||
if (!doc.isObject()) return false;
|
||
|
||
QJsonObject rootObj = doc.object();
|
||
ui->lineEdit_name->setText(rootObj["scaleName"].toString());
|
||
ui->plainTextEdit_description_3->setPlainText(rootObj["description"].toString());
|
||
|
||
QJsonArray channelsArray = rootObj["channels"].toArray();
|
||
for (const auto& chVal : channelsArray) {
|
||
QJsonObject chObj = chVal.toObject();
|
||
QString chName = chObj["channelName"].toString();
|
||
if (!m_channelDataMap.contains(chName)) continue;
|
||
|
||
ChannelEfficiencyData chData;
|
||
chData.inflectionEnergy = chObj["inflectionEnergy"].toDouble();
|
||
|
||
// 刻度点
|
||
QJsonArray pointsArray = chObj["points"].toArray();
|
||
for (const auto& ptVal : pointsArray) {
|
||
QJsonObject ptObj = ptVal.toObject();
|
||
EfficiencyPoint pt;
|
||
pt.energy = ptObj["energy"].toDouble();
|
||
pt.netCount = ptObj["netCount"].toDouble();
|
||
pt.liveTime = ptObj["liveTime"].toDouble();
|
||
pt.activity = ptObj["activity"].toDouble();
|
||
pt.branchRatio = ptObj["branchRatio"].toDouble();
|
||
pt.countRate = ptObj["countRate"].toDouble();
|
||
pt.efficiency = ptObj["efficiency"].toDouble();
|
||
pt.fitEfficiency = ptObj["fitEfficiency"].toDouble();
|
||
pt.error = ptObj["error"].toDouble();
|
||
pt.scaleDate = ptObj["scaleDate"].toString();
|
||
chData.points.append(pt);
|
||
}
|
||
|
||
// 高能拟合
|
||
QJsonObject highFitObj = chObj["highFit"].toObject();
|
||
chData.highFit.valid = highFitObj["valid"].toBool();
|
||
chData.highFit.degree = highFitObj["degree"].toInt();
|
||
QJsonArray highCoeffs = highFitObj["coeffs"].toArray();
|
||
for (const auto& c : highCoeffs) chData.highFit.coeffs.append(c.toDouble());
|
||
|
||
// 低能拟合
|
||
QJsonObject lowFitObj = chObj["lowFit"].toObject();
|
||
chData.lowFit.valid = lowFitObj["valid"].toBool();
|
||
chData.lowFit.degree = lowFitObj["degree"].toInt();
|
||
QJsonArray lowCoeffs = lowFitObj["coeffs"].toArray();
|
||
for (const auto& c : lowCoeffs) chData.lowFit.coeffs.append(c.toDouble());
|
||
|
||
m_channelDataMap[chName] = chData;
|
||
}
|
||
|
||
m_currentFilePath = filePath;
|
||
refreshTable();
|
||
refreshPlot();
|
||
return true;
|
||
}
|
||
|
||
|
||
void EfficiencyScale::onItemDoubleClicked(QListWidgetItem *item)
|
||
{
|
||
if (!item) return;
|
||
QString filePath = item->data(Qt::UserRole).toString();
|
||
if (filePath.isEmpty()) return;
|
||
|
||
QFileInfo fileInfo(filePath);
|
||
if (!fileInfo.isFile()) return;
|
||
|
||
if (loadFromJsonFile(filePath)) {
|
||
ui->lineEdit_name->setText(item->text());
|
||
QMessageBox::information(this, QStringLiteral(u"成功"),
|
||
QStringLiteral(u"加载成功:%1").arg(item->text()));
|
||
} else {
|
||
QMessageBox::critical(this, QStringLiteral(u"错误"),
|
||
QStringLiteral(u"加载失败:文件格式不正确"));
|
||
}
|
||
}
|
||
|
||
void EfficiencyScale::on_pBtn_Save_clicked()
|
||
{
|
||
if (m_currentFilePath.isEmpty()) {
|
||
on_pBtn_SaveAs_clicked();
|
||
return;
|
||
}
|
||
if (saveToJsonFile(m_currentFilePath)) {
|
||
QMessageBox::information(this, QStringLiteral(u"成功"),
|
||
QStringLiteral(u"保存成功"));
|
||
} else {
|
||
QMessageBox::critical(this, QStringLiteral(u"错误"),
|
||
QStringLiteral(u"保存失败"));
|
||
}
|
||
}
|
||
|
||
void EfficiencyScale::on_pBtn_SaveAs_clicked()
|
||
{
|
||
QString defaultDir = QDir(qApp->applicationDirPath()).filePath("configure/EfficiencyScale");
|
||
QString fileName = QFileDialog::getSaveFileName(this,
|
||
QStringLiteral(u"另存为"), defaultDir,
|
||
QStringLiteral(u"效率刻度文件 (*.json)"));
|
||
if (fileName.isEmpty()) return;
|
||
|
||
if (saveToJsonFile(fileName)) {
|
||
m_currentFilePath = fileName;
|
||
loadAllFilesInTheFolder(); // 刷新列表
|
||
QMessageBox::information(this, QStringLiteral(u"成功"),
|
||
QStringLiteral(u"保存成功"));
|
||
} else {
|
||
QMessageBox::critical(this, QStringLiteral(u"错误"),
|
||
QStringLiteral(u"保存失败"));
|
||
}
|
||
}
|
||
|
||
void EfficiencyScale::on_pBtn_Add_File_clicked()
|
||
{
|
||
QString defaultDir = QDir(qApp->applicationDirPath()).filePath("configure/EfficiencyScale");
|
||
QString fileName = QFileDialog::getSaveFileName(this,
|
||
QStringLiteral(u"新建效率刻度文件"), defaultDir,
|
||
QStringLiteral(u"效率刻度文件 (*.json)"));
|
||
if (fileName.isEmpty()) return;
|
||
|
||
// 确保后缀
|
||
if (!fileName.endsWith(".json", Qt::CaseInsensitive)) {
|
||
fileName += ".json";
|
||
}
|
||
|
||
// 清空当前数据,生成空白刻度
|
||
m_scaleName = QFileInfo(fileName).completeBaseName();
|
||
m_description.clear();
|
||
m_currentChannel = QStringLiteral(u"通道1");
|
||
for (auto it = m_channelDataMap.begin(); it != m_channelDataMap.end(); ++it) {
|
||
it.value().points.clear();
|
||
it.value().highFit = FitResult();
|
||
it.value().lowFit = FitResult();
|
||
it.value().inflectionEnergy = 0.0;
|
||
}
|
||
|
||
// 保存空白文件
|
||
if (saveToJsonFile(fileName)) {
|
||
m_currentFilePath = fileName;
|
||
ui->lineEdit_name->setText(m_scaleName);
|
||
ui->plainTextEdit_description_3->setPlainText(m_description);
|
||
loadAllFilesInTheFolder();
|
||
refreshTable();
|
||
refreshPlot();
|
||
QMessageBox::information(this, QStringLiteral(u"成功"),
|
||
QStringLiteral(u"新建成功"));
|
||
} else {
|
||
QMessageBox::critical(this, QStringLiteral(u"错误"),
|
||
QStringLiteral(u"新建失败"));
|
||
}
|
||
}
|
||
|
||
void EfficiencyScale::on_pBtn_Delete_File_clicked()
|
||
{
|
||
QListWidgetItem* selectedItem = ui->listWidget->currentItem();
|
||
if (!selectedItem) {
|
||
QMessageBox::warning(this, QStringLiteral(u"提示"),
|
||
QStringLiteral(u"请先选中要删除的文件/文件夹"));
|
||
return;
|
||
}
|
||
QString filePath = selectedItem->data(Qt::UserRole).toString();
|
||
if (filePath.isEmpty()) {
|
||
QMessageBox::warning(this, QStringLiteral(u"错误"),
|
||
QStringLiteral(u"无法获取文件路径"));
|
||
return;
|
||
}
|
||
QMessageBox::StandardButton ret = QMessageBox::question(this,
|
||
QStringLiteral(u"确认删除"),
|
||
QStringLiteral(u"是否确定删除%1?\n删除后无法恢复!").arg(selectedItem->text()),
|
||
QMessageBox::Yes | QMessageBox::No,
|
||
QMessageBox::No);
|
||
if (ret != QMessageBox::Yes) {
|
||
return;
|
||
}
|
||
bool isDeleted = false;
|
||
QFileInfo fileInfo(filePath);
|
||
if (fileInfo.isFile()) {
|
||
QFile file(filePath);
|
||
isDeleted = file.remove();
|
||
}
|
||
if (isDeleted) {
|
||
loadAllFilesInTheFolder();
|
||
if (m_currentFilePath == filePath) {
|
||
m_currentFilePath.clear();
|
||
ui->lineEdit_name->clear();
|
||
ui->table_scale_data->setRowCount(0);
|
||
clearPlot();
|
||
_plot->replot();
|
||
}
|
||
QMessageBox::information(this, QStringLiteral(u"成功"),
|
||
QStringLiteral(u"删除成功"));
|
||
} else {
|
||
QMessageBox::critical(this, QStringLiteral(u"错误"),
|
||
QStringLiteral(u"删除失败:%1").arg(filePath));
|
||
}
|
||
}
|