EnergySpectrumAnalyer/src/EnergyScaleForm.cpp

1121 lines
41 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "EnergyScaleForm.h"
#include "ui_EnergyScaleForm.h"
#include <QDir>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonValue>
#include <QwtPlotCanvas>
#include <QwtText>
#include <QwtPlotCurve>
#include "CustomQwtPlot.h"
#include "DataCalcProcess/GaussPolyCoe.h"
#include "DataCalcProcess/MathModelDefine.h"
#include "DataCalcProcess/NolinearLeastSquaresCurveFit.h"
#include <QDebug>
#include <QDialog>
#include <QMessageBox>
#include <QInputDialog>
#include <QFileDialog>
EnergyScaleForm::EnergyScaleForm(QWidget *parent)
: MeasureAnalysisView(parent)
, ui(new Ui::EnergyScaleForm)
, _plot(nullptr)
, _rawDataCurve(nullptr)
, _fitCurve(nullptr)
, _rawDataSymbol(nullptr)
, m_isLoadingTable(false)
{
ui->setupUi(this);
this->_plot = new CustomQwtPlot(this);
ui->layout_fittingCurve->addWidget(this->_plot);
_rawDataCurve = new QwtPlotCurve("原始数据");
_fitCurve = new QwtPlotCurve("拟合曲线");
_rawDataSymbol = new QwtSymbol(QwtSymbol::Ellipse);
_rawDataSymbol->setSize(8);
_rawDataSymbol->setBrush(Qt::red);
_rawDataSymbol->setPen(QPen(Qt::black, 1));
setupPlot();
initComboBoxUi();
initScaleDataTable();
loadAllFilesInTheFolder();
connect(ui->comboBox_channel, &QComboBox::currentTextChanged, this, &EnergyScaleForm::on_comboBox_channel_currentTextChanged);
connect(ui->comboBox_fit_type, &QComboBox::currentTextChanged,this, &EnergyScaleForm::on_comboBox_fit_type_currentTextChanged);
connect(ui->tablew_scale_data, &QTableWidget::cellChanged,this, &EnergyScaleForm::on_tablew_scale_data_cellChanged);
}
EnergyScaleForm::~EnergyScaleForm()
{
delete ui;
}
void EnergyScaleForm::InitViewWorkspace(const QString &project_name)
{
}
void EnergyScaleForm::SetAnalyzeDataFilename(const QMap<QString, QVariant> &data_files_set)
{
if (!data_files_set.isEmpty()) {
const QString& data_name = data_files_set.firstKey();
const QString& data_filename = data_files_set.first().toString();
QFileInfo info(data_filename);
QDir dir = info.dir();
if (QFileInfo(data_filename).exists()) {
ui->groupBox_3->hide();
QFileInfo fileInfo(data_filename);
QString baseName =QString("[%1]%2").arg(dir.dirName()).arg(fileInfo.baseName());
ui->lineEdit_name->setText(baseName);
ui->lineEdit_name->setReadOnly(true);
ui->groupBox_2->hide();
ui->pBtn_SaveAs->setText(QStringLiteral(u"保存到系统"));
m_currentFilePath = data_filename;
QString errorMsg;
m_channelList = parseJsonToChannels(data_filename, errorMsg);
on_comboBox_channel_currentTextChanged(QStringLiteral(u"通道1"));
}
}
}
void EnergyScaleForm::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 EnergyScaleForm::loadAllFilesInTheFolder()
{
const QString& energy_scale_dir_path = QDir(qApp->applicationDirPath()).filePath("configure/EnergyScale");
QDir dir(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, &EnergyScaleForm::onItemDoubleClicked);
}
QMap<QString, ChannelData> EnergyScaleForm::parseJsonToChannels(const QString &filePath, QString &errorMsg)
{
QMap<QString, ChannelData> result;
errorMsg.clear();
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
errorMsg = QString("无法打开文件: %1").arg(file.errorString());
return result;
}
QByteArray data = file.readAll();
file.close();
QJsonParseError parseError;
QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);
if (parseError.error != QJsonParseError::NoError) {
errorMsg = QString("JSON 解析错误: %1").arg(parseError.errorString());
return result;
}
if (!doc.isObject()) {
errorMsg = "JSON 根节点不是对象";
return result;
}
QJsonObject rootObj = doc.object();
if (rootObj.contains("Remark") && rootObj["Remark"].isString())
ui->plainTextEdit_description->setPlainText(rootObj["Remark"].toString());
for (auto it = rootObj.begin(); it != rootObj.end(); ++it) {
QString channelName = it.key();
QJsonValue channelVal = it.value();
if (!channelVal.isObject()) {
errorMsg += QString("通道 %1 的值不是对象,跳过\n").arg(channelName);
continue;
}
QJsonObject channelObj = channelVal.toObject();
ChannelData chData;
if (channelObj.contains("EnergyFitDegree") && channelObj["EnergyFitDegree"].isDouble()) {
chData.energyFitDegree = channelObj["EnergyFitDegree"].toInt();
} else {
errorMsg += QString("通道 %1 缺少 EnergyFitDegree 字段\n").arg(channelName);
continue;
}
if (channelObj.contains("EnergyFitResultCoeffs") && channelObj["EnergyFitResultCoeffs"].isArray()) {
QJsonArray coeffsArr = channelObj["EnergyFitResultCoeffs"].toArray();
for (QJsonValue v : coeffsArr) {
if (v.isDouble())
chData.energyFitResultCoeffs.append(v.toDouble());
else
errorMsg += QString("通道 %1 的 EnergyFitResultCoeffs 包含非数字\n").arg(channelName);
}
} else {
errorMsg += QString("通道 %1 缺少 EnergyFitResultCoeffs 字段\n").arg(channelName);
}
if (channelObj.contains("FitData") && channelObj["FitData"].isArray()) {
QJsonArray fitDataArr = channelObj["FitData"].toArray();
for (QJsonValue rowVal : fitDataArr) {
if (!rowVal.isArray()) {
errorMsg += QString("通道 %1 的 FitData 行不是数组\n").arg(channelName);
continue;
}
QJsonArray rowArr = rowVal.toArray();
if (rowArr.size() != 7) {
errorMsg += QString("通道 %1 的 FitData 行长度不为 7实际 %2\n")
.arg(channelName).arg(rowArr.size());
continue;
}
FitDataRow row;
row.daoSite = rowArr[0].toDouble();
row.energy = rowArr[1].toDouble();
row.fittingEnergy = rowArr[2].toDouble();
row.energyFittingDeviation = rowArr[3].toDouble();
row.resolution = rowArr[4].toDouble();
row.fitResolution = rowArr[5].toDouble();
row.resolutionFittingDeviation = rowArr[6].toDouble();
chData.fitData.append(row);
}
} else {
errorMsg += QString("通道 %1 缺少 FitData 字段\n").arg(channelName);
}
if (channelObj.contains("FwhmFitResultCoeffs") && channelObj["FwhmFitResultCoeffs"].isArray()) {
QJsonArray fwhmArr = channelObj["FwhmFitResultCoeffs"].toArray();
for (QJsonValue v : fwhmArr) {
if (v.isDouble())
chData.fwhmFitResultCoeffs.append(v.toDouble());
else
errorMsg += QString("通道 %1 的 FwhmFitResultCoeffs 包含非数字\n").arg(channelName);
}
} else {
errorMsg += QString("通道 %1 缺少 FwhmFitResultCoeffs 字段\n").arg(channelName);
}
result.insert(channelName, chData);
}
if (result.isEmpty() && errorMsg.isEmpty())
errorMsg = "未找到任何通道数据。";
return result;
}
void EnergyScaleForm::initComboBoxUi()
{
ui->comboBox_fit_type->addItem(QStringLiteral(u"一次拟合"));
ui->comboBox_fit_type->addItem(QStringLiteral(u"二次拟合"));
for(int i = 1;i <= 32; ++i)
{
QString channelName = QStringLiteral(u"通道%1").arg(i);
ui->comboBox_channel->addItem(channelName);
if (!m_channelList.contains(channelName)) {
ChannelData emptyData;
emptyData.energyFitDegree = 1;
m_channelList.insert(channelName, emptyData);
}
}
}
void EnergyScaleForm::initScaleDataTable()
{
QTableWidget* table = ui->tablew_scale_data;
table->setColumnCount(7);
// 设置表格列名
QStringList headers = {
"道址", "能量", "拟合能量", "能量拟合偏差",
"分辨率", "拟合分辨率", "分辨率拟合偏差"
};
table->setHorizontalHeaderLabels(headers);
table->setEditTriggers(QTableWidget::DoubleClicked | QTableWidget::EditKeyPressed);
table->setSelectionBehavior(QTableWidget::SelectRows); // 整行选中
table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
}
void EnergyScaleForm::showChannelData(const QString channelName)
{
if (!m_channelList.contains(channelName)) {
return;
}
m_isLoadingTable = true; // 防止触发cellChanged
ui->tablew_scale_data->setRowCount(0);
const ChannelData& chData = m_channelList[channelName];
switch (chData.energyFitDegree) {
case 1:
{
ui->comboBox_fit_type->setCurrentIndex(0);
ui->label_fit_type_text->setText(QStringLiteral(u"y = a + bx"));
QString coefficient = QStringLiteral(u"a = %1 , b = %2").arg(chData.energyFitResultCoeffs.at(0)).arg(chData.energyFitResultCoeffs.at(1));
ui->label_fit_result->setText(coefficient);
} break;
case 2:
ui->comboBox_fit_type->setCurrentIndex(1);
ui->label_fit_type_text->setText(QStringLiteral(u"y = a + bx + c*x^2"));
QString coefficient = QStringLiteral(u"a = %1 , b = %2, c = %3").arg(chData.energyFitResultCoeffs.at(0)).arg(chData.energyFitResultCoeffs.at(1)).arg(chData.energyFitResultCoeffs.at(2));
ui->label_fit_result->setText(coefficient);
break;
}
QString energyCoeffsStr;
for (int i = 0; i < chData.energyFitResultCoeffs.size(); ++i) {
energyCoeffsStr += QString("系数%1: %2 ").arg(i+1).arg(chData.energyFitResultCoeffs[i], 0, 'f', 6);
}
QTableWidget* table = ui->tablew_scale_data;
table->setRowCount(chData.fitData.size());
for (int row = 0; row < chData.fitData.size(); ++row) {
const FitDataRow& fitRow = chData.fitData[row];
table->setItem(row, 0, new QTableWidgetItem(QString::number(fitRow.daoSite, 'f', 3)));
table->setItem(row, 1, new QTableWidgetItem(QString::number(fitRow.energy, 'f', 3)));
table->setItem(row, 2, new QTableWidgetItem(QString::number(fitRow.fittingEnergy, 'f', 3)));
table->setItem(row, 3, new QTableWidgetItem(QString::number(fitRow.energyFittingDeviation, 'f', 3)));
table->setItem(row, 4, new QTableWidgetItem(QString::number(fitRow.resolution, 'f', 3)));
table->setItem(row, 5, new QTableWidgetItem(QString::number(fitRow.fitResolution, 'f', 3)));
table->setItem(row, 6, new QTableWidgetItem(QString::number(fitRow.resolutionFittingDeviation, 'f', 3)));
for (int col = 0; col < 7; ++col) {
Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
if (col == 0 || col == 1 || col == 4) {
flags |= Qt::ItemIsEditable;
}
table->item(row, col)->setFlags(flags);
}
}
m_isLoadingTable = false;
}
void EnergyScaleForm::showCurve()
{
clearPlot();
QString channelName = ui->comboBox_channel->currentText();
if (!m_channelList.contains(channelName))
{
_plot->replot();
return;
}
const ChannelData &chData = m_channelList[channelName];
if (chData.fitData.isEmpty()) return;
QVector<double> xRaw, yRaw;
double xMin = 0;
double xMax = 4096;
for (const FitDataRow &row : chData.fitData) {
xRaw.append(row.daoSite);
yRaw.append(row.energy);
}
_rawDataCurve->setSamples(xRaw, yRaw);
_rawDataCurve->setSymbol(_rawDataSymbol);
_rawDataCurve->setStyle(QwtPlotCurve::NoCurve);
_rawDataCurve->attach(_plot);
if (!chData.energyFitResultCoeffs.isEmpty()) {
QVector<QPointF> fitPoints = generateFitCurvePoints(chData);
_fitCurve->setSamples(fitPoints);
_fitCurve->setPen(QPen(Qt::blue, 2)); // 拟合曲线用蓝色粗线
_fitCurve->setStyle(QwtPlotCurve::Lines);
_fitCurve->attach(_plot);
}
_plot->replot();
}
void EnergyScaleForm::clearPlot()
{
_rawDataCurve->detach();
_fitCurve->detach();
_plot->detachItems(QwtPlotItem::Rtti_PlotCurve, false);
_plot->replot();
}
QVector<QPointF> EnergyScaleForm::generateFitCurvePoints(const ChannelData &chData)
{
int pointCount = 4096;
QVector<QPointF> points;
if (chData.energyFitResultCoeffs.isEmpty()) return points;
for (int i = 0; i < pointCount; ++i) {
double y = 0.0;
switch (chData.energyFitDegree) {
case 1: // 一次拟合y = a + b*x
y = LinearFunction(i,chData.energyFitResultCoeffs[0],chData.energyFitResultCoeffs[1]);
break;
case 2: // 二次拟合y = a + b*x + c*x²
if (chData.energyFitResultCoeffs.size() >= 3) {
y = QuadraticPolynomial(i,chData.energyFitResultCoeffs[0],chData.energyFitResultCoeffs[1],chData.energyFitResultCoeffs[2]);
}
break;
default:
break;
}
points.append(QPointF(i, y));
}
return points;
}
QVector<QPointF> EnergyScaleForm::generateFitCurvePoints(const double a, const double b, const double c)
{
int pointCount = 4096;
QVector<QPointF> points;
// if (chData.energyFitResultCoeffs.isEmpty()) return points;
for (int i = 0; i < pointCount; ++i) {
double y = 0.0;
switch (ui->comboBox_fit_type->currentIndex() + 1) {
case 1: y = LinearFunction(i,a,b); ;break;
case 2: y = QuadraticPolynomial(i,a,b,c);break;
}
points.append(QPointF(i, y));
}
return points;
}
void EnergyScaleForm::calculateFitValues(ChannelData &chData)
{
if (chData.fitData.isEmpty())
return;
if (!chData.energyFitResultCoeffs.isEmpty()) {
for (auto &row : chData.fitData) {
double x = row.daoSite;
double fitE = 0.0;
if (chData.energyFitDegree == 1 && chData.energyFitResultCoeffs.size() >= 2) {
fitE = LinearFunction(x,chData.energyFitResultCoeffs[0],chData.energyFitResultCoeffs[1]);
}
else if (chData.energyFitDegree == 2 && chData.energyFitResultCoeffs.size() >= 3) {
fitE = QuadraticPolynomial(x,chData.energyFitResultCoeffs[0],chData.energyFitResultCoeffs[1],chData.energyFitResultCoeffs[2]);
}
row.fittingEnergy = fitE;
row.energyFittingDeviation = row.energy - fitE;
}
}
if (chData.fwhmFitResultCoeffs.size() >= 2)
{
arma::vec p(2);
p(0) = chData.fwhmFitResultCoeffs[0]; // k
p(1) = chData.fwhmFitResultCoeffs[1]; // c
for (auto &row : chData.fitData) {
double E = row.fittingEnergy;
if (E <= 0) {
row.fitResolution = 0.0;
row.resolutionFittingDeviation = 0.0;
continue;
}
double fitFwhm = FwhmModel(E, p);
row.fitResolution = fitFwhm;
row.resolutionFittingDeviation = row.resolution - fitFwhm;
}
}
else {
// 没有分辨率参数就清空
for (auto &row : chData.fitData) {
row.fitResolution = 0.0;
row.resolutionFittingDeviation = 0.0;
}
}
}
bool EnergyScaleForm::fitFwhmModel(ChannelData &chData)
{
std::vector<double> E_list, fwhm_list;
for (const auto& row : chData.fitData) {
if (row.fittingEnergy > 0 && row.resolution > 0) {
E_list.push_back(row.fittingEnergy);
fwhm_list.push_back(row.resolution);
}
}
if (E_list.size() < 2) {
return false;
}
arma::vec E(E_list.data(), E_list.size());
arma::vec FWHM(fwhm_list.data(), fwhm_list.size());
arma::vec params = NolinearLeastSquaresCurveFit::Lsqcurvefit(FwhmModel,E,FWHM, { 1.0, 1.0 });
chData.fwhmFitResultCoeffs.clear();
chData.fwhmFitResultCoeffs.append(params(0)); // k
chData.fwhmFitResultCoeffs.append(params(1)); // c
return true;
}
bool EnergyScaleForm::saveChannelDataToJson(const QString &filePath)
{
if (filePath.isEmpty()) return false;
QJsonObject rootObj;
QString remark;
if(systemEnble)
remark = ui->plainTextEdit_description->property("systemData").toString();
else
remark = ui->plainTextEdit_description->toPlainText().trimmed();
rootObj["Remark"] = remark;
for (auto it = m_channelList.begin(); it != m_channelList.end(); ++it) {
QString channelName = it.key();
const ChannelData& chData = it.value();
QJsonObject channelObj;
channelObj["EnergyFitDegree"] = chData.energyFitDegree;
// 能量拟合系数
QJsonArray energyCoeffsArr;
for (double coeff : chData.energyFitResultCoeffs) {
energyCoeffsArr.append(coeff);
}
channelObj["EnergyFitResultCoeffs"] = energyCoeffsArr;
// 拟合数据
QJsonArray fitDataArr;
for (const FitDataRow& row : chData.fitData) {
QJsonArray rowArr;
rowArr.append(row.daoSite);
rowArr.append(row.energy);
rowArr.append(row.fittingEnergy);
rowArr.append(row.energyFittingDeviation);
rowArr.append(row.resolution);
rowArr.append(row.fitResolution);
rowArr.append(row.resolutionFittingDeviation);
fitDataArr.append(rowArr);
}
channelObj["FitData"] = fitDataArr;
QJsonArray fwhmCoeffsArr;
for (double coeff : chData.fwhmFitResultCoeffs) {
fwhmCoeffsArr.append(coeff);
}
channelObj["FwhmFitResultCoeffs"] = fwhmCoeffsArr;
rootObj[channelName] = channelObj;
}
QJsonDocument doc(rootObj);
QFile file(filePath);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QMessageBox::critical(this, "错误", QString("保存文件失败:%1").arg(file.errorString()));
return false;
}
file.write(doc.toJson(QJsonDocument::Indented));
file.close();
return true;
}
bool EnergyScaleForm::isDaoSiteDuplicated(const QString &channelName, double daoSite)
{
if (!m_channelList.contains(channelName)) return false;
const auto& fitData = m_channelList[channelName].fitData;
for (const auto& row : fitData) {
if (qAbs(row.daoSite - daoSite) < 1e-3) {
return true;
}
}
return false;
}
void EnergyScaleForm::onItemDoubleClicked(QListWidgetItem *item)
{
if (!item) return;
ui->plainTextEdit_description->clear();
QString filePath = item->data(Qt::UserRole).toString();
if (filePath.isEmpty()) return;
ui->lineEdit_name->setText(item->text());
m_currentFilePath = filePath;
QString errorMsg;
m_channelList = parseJsonToChannels(filePath, errorMsg);
on_comboBox_channel_currentTextChanged(QStringLiteral(u"通道1"));
}
void EnergyScaleForm::on_comboBox_channel_currentTextChanged(const QString &text)
{
if (text.isEmpty()) {
return;
}
if (!m_channelList.contains(text)) {
ChannelData emptyData;
emptyData.energyFitDegree = 1;
m_channelList.insert(text, emptyData);
}
showChannelData(text);
showCurve();
}
void EnergyScaleForm::on_comboBox_fit_type_currentTextChanged(const QString &text)
{
if(text == QStringLiteral(u"一次拟合")) {
ui->label_fit_type_text->setText(QStringLiteral(u"y = a + bx"));
} else if(text == QStringLiteral(u"二次拟合")) {
ui->label_fit_type_text->setText(QStringLiteral(u"y = a + bx + c*x^2"));
}
showCurve();
}
void EnergyScaleForm::on_pBtn_Add_clicked()
{
QString channelName = ui->comboBox_channel->currentText();
QDialog dialog(this);
dialog.setWindowTitle("添加能量刻度点");
dialog.setFixedSize(300, 180); // 调整高度,预留分辨率输入
dialog.setModal(true);
QVBoxLayout* mainLayout = new QVBoxLayout(&dialog);
mainLayout->setSpacing(15);
mainLayout->setContentsMargins(20, 20, 20, 20);
// 道址输入
QHBoxLayout* daoLayout = new QHBoxLayout();
QLabel* daoLabel = new QLabel("道址:");
QLineEdit* daoEdit = new QLineEdit();
daoEdit->setPlaceholderText("请输入道址值(数字)");
QDoubleValidator* daoValidator = new QDoubleValidator(0, 100000, 3, daoEdit);
daoValidator->setNotation(QDoubleValidator::StandardNotation);
daoEdit->setValidator(daoValidator);
daoLayout->addWidget(daoLabel);
daoLayout->addWidget(daoEdit);
// 能量输入
QHBoxLayout* energyLayout = new QHBoxLayout();
QLabel* energyLabel = new QLabel("能量:");
QLineEdit* energyEdit = new QLineEdit();
energyEdit->setPlaceholderText("请输入能量值(数字)");
QDoubleValidator* energyValidator = new QDoubleValidator(0, 10000, 3, energyEdit);
energyValidator->setNotation(QDoubleValidator::StandardNotation);
energyEdit->setValidator(energyValidator);
energyLayout->addWidget(energyLabel);
energyLayout->addWidget(energyEdit);
//分辨率输入
QHBoxLayout* resolutionLayout = new QHBoxLayout();
QLabel* resolutionLabel = new QLabel("分辨率:");
QLineEdit* resolutionEdit = new QLineEdit();
resolutionEdit->setPlaceholderText("请输入分辨率(数字)");
QDoubleValidator* resolutionValidator = new QDoubleValidator(0, 1000, 3, resolutionEdit);
resolutionValidator->setNotation(QDoubleValidator::StandardNotation);
resolutionEdit->setValidator(resolutionValidator);
resolutionLayout->addWidget(resolutionLabel);
resolutionLayout->addWidget(resolutionEdit);
// 按钮行
QHBoxLayout* btnLayout = new QHBoxLayout();
QPushButton* okBtn = new QPushButton("确定");
QPushButton* cancelBtn = new QPushButton("取消");
okBtn->setDefault(true);
btnLayout->addStretch();
btnLayout->addWidget(okBtn);
btnLayout->addWidget(cancelBtn);
mainLayout->addLayout(daoLayout);
mainLayout->addLayout(energyLayout);
mainLayout->addLayout(resolutionLayout);
mainLayout->addLayout(btnLayout);
connect(okBtn, &QPushButton::clicked, &dialog, &QDialog::accept);
connect(cancelBtn, &QPushButton::clicked, &dialog, &QDialog::reject);
daoEdit->setFocus();
if (dialog.exec() != QDialog::Accepted) {
return;
}
QString daoStr = daoEdit->text().trimmed();
QString energyStr = energyEdit->text().trimmed();
QString resolutionStr = resolutionEdit->text().trimmed();
if (daoStr.isEmpty() || energyStr.isEmpty()) {
QMessageBox::warning(this, "输入错误", "道址和能量不能为空");
return;
}
bool daoOk = false, energyOk = false, resolutionOk = false;
double daoSite = daoStr.toDouble(&daoOk);
double energy = energyStr.toDouble(&energyOk);
double resolution = resolutionStr.isEmpty() ? 0.0 : resolutionStr.toDouble(&resolutionOk);
if (!daoOk || !energyOk || (!resolutionStr.isEmpty() && !resolutionOk)) {
QMessageBox::warning(this, "输入错误", "请输入有效的数字");
return;
}
if (isDaoSiteDuplicated(channelName, daoSite)) {
QMessageBox::warning(this, "输入错误", "该道址已存在,无法重复添加");
return;
}
ChannelData& chData = m_channelList[channelName];
QTableWidget* table = ui->tablew_scale_data;
FitDataRow newRow;
newRow.daoSite = daoSite;
newRow.energy = energy;
newRow.resolution = resolution;
newRow.fittingEnergy = 0.0;
newRow.energyFittingDeviation = 0.0;
newRow.fitResolution = 0.0;
newRow.resolutionFittingDeviation = 0.0;
chData.fitData.append(newRow);
m_isLoadingTable = true;
int newRowIndex = table->rowCount();
table->insertRow(newRowIndex);
table->setItem(newRowIndex, 0, new QTableWidgetItem(QString::number(daoSite, 'f', 3)));
table->setItem(newRowIndex, 1, new QTableWidgetItem(QString::number(energy, 'f', 3)));
table->setItem(newRowIndex, 4, new QTableWidgetItem(QString::number(resolution, 'f', 3)));
for (int col = 2; col < 7; ++col) {
if (col != 4) {
table->setItem(newRowIndex, col, new QTableWidgetItem("0.000"));
}
}
for (int col = 0; col < 7; ++col) {
Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
if (col == 0 || col == 1 || col == 4) {
flags |= Qt::ItemIsEditable;
}
table->item(newRowIndex, col)->setFlags(flags);
}
m_isLoadingTable = false;
table->scrollToBottom();
table->selectRow(newRowIndex);
showCurve();
QMessageBox::information(this, "成功", "数据点添加成功");
}
void EnergyScaleForm::on_pBtn_Delete_clicked()
{
QTableWidget* table = ui->tablew_scale_data;
int currentRow = table->currentRow();
if (currentRow < 0 || currentRow >= table->rowCount()) {
QMessageBox::warning(this, "提示", "请选中要删除的行");
return;
}
if (QMessageBox::question(this, "确认", "是否删除选中的行?", QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) {
return;
}
table->removeRow(currentRow);
QString channelName = ui->comboBox_channel->currentText();
if (m_channelList.contains(channelName)) {
ChannelData& chData = m_channelList[channelName];
if (currentRow < chData.fitData.size()) {
chData.fitData.remove(currentRow);
}
// 删除后重新计算拟合值(如果有拟合系数)
calculateFitValues(chData);
// 刷新表格显示
showChannelData(channelName);
}
showCurve();
}
void EnergyScaleForm::on_pBtn_fitting_clicked()
{
QString channelName = ui->comboBox_channel->currentText();
if (!m_channelList.contains(channelName)) {
QMessageBox::warning(this, "拟合失败", "当前通道无数据");
return;
}
ChannelData& chData = m_channelList[channelName];
int rowCount = ui->tablew_scale_data->rowCount();
int fitDegree = ui->comboBox_fit_type->currentIndex() + 1;
int minEnergyPoints = (fitDegree == 1) ? 2 : 3;
if (rowCount < minEnergyPoints) {
QMessageBox::warning(this, "拟合失败",
QString("至少需要%1个数据点才能进行%2").arg(minEnergyPoints).arg(fitDegree == 1 ? "一次拟合" : "二次拟合"));
return;
}
std::vector<double> xRow,yRow;
for(int i = 0; i < rowCount;++i) {
QTableWidgetItem* daoItem = ui->tablew_scale_data->item(i,0);
QTableWidgetItem* energyItem = ui->tablew_scale_data->item(i,1);
if (!daoItem || !energyItem) continue;
bool daoOk = false, energyOk = false;
double dao = daoItem->text().toDouble(&daoOk);
double energy = energyItem->text().toDouble(&energyOk);
if (daoOk && energyOk) {
xRow.push_back(dao);
yRow.push_back(energy);
}
}
if (xRow.size() < minEnergyPoints || yRow.size() < minEnergyPoints) {
QMessageBox::warning(this, "拟合失败", "有效能量数据点不足");
return;
}
std::vector<double> energyCoeff = GaussPolyCoe::PolynomialFit(xRow, yRow, fitDegree);
if (energyCoeff.size() < fitDegree + 1) {
QMessageBox::warning(this, "拟合失败", "能量拟合计算出错");
return;
}
chData.energyFitDegree = fitDegree;
chData.energyFitResultCoeffs.clear();
for (double coeff : energyCoeff) {
chData.energyFitResultCoeffs.append(coeff);
}
fitFwhmModel(chData);
calculateFitValues(chData);
m_isLoadingTable = true;
showChannelData(channelName);
m_isLoadingTable = false;
showCurve();
QString energyMsg;
if (fitDegree == 1) {
energyMsg = QString("能量拟合成功a = %1 , b = %2")
.arg(energyCoeff[0], 0, 'f', 6)
.arg(energyCoeff[1], 0, 'f', 6);
} else {
energyMsg = QString("能量拟合成功a = %1 , b = %2 , c = %3")
.arg(energyCoeff[0], 0, 'f', 6)
.arg(energyCoeff[1], 0, 'f', 6)
.arg(energyCoeff[2], 0, 'f', 6);
}
}
void EnergyScaleForm::on_tablew_scale_data_cellChanged(int row, int column)
{
if (m_isLoadingTable) return;
QString channelName = ui->comboBox_channel->currentText();
if (!m_channelList.contains(channelName)) return;
if (row < 0 || row >= m_channelList[channelName].fitData.size()) return;
ChannelData& chData = m_channelList[channelName];
FitDataRow& currentRow = chData.fitData[row];
QTableWidgetItem* item = ui->tablew_scale_data->item(row, column);
if (!item) return;
bool ok = false;
double value = item->text().toDouble(&ok);
if (!ok) {
// 输入非数字时恢复原值
m_isLoadingTable = true;
if (column == 0) {
item->setText(QString::number(currentRow.daoSite, 'f', 3));
} else if (column == 1) {
item->setText(QString::number(currentRow.energy, 'f', 3));
} else if (column == 4) {
item->setText(QString::number(currentRow.resolution, 'f', 3));
}
m_isLoadingTable = false;
QMessageBox::warning(this, "输入错误", "请输入有效的数字");
return;
}
bool needRecalc = false;
if (column == 0) {
// 检查道址重复
if (isDaoSiteDuplicated(channelName, value) && qAbs(currentRow.daoSite - value) > 1e-3) {
m_isLoadingTable = true;
item->setText(QString::number(currentRow.daoSite, 'f', 3));
m_isLoadingTable = false;
QMessageBox::warning(this, "提示", "道址重复,修改失败");
return;
}
currentRow.daoSite = value;
needRecalc = true;
} else if (column == 1) {
currentRow.energy = value;
needRecalc = true;
} else if (column == 4) {
currentRow.resolution = value;
needRecalc = true;
}
if (needRecalc) {
// calculateFitValues(chData);
// m_isLoadingTable = true;
// showChannelData(channelName);
// m_isLoadingTable = false;
// showCurve();
on_pBtn_fitting_clicked();
}
}
void EnergyScaleForm::on_pBtn_SaveAs_clicked()
{
QString SaveAsName = ui->pBtn_SaveAs->text();
QString targetFilePath;
QString fileName;
QString remark;
if(SaveAsName.contains("保存到系统"))
{
systemEnble = true;
QDialog dialog(this);
dialog.setWindowTitle(QStringLiteral(u"保存到系统 - 能量刻度配置"));
dialog.setFixedSize(400, 250);
dialog.setModal(true);
QVBoxLayout* mainLayout = new QVBoxLayout(&dialog);
mainLayout->setSpacing(15);
mainLayout->setContentsMargins(20, 20, 20, 20);
QHBoxLayout* nameLayout = new QHBoxLayout();
QLabel* nameLabel = new QLabel(QStringLiteral(u"能量刻度名称:"));
QLineEdit* nameEdit = new QLineEdit();
nameEdit->setPlaceholderText(QStringLiteral(u"请输入刻度名称:"));
if (!ui->lineEdit_name->text().isEmpty()) {
QString baseName = ui->lineEdit_name->text();
baseName = baseName.contains("[") ? baseName.split("]").last().trimmed() : baseName;
nameEdit->setText(baseName);
}
nameLayout->addWidget(nameLabel);
nameLayout->addWidget(nameEdit);
// 备注输入框
QVBoxLayout* remarkLayout = new QVBoxLayout();
QLabel* remarkLabel = new QLabel(QStringLiteral(u"备注:"));
QTextEdit* remarkEdit = new QTextEdit();
remarkEdit->setPlaceholderText(QStringLiteral(u"请输入备注信息(可选)"));
remarkEdit->setPlainText(ui->plainTextEdit_description->toPlainText()); // 填充现有备注
remarkLayout->addWidget(remarkLabel);
remarkLayout->addWidget(remarkEdit);
// 按钮组
QHBoxLayout* btnLayout = new QHBoxLayout();
QPushButton* okBtn = new QPushButton(QStringLiteral(u"保存"));
QPushButton* cancelBtn = new QPushButton(QStringLiteral(u"取消"));
okBtn->setDefault(true);
btnLayout->addStretch();
btnLayout->addWidget(okBtn);
btnLayout->addWidget(cancelBtn);
// 组装布局
mainLayout->addLayout(nameLayout);
mainLayout->addLayout(remarkLayout);
mainLayout->addLayout(btnLayout);
connect(okBtn, &QPushButton::clicked, &dialog, &QDialog::accept);
connect(cancelBtn, &QPushButton::clicked, &dialog, &QDialog::reject);
if (dialog.exec() != QDialog::Accepted) {
return;
}
fileName = nameEdit->text().trimmed();
remark = remarkEdit->toPlainText().trimmed();
if (fileName.isEmpty()) {
QMessageBox::warning(this, QStringLiteral(u"输入错误"), QStringLiteral(u"能量刻度名称不能为空!"));
return;
}
QString defaultDir = QDir(qApp->applicationDirPath()).filePath("configure/EnergyScale");
QDir dir(defaultDir);
if (!dir.exists() && !dir.mkpath(".")) {
QMessageBox::critical(this, QStringLiteral(u"错误"), QStringLiteral(u"无法创建目录:%1").arg(defaultDir));
return;
}
targetFilePath = dir.filePath(fileName + ".json"); // 强制.json后缀
if (QFile::exists(targetFilePath)) {
QMessageBox::StandardButton ret = QMessageBox::question(
this,
QStringLiteral(u"文件已存在"),
QStringLiteral(u"名称为“%1”的能量刻度文件已存在是否覆盖").arg(fileName),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No
);
if (ret != QMessageBox::Yes) {
return;
}
}
ui->plainTextEdit_description->setProperty("systemData",remark);
}else{
systemEnble = false;
// 选择保存路径
QString defaultDir = QDir(qApp->applicationDirPath()).filePath("configure/EnergyScale");
targetFilePath = QFileDialog::getSaveFileName(this,
"另存为能量刻度文件",
defaultDir,
"JSON文件 (*.json);;所有文件 (*.*)");
if (targetFilePath.isEmpty()) {
return;
}
// 确保后缀是.json
if (!targetFilePath.endsWith(".json", Qt::CaseInsensitive)) {
targetFilePath += ".json";
}
}
// 保存文件
if (saveChannelDataToJson(targetFilePath)) {
// m_currentFilePath = targetFilePath; // 更新当前文件路径
// ui->lineEdit_name->setText(QFileInfo(targetFilePath).baseName());
loadAllFilesInTheFolder();
}
}
void EnergyScaleForm::on_pBtn_Save_clicked()
{
systemEnble = false;
if (saveChannelDataToJson(m_currentFilePath)) {
QMessageBox::information(this, "成功", "数据保存成功");
} else {
QMessageBox::critical(this, "失败", "数据保存失败");
}
}
void EnergyScaleForm::on_pBtn_Add_File_clicked()
{
bool isOk = false;
QString fileName = QInputDialog::getText(this,
QStringLiteral(u"新建能量刻度文件"),
QStringLiteral(u"请输入文件名(无需后缀):"),
QLineEdit::Normal,
QStringLiteral(u"新刻度文件"),
&isOk);
if (!isOk || fileName.trimmed().isEmpty()) {
return; // 用户取消或输入为空
}
fileName = fileName.trimmed();
const QString energyScaleDir = QDir(qApp->applicationDirPath()).filePath("configure/EnergyScale");
QDir dir(energyScaleDir);
if (!dir.exists() && !dir.mkpath(".")) {
QMessageBox::critical(this, QStringLiteral(u"错误"),
QStringLiteral(u"无法创建目录:%1").arg(energyScaleDir));
return;
}
QString filePath = dir.filePath(fileName + ".json");
if (QFile::exists(filePath)) {
QMessageBox::warning(this, QStringLiteral(u"提示"),
QStringLiteral(u"文件%1已存在请更换文件名").arg(fileName));
return;
}
QJsonObject rootObj;
for (int i = 1; i <= 32; ++i) {
QString channelName = QStringLiteral(u"通道%1").arg(i);
QJsonObject channelObj;
channelObj["EnergyFitDegree"] = 1; // 默认一次拟合
channelObj["EnergyFitResultCoeffs"] = QJsonArray(); // 空系数
channelObj["FitData"] = QJsonArray(); // 空拟合数据
channelObj["FwhmFitResultCoeffs"] = QJsonArray(); // 空分辨率系数
rootObj[channelName] = channelObj;
}
QJsonDocument doc(rootObj);
QFile file(filePath);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QMessageBox::critical(this, QStringLiteral(u"错误"),
QStringLiteral(u"无法创建文件:%1\n%2").arg(filePath).arg(file.errorString()));
return;
}
file.write(doc.toJson(QJsonDocument::Indented));
file.close();
loadAllFilesInTheFolder();
for (int i = 0; i < ui->listWidget->count(); ++i) {
QListWidgetItem* item = ui->listWidget->item(i);
if (item->text() == fileName) {
ui->listWidget->setCurrentItem(item);
break;
}
}
QMessageBox::information(this, QStringLiteral(u"成功"),
QStringLiteral(u"文件%1创建成功").arg(fileName + ".json"));
}
void EnergyScaleForm::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->tablew_scale_data->setRowCount(0);
clearPlot();
_plot->replot();
ui->label_fit_result->clear();
ui->label_fit_type_text->clear();
}
QMessageBox::information(this, QStringLiteral(u"成功"),
QStringLiteral(u"删除成功"));
} else {
QMessageBox::critical(this, QStringLiteral(u"错误"),
QStringLiteral(u"删除失败:%1").arg(filePath));
}
}