EnergySpectrumAnalyer/src/EnergyScaleForm.cpp

1121 lines
41 KiB
C++
Raw Normal View History

#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));
}
}