1659 lines
62 KiB
C++
1659 lines
62 KiB
C++
#include "EnergyCountPeakFitView.h"
|
||
#include "CustomQwtPlot.h"
|
||
#include "PlotRectItem.h"
|
||
#include "csv.h"
|
||
#include <GlobalDefine.h>
|
||
#include <QCheckBox>
|
||
#include <QDebug>
|
||
#include <QDialog>
|
||
#include <QFileInfo>
|
||
#include <QHBoxLayout>
|
||
#include <QInputDialog>
|
||
#include <QMenu>
|
||
#include <QPushButton>
|
||
#include <QwtLegend>
|
||
#include <QwtPlotCanvas>
|
||
#include <QwtPlotCurve>
|
||
#include <QwtPlotMarker>
|
||
#include <QwtScaleDiv>
|
||
#include <QwtScaleMap>
|
||
#include <QwtText>
|
||
#include <QDialog>
|
||
#include <QPushButton>
|
||
#include <QCheckBox>
|
||
#include <QwtScaleDiv>
|
||
#include <QFileInfo>
|
||
#include "PlotRectItem.h"
|
||
#include <QInputDialog>
|
||
#include <QDebug>
|
||
#include <QPainter>
|
||
#include <QMessageBox>
|
||
#include <QListWidget>
|
||
#include <QDateTime>
|
||
#include <QDir>
|
||
#include <QJsonArray>
|
||
#include <QJsonDocument>
|
||
#include <QJsonObject>
|
||
#include <QDir>
|
||
#include <QTableView>
|
||
#include <QStandardItemModel>
|
||
#include <QHeaderView>
|
||
#include "MeasureAnalysisProjectModel.h"
|
||
#include "BusyIndicator.h"
|
||
|
||
|
||
QJsonObject PeakFitHistoryItem::toJson() const
|
||
{
|
||
QJsonObject obj;
|
||
obj["timestamp"] = timestamp.toString(Qt::ISODate);
|
||
QJsonArray curvesJson;
|
||
for (const auto& curve : curveList) {
|
||
QJsonObject curveObj;
|
||
curveObj["amplitude"] = curve.amplitude;
|
||
curveObj["sigma"] = curve.sigma;
|
||
curveObj["sigmoidH"] = curve.sigmoidH;
|
||
curveObj["sigmoidW"] = curve.sigmoidW;
|
||
curveObj["baseline"] = curve.baseline;
|
||
curveObj["center"] = curve.center;
|
||
curveObj["xMin"] = curve.xMin;
|
||
curveObj["xMax"] = curve.xMax;
|
||
|
||
curveObj["fwhm"] = curve.fwhm;
|
||
curveObj["area"] = curve.area;
|
||
|
||
//保存框选区域
|
||
if (!curve.selectionRect.isNull()) {
|
||
QJsonObject rectObj;
|
||
rectObj["left"] = curve.selectionRect.left();
|
||
rectObj["top"] = curve.selectionRect.top();
|
||
rectObj["right"] = curve.selectionRect.right();
|
||
rectObj["bottom"] = curve.selectionRect.bottom();
|
||
curveObj["selectionRect"] = rectObj;
|
||
}
|
||
curvesJson.append(curveObj);
|
||
}
|
||
obj["curves"] = curvesJson;
|
||
return obj;
|
||
}
|
||
|
||
PeakFitHistoryItem PeakFitHistoryItem::fromJson(const QJsonObject& obj)
|
||
{
|
||
PeakFitHistoryItem item;
|
||
item.timestamp = QDateTime::fromString(obj["timestamp"].toString(), Qt::ISODate);
|
||
QJsonArray curvesJson = obj["curves"].toArray();
|
||
for (const auto& value : curvesJson) {
|
||
FitCurveData curve;
|
||
QJsonObject curveObj = value.toObject();
|
||
curve.amplitude = curveObj["amplitude"].toDouble();
|
||
curve.sigma = curveObj["sigma"].toDouble();
|
||
curve.sigmoidH = curveObj["sigmoidH"].toDouble();
|
||
curve.sigmoidW = curveObj["sigmoidW"].toDouble();
|
||
curve.baseline = curveObj["baseline"].toDouble();
|
||
curve.center = curveObj["center"].toDouble();
|
||
curve.xMin = curveObj["xMin"].toDouble();
|
||
curve.xMax = curveObj["xMax"].toDouble();
|
||
|
||
curve.fwhm = curveObj["fwhm"].toDouble(0.0);
|
||
curve.area = curveObj["area"].toDouble(0.0);
|
||
|
||
//加载框选区域
|
||
if (curveObj.contains("selectionRect")) {
|
||
QJsonObject rectObj = curveObj["selectionRect"].toObject();
|
||
curve.selectionRect = QRectF(
|
||
rectObj["left"].toDouble(),
|
||
rectObj["top"].toDouble(),
|
||
rectObj["right"].toDouble() - rectObj["left"].toDouble(),
|
||
rectObj["bottom"].toDouble() - rectObj["top"].toDouble()
|
||
);
|
||
}
|
||
item.curveList.append(curve);
|
||
}
|
||
return item;
|
||
}
|
||
|
||
EnergyCountPeakFitView::EnergyCountPeakFitView(QWidget* parent)
|
||
: MeasureAnalysisView { parent }
|
||
{
|
||
this->setViewType(PlotFrame);
|
||
|
||
QHBoxLayout* layout = new QHBoxLayout(this);
|
||
this->_plot = new CustomQwtPlot(this);
|
||
layout->addWidget(this->_plot);
|
||
setupPlot();
|
||
this->_menu = new QMenu(this);
|
||
setupMenu();
|
||
}
|
||
|
||
EnergyCountPeakFitView::~EnergyCountPeakFitView()
|
||
{
|
||
LOG_DEBUG(QStringLiteral(u"%1析构.").arg(this->GetViewName()));
|
||
delete _rubberBand;
|
||
clearAllSelectionRects();
|
||
clearFitCurves();
|
||
}
|
||
|
||
void EnergyCountPeakFitView::InitViewWorkspace(const QString& project_name)
|
||
{
|
||
Q_UNUSED(project_name);
|
||
if (project_name.isEmpty())
|
||
return;
|
||
auto project_model = ProjectList::Instance()->GetProjectModel(project_name);
|
||
if (!project_model)
|
||
return;
|
||
|
||
QDir project_dir(project_model->GetProjectDir());
|
||
_workspace = project_dir.filePath(this->GetViewName());
|
||
if (!QDir(_workspace).exists())
|
||
project_dir.mkpath(_workspace);
|
||
|
||
_historyFilePath = QDir(_workspace).filePath("peak_fit_history.json");
|
||
loadHistoryFromFile();
|
||
}
|
||
|
||
void EnergyCountPeakFitView::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();
|
||
if (QFileInfo(data_filename).exists()) {
|
||
loadDataFromFile(data_name, data_filename);
|
||
}
|
||
}
|
||
}
|
||
|
||
void EnergyCountPeakFitView::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"能量(KeV)");
|
||
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);
|
||
|
||
// set axis auto scale
|
||
_plot->setAxisAutoScale(QwtPlot::xBottom, true);
|
||
_plot->setAxisAutoScale(QwtPlot::yLeft, true);
|
||
|
||
_plot->enableAxis(QwtPlot::xBottom);
|
||
_plot->enableAxis(QwtPlot::yLeft);
|
||
|
||
// 设置QWT图例
|
||
QwtLegend* legend = new QwtLegend();
|
||
legend->setDefaultItemMode(QwtLegendData::ReadOnly);
|
||
_plot->insertLegend(legend, QwtPlot::RightLegend);
|
||
_plot->SetAxisDragScale(QwtPlot::xBottom, true);
|
||
|
||
QwtPlotCurve *dummyFit = new QwtPlotCurve(QStringLiteral(u"拟合数据"));
|
||
dummyFit->setPen(QPen(Qt::blue, 2));
|
||
dummyFit->setSamples(QVector<double>(), QVector<double>()); // 无数据
|
||
dummyFit->setVisible(false); // 不在画布上绘制
|
||
dummyFit->setItemAttribute(QwtPlotItem::Legend, true); // 保证图例显示(默认 true)
|
||
dummyFit->attach(_plot);
|
||
|
||
// 本底数据(虚拟,图例用)
|
||
QwtPlotCurve *dummyBg = new QwtPlotCurve(QStringLiteral(u"本底数据"));
|
||
dummyBg->setPen(QPen(Qt::yellow, 2));
|
||
dummyBg->setSamples(QVector<double>(), QVector<double>());
|
||
dummyBg->setVisible(false);
|
||
dummyBg->attach(_plot);
|
||
|
||
_data_selector = new CustomQwtPlotXaxisSelector(_plot->canvas());
|
||
_data_selector->setEnabled(false);
|
||
|
||
// 启用鼠标追踪,并安装事件过滤器
|
||
_plot->canvas()->setMouseTracking(true);
|
||
_plot->canvas()->installEventFilter(this);
|
||
}
|
||
|
||
void EnergyCountPeakFitView::setupMenu()
|
||
{
|
||
this->setContextMenuPolicy(Qt::CustomContextMenu);
|
||
connect(this, &EnergyCountPeakFitView::customContextMenuRequested, [this](const QPoint& pos) {
|
||
this->_menu->exec(this->mapToGlobal(pos));
|
||
});
|
||
QAction* action_plot_reset = this->_menu->addAction(QStringLiteral(u"还原"));
|
||
action_plot_reset->setObjectName("plot_reset");
|
||
connect(action_plot_reset, &QAction::triggered, [this]() {
|
||
this->_plot->ResetPlot();
|
||
});
|
||
this->_menu->addSeparator();
|
||
//QAction* action_set_curve_show = this->_menu->addAction(QStringLiteral(u"选择曲线"));
|
||
//action_set_curve_show->setObjectName("curve_show_setting");
|
||
//connect(action_set_curve_show, &QAction::triggered, this, &EnergyCountPeakFitView::onActionCurveShowSetting);
|
||
QAction* action_plot_config = this->_menu->addAction(QStringLiteral(u"图表配置"));
|
||
action_plot_config->setObjectName("plot_config");
|
||
connect(action_plot_config, &QAction::triggered, this, &EnergyCountPeakFitView::onActionPlotConfigure);
|
||
|
||
this->_menu->addSeparator();
|
||
QAction* action_refit_rect = this->_menu->addAction(QStringLiteral(u"重新拟合当前区域"));
|
||
connect(action_refit_rect, &QAction::triggered, this, &EnergyCountPeakFitView::onActionRefitCurrentRect);
|
||
//QAction* action_save_fit = this->_menu->addAction(QStringLiteral(u"保存当前拟合结果"));
|
||
//connect(action_save_fit, &QAction::triggered, this, &EnergyCountPeakFitView::onActionSaveCurrentFit);
|
||
|
||
QAction* action_show_history = this->_menu->addAction(QStringLiteral(u"峰拟合结果"));
|
||
connect(action_show_history, &QAction::triggered, this, &EnergyCountPeakFitView::onActionShowFitHistory);
|
||
//删除当前悬停的框选区域
|
||
QAction* action_delete_hovered_rect = this->_menu->addAction(QStringLiteral(u"删除当前框选区域"));
|
||
connect(action_delete_hovered_rect, &QAction::triggered, this, &EnergyCountPeakFitView::onActionDeleteHoveredRect);
|
||
|
||
//QAction* action_hint = this->_menu->addAction(QStringLiteral(u"框选提示:按住 Ctrl 键并拖动左键"));
|
||
//action_hint->setEnabled(false);
|
||
}
|
||
|
||
void EnergyCountPeakFitView::loadDataFromFile(const QString& data_name, const QString& filename)
|
||
{
|
||
//加载新数据前清空旧数据
|
||
_currentFitRecords.clear();
|
||
clearFitCurves();
|
||
clearAllSelectionRects();
|
||
_plot->replot();
|
||
std::string address_str = QString(QStringLiteral(u"能量(KeV)")).toStdString();
|
||
std::string count_str = QString(QStringLiteral(u"计数")).toStdString();
|
||
|
||
io::CSVReader<
|
||
2,
|
||
io::trim_chars<' ', '\t'>,
|
||
io::double_quote_escape<',', '"'>,
|
||
io::throw_on_overflow,
|
||
io::empty_line_comment>
|
||
reader(QStrToSysPath(filename));
|
||
reader.read_header(io::ignore_extra_column, address_str, count_str);
|
||
|
||
double energy;
|
||
unsigned long long energy_count;
|
||
QVector<double> x, y;
|
||
while (reader.read_row(energy, energy_count)) {
|
||
x.push_back(energy);
|
||
y.push_back(energy_count);
|
||
}
|
||
// 绘制曲线
|
||
QwtPlotCurve* curve = new QwtPlotCurve(QStringLiteral(u"原始数据"));
|
||
curve->setPen(QPen(Qt::gray, 2)); // 原始数据统一灰色
|
||
curve->setSamples(x, y);
|
||
_plot->AddCurve(curve,false);
|
||
|
||
|
||
}
|
||
|
||
void EnergyCountPeakFitView::onActionCurveShowSetting()
|
||
{
|
||
if (!_curve_show_setting_dlg) {
|
||
_curve_show_setting_dlg = new QDialog(this, Qt::Dialog | Qt::WindowCloseButtonHint);
|
||
_curve_show_setting_dlg->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||
_curve_show_setting_dlg->setSizeGripEnabled(false);
|
||
_curve_show_setting_dlg->setWindowTitle(QString(QStringLiteral(u"选择%1曲线显示").arg(this->_plot->title().text())));
|
||
_curve_show_setting_dlg->setWindowModality(Qt::WindowModal);
|
||
_curve_show_setting_dlg->setModal(false);
|
||
QVBoxLayout* layout = new QVBoxLayout(_curve_show_setting_dlg);
|
||
|
||
QStringList curve_name_list;
|
||
for (QwtPlotCurve* curve : this->_plot->GetCurveList()) {
|
||
curve_name_list.append(curve->title().text());
|
||
}
|
||
// 自动计算多列排布
|
||
int num_columns = std::sqrt(this->_plot->GetCurveList().size());
|
||
if (num_columns == 0)
|
||
num_columns = 1;
|
||
QVBoxLayout* checkbox_layout = new QVBoxLayout();
|
||
QHBoxLayout* checkbox_column_layout = new QHBoxLayout();
|
||
QMap<QwtPlotCurve*, QCheckBox*> curve_checkbox_map;
|
||
auto addCurveToLayout = [&](const QString& curve_name) {
|
||
QwtPlotCurve* curve = this->_plot->GetCurve(curve_name);
|
||
QCheckBox* check_box = new QCheckBox(curve->title().text());
|
||
check_box->setChecked(curve->isVisible());
|
||
checkbox_column_layout->addWidget(check_box);
|
||
connect(check_box, &QCheckBox::stateChanged, [curve](int state) {
|
||
curve->setVisible(state == Qt::Checked);
|
||
curve->plot()->replot();
|
||
});
|
||
curve_checkbox_map[curve] = check_box;
|
||
if (checkbox_column_layout->count() >= num_columns) {
|
||
checkbox_layout->addLayout(checkbox_column_layout);
|
||
checkbox_column_layout = new QHBoxLayout();
|
||
}
|
||
};
|
||
for (const QString& ch_name : curve_name_list) {
|
||
addCurveToLayout(ch_name);
|
||
}
|
||
if (checkbox_column_layout->count() < num_columns) {
|
||
checkbox_column_layout->addStretch();
|
||
}
|
||
checkbox_layout->addLayout(checkbox_column_layout);
|
||
// 全选和反选
|
||
auto curveCheckboxUpdate = [this, curve_checkbox_map]() {
|
||
for (QwtPlotCurve* curve : this->_plot->GetCurveList()) {
|
||
curve_checkbox_map[curve]->setChecked(curve->isVisible());
|
||
}
|
||
};
|
||
QHBoxLayout* button_layout = new QHBoxLayout();
|
||
QPushButton* btn_all_select = new QPushButton(QString(QStringLiteral(u"全选")));
|
||
connect(btn_all_select, &QPushButton::clicked, [this, curveCheckboxUpdate]() {
|
||
for (QwtPlotCurve* curve : this->_plot->GetCurveList()) {
|
||
curve->setVisible(true);
|
||
}
|
||
curveCheckboxUpdate();
|
||
this->_plot->replot();
|
||
});
|
||
button_layout->addWidget(btn_all_select);
|
||
QPushButton* btn_reserve_select = new QPushButton(QString(QStringLiteral(u"反选")));
|
||
connect(btn_reserve_select, &QPushButton::clicked, [this, curveCheckboxUpdate]() {
|
||
for (QwtPlotCurve* curve : this->_plot->GetCurveList()) {
|
||
curve->setVisible(!curve->isVisible());
|
||
}
|
||
curveCheckboxUpdate();
|
||
this->_plot->replot();
|
||
});
|
||
button_layout->addWidget(btn_reserve_select);
|
||
|
||
layout->addLayout(button_layout);
|
||
layout->addLayout(checkbox_layout);
|
||
}
|
||
_curve_show_setting_dlg->show();
|
||
}
|
||
|
||
void EnergyCountPeakFitView::onActionPlotConfigure()
|
||
{
|
||
}
|
||
|
||
void EnergyCountPeakFitView::clearAllSelectionRects()
|
||
{
|
||
for (PlotRectItem* item : _selectionRectItems) {
|
||
if (item) {
|
||
item->detach();
|
||
delete item;
|
||
}
|
||
}
|
||
_selectionRectItems.clear();
|
||
_plot->replot();
|
||
}
|
||
|
||
bool EnergyCountPeakFitView::eventFilter(QObject* watched, QEvent* event)
|
||
{
|
||
if (watched == _plot->canvas()) {
|
||
QMouseEvent* me = static_cast<QMouseEvent*>(event);
|
||
|
||
// 按下 Ctrl+左键 开始框选
|
||
if (event->type() == QEvent::MouseButtonPress &&
|
||
me->button() == Qt::LeftButton /*&&*/
|
||
/*(me->modifiers() & Qt::ControlModifier)*/) {
|
||
startSelection(me->pos());
|
||
|
||
return true;
|
||
}
|
||
// 移动时更新橡皮筋
|
||
else if (event->type() == QEvent::MouseMove && _isSelecting) {
|
||
updateSelection(me->pos());
|
||
return true;
|
||
}
|
||
// 释放左键完成框选
|
||
else if (event->type() == QEvent::MouseButtonRelease &&
|
||
me->button() == Qt::LeftButton && _isSelecting) {
|
||
finishSelection();
|
||
fadeSelectionRectBorders();
|
||
return true;
|
||
}
|
||
//鼠标移动时检测悬停
|
||
else if (event->type() == QEvent::MouseMove) {
|
||
updateHoverState(me->pos());
|
||
return true;
|
||
}
|
||
}
|
||
return MeasureAnalysisView::eventFilter(watched, event);
|
||
}
|
||
|
||
void EnergyCountPeakFitView::startSelection(const QPoint& pos)
|
||
{
|
||
_isSelecting = true;
|
||
_selectionStart = pos;
|
||
|
||
if (!_rubberBand) {
|
||
_rubberBand = new QRubberBand(QRubberBand::Rectangle, _plot->canvas());
|
||
}
|
||
int canvasHeight = _plot->canvas()->height();
|
||
// 初始矩形:宽度为0,高度为画布全高
|
||
_rubberBand->setGeometry(QRect(_selectionStart.x(), 0, 0, canvasHeight));
|
||
_rubberBand->show();
|
||
}
|
||
|
||
void EnergyCountPeakFitView::updateSelection(const QPoint& pos)
|
||
{
|
||
if (!_rubberBand)
|
||
return;
|
||
int canvasHeight = _plot->canvas()->height();
|
||
int x1 = _selectionStart.x();
|
||
int x2 = pos.x();
|
||
int left = qMin(x1, x2);
|
||
int width = qAbs(x1 - x2);
|
||
_rubberBand->setGeometry(QRect(left, 0, width, canvasHeight));
|
||
}
|
||
void EnergyCountPeakFitView::finishSelection()
|
||
{
|
||
if (_rubberBand)
|
||
{
|
||
|
||
QRect finalRect = _rubberBand->geometry();
|
||
_rubberBand->hide();
|
||
|
||
if (finalRect.width() > 2) {
|
||
const QwtScaleMap xMap = _plot->canvasMap(QwtPlot::xBottom);
|
||
double xMin = xMap.invTransform(finalRect.left());
|
||
double xMax = xMap.invTransform(finalRect.right());
|
||
|
||
if (xMin > xMax) std::swap(xMin, xMax);
|
||
|
||
|
||
double yMin = _plot->axisScaleDiv(QwtPlot::yLeft).lowerBound();
|
||
double yMax = _plot->axisScaleDiv(QwtPlot::yLeft).upperBound();
|
||
QRectF plotRect(xMin, yMin, xMax - xMin, yMax - yMin);
|
||
|
||
// addSelectionRect(plotRect);
|
||
|
||
|
||
// 获取曲线数据
|
||
QList<QwtPlotCurve*> curves = _plot->GetCurveList();
|
||
if (!curves.isEmpty()) {
|
||
QwtPlotCurve* originalCurve = curves.first();
|
||
QVector<double> origX, origY;
|
||
for (size_t i = 0; i < originalCurve->dataSize(); ++i) {
|
||
origX.push_back(originalCurve->sample(i).x());
|
||
origY.push_back(originalCurve->sample(i).y());
|
||
}
|
||
|
||
QVector<double> roiX, roiY;
|
||
for (int i = 0; i < origX.size(); ++i) {
|
||
if (origX[i] >= xMin && origX[i] <= xMax) {
|
||
roiX.push_back(origX[i]);
|
||
roiY.push_back(origY[i]);
|
||
}
|
||
}
|
||
|
||
if (roiX.size() >= 3) {
|
||
QStringList algorithms;
|
||
algorithms << QStringLiteral(u"y=A*exp(-pow(x-P,2)/(2*pow(delt,2))) + H/(1+exp((x-P)/W)) + C");
|
||
bool ok = false;
|
||
QString selected = QInputDialog::getItem(this,
|
||
QStringLiteral(u"选择拟合算法"),
|
||
QStringLiteral(u"请选择用于当前框选区域的峰拟合算法:"),
|
||
algorithms,
|
||
0,
|
||
false,
|
||
&ok);
|
||
if (ok && selected == QStringLiteral(u"y=A*exp(-pow(x-P,2)/(2*pow(delt,2))) + H/(1+exp((x-P)/W)) + C"))
|
||
{
|
||
arma::vec armaRoiX(roiX.size()), armaRoiY(roiY.size());
|
||
for (int i = 0; i < roiX.size(); ++i) {
|
||
armaRoiX(i) = roiX[i];
|
||
armaRoiY(i) = roiY[i];
|
||
|
||
}
|
||
// 3. 执行拟合(曲线自动添加到主图)
|
||
QList<PeakFitResult> results = performPeakFitting(roiX, roiY/*, &userP0*/);
|
||
if (results.isEmpty()) {
|
||
qDebug()<< QStringLiteral(u"拟合结果:") << QStringLiteral(u"未检测到有效峰或拟合失败。");
|
||
} else {
|
||
addSelectionRect(plotRect);
|
||
fadeSelectionRectBorders(); // 框选边框变为虚线(与原逻辑一致)
|
||
if (!_selectionRectItems.isEmpty() && !results.isEmpty())
|
||
{
|
||
PlotRectItem* lastRect = _selectionRectItems.last();
|
||
const PeakFitResult& r = results.first();
|
||
// 传入:峰位、FWHM、面积、本底
|
||
lastRect->setPeakData(r.center, r.fwhm, r.area, r.baseline);
|
||
_plot->replot(); // 刷新显示
|
||
}
|
||
saveCurrentFitToHistory();
|
||
}
|
||
}
|
||
} else {
|
||
qDebug() << QStringLiteral(u"框选区域内数据点不足3个,无法拟合。");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
_isSelecting = false;
|
||
}
|
||
|
||
void EnergyCountPeakFitView::addSelectionRect(const QRectF& plotRect)
|
||
{
|
||
PlotRectItem* rectItem = new PlotRectItem("Selection");
|
||
rectItem->setAxes(QwtPlot::xBottom, QwtPlot::yLeft);
|
||
rectItem->setRect(plotRect);
|
||
// 设置填充为透明(无填充)
|
||
rectItem->setBrush(QBrush(Qt::transparent)); // 或 Qt::NoBrush
|
||
// 设置边框颜色为红色,线宽2
|
||
rectItem->setPen(QPen(Qt::red, 2));
|
||
rectItem->setSelectionType("current");
|
||
rectItem->setSelectionIndex(_selectionRectItems.size());
|
||
rectItem->attach(_plot);
|
||
_selectionRectItems.append(rectItem);
|
||
_plot->replot();
|
||
|
||
}
|
||
|
||
void EnergyCountPeakFitView::fadeSelectionRectBorders()
|
||
{
|
||
for (PlotRectItem* item : _selectionRectItems) {
|
||
if (item) {
|
||
QPen fadedPen(Qt::red, 1, Qt::DashLine); // 可根据原样式调整
|
||
item->setPen(fadedPen);
|
||
}
|
||
}
|
||
_plot->replot();
|
||
}
|
||
|
||
|
||
|
||
QList<PeakFitResult> EnergyCountPeakFitView::performPeakFitting(const QVector<double> &x, const QVector<double> &y)
|
||
{
|
||
QList<PeakFitResult> results;
|
||
if (x.size() != y.size() || x.size() < 3)
|
||
{
|
||
qDebug() << QStringLiteral(u"数据长度不足");
|
||
return results;
|
||
}
|
||
|
||
// 转换为 Armadillo 向量
|
||
arma::vec armaX(x.size()), armaY(y.size());
|
||
for (int i = 0; i < x.size(); ++i) {
|
||
armaX(i) = x[i];
|
||
armaY(i) = y[i];
|
||
}
|
||
arma::vec p0 = EstimatePhotonPeakModelInitialParams(armaX, armaY);
|
||
arma::vec p_fit = NolinearLeastSquaresCurveFit::Lsqcurvefit(PhotonPeakModel, armaX, armaY, p0);
|
||
double sigma = p_fit(1);
|
||
double fwhm = sigma * 2.355;
|
||
|
||
FitParams defaultParams;
|
||
defaultParams.A = p_fit(0);
|
||
defaultParams.delt = p_fit(1);
|
||
defaultParams.H = p_fit(2);
|
||
defaultParams.W = p_fit(3);
|
||
defaultParams.C = p_fit(4);
|
||
defaultParams.P = p_fit(5);
|
||
PeakFitParamsDialog paramDlg(defaultParams, this);
|
||
if (paramDlg.exec() != QDialog::Accepted)
|
||
return results; // 用户取消
|
||
|
||
FitParams userParams = paramDlg.getParams();
|
||
|
||
AdaptiveSimpsonIntegrate::FitFunction fitFunc;
|
||
fitFunc.A = userParams.A ;//p_fit(0);
|
||
fitFunc.delt= userParams.delt;//p_fit(1);
|
||
fitFunc.H = userParams.H ;//p_fit(2);
|
||
fitFunc.W = userParams.W ;//p_fit(3);
|
||
fitFunc.C = userParams.C ;//p_fit(4);
|
||
fitFunc.P = userParams.P ;//p_fit(5);
|
||
double lower = fitFunc.P - 3.0 * fitFunc.delt;
|
||
double upper = fitFunc.P + 3.0 * fitFunc.delt;
|
||
double area = AdaptiveSimpsonIntegrate::integrate(fitFunc, lower, upper);
|
||
|
||
PeakFitResult result;
|
||
result.center = p_fit(5);
|
||
result.amplitude = p_fit(0);
|
||
result.sigma = p_fit(1);
|
||
result.fwhm = fwhm;
|
||
result.area = area;
|
||
result.baseline = p_fit(4);
|
||
result.sigmoidH = p_fit(2);
|
||
result.sigmoidW = p_fit(3);
|
||
|
||
QList<QwtPlotCurve*> curves = createFitCurve(result, armaX);
|
||
for (QwtPlotCurve* c : curves) {
|
||
_fitCurves.append(c);
|
||
}
|
||
|
||
|
||
results.append(result);
|
||
_plot->replot();
|
||
|
||
_lastFitResult = result;
|
||
_lastFitParams = p_fit;
|
||
|
||
_hasLastFit = true;
|
||
|
||
CurrentFitRecord record;
|
||
record.result = result;
|
||
record.params = p_fit;
|
||
record.xMin = armaX.min(); // 记录X范围
|
||
record.xMax = armaX.max();
|
||
_currentFitRecords.append(record); // 加入当前多曲线列表
|
||
return results;
|
||
}
|
||
|
||
QwtPlotCurve* EnergyCountPeakFitView::createFitCurve(const PeakFitResult& result, double xMin, double xMax, const QString& name)
|
||
{
|
||
const int numPoints = 200;
|
||
QVector<double> xs, ys,ysTw;
|
||
double step = (xMax - xMin) / (numPoints - 1);
|
||
|
||
arma::vec p(6);
|
||
p(0) = result.amplitude;
|
||
p(1) = result.sigma;
|
||
p(2) = result.sigmoidH;
|
||
p(3) = result.sigmoidW;
|
||
p(4) = result.baseline;
|
||
p(5) = result.center;
|
||
|
||
for (int i = 0; i < numPoints; ++i) {
|
||
double x = xMin + i * step;
|
||
xs.append(x);
|
||
double y = PhotonPeakModel(x, p);
|
||
ys.append(y);
|
||
}
|
||
|
||
|
||
QwtPlotCurve* curve = new QwtPlotCurve(QStringLiteral(u"拟合数据"));
|
||
curve->setPen(QPen(Qt::blue, 2));
|
||
curve->setSamples(xs, ys);
|
||
curve->attach(_plot);
|
||
return curve;
|
||
}
|
||
|
||
QList<QwtPlotCurve *> EnergyCountPeakFitView::createFitCurve(const PeakFitResult &result, arma::vec xVec)
|
||
{
|
||
QList<QwtPlotCurve*> curveList; // 用于返回
|
||
|
||
QVector<double> xs, ys ,ysTw;
|
||
arma::vec p(6);
|
||
p(0) = result.amplitude;
|
||
p(1) = result.sigma;
|
||
p(2) = result.sigmoidH;
|
||
p(3) = result.sigmoidW;
|
||
p(4) = result.baseline;
|
||
p(5) = result.center;
|
||
for (int i = 0; i < xVec.size(); ++i) {
|
||
double x = xVec.at(i);
|
||
xs.append(x);
|
||
double y = PhotonPeakModel(x, p);
|
||
ys.append(y);
|
||
}
|
||
arma::vec yTw = PhotonPeakModelTuowei(xVec,p);
|
||
for (int i = 0;i<yTw.size();++i)
|
||
{
|
||
double y =yTw.at(i);
|
||
ysTw.append(y);
|
||
}
|
||
|
||
QwtPlotCurve* curve = new QwtPlotCurve(QStringLiteral(u"拟合数据"));
|
||
curve->setPen(QPen(Qt::blue, 2));
|
||
curve->setSamples(xs, ys);
|
||
curve->setItemAttribute(QwtPlotItem::Legend, false);
|
||
curve->attach(_plot);
|
||
curveList.append(curve); // 加入列表
|
||
|
||
QwtPlotCurve* curveTw = new QwtPlotCurve(QStringLiteral(u"本底数据"));
|
||
curveTw->setPen(QPen(Qt::yellow, 2));
|
||
curveTw->setSamples(xs, ysTw);
|
||
curveTw->setItemAttribute(QwtPlotItem::Legend, false);
|
||
curveTw->attach(_plot);
|
||
curveList.append(curveTw); // 加入列表
|
||
|
||
return curveList;
|
||
}
|
||
|
||
|
||
void EnergyCountPeakFitView::clearFitCurves()
|
||
{
|
||
for (QwtPlotCurve* curve : _fitCurves) {
|
||
curve->detach();
|
||
delete curve;
|
||
}
|
||
_fitCurves.clear();
|
||
|
||
_currentFitRecords.clear();
|
||
|
||
_plot->replot();
|
||
}
|
||
|
||
void EnergyCountPeakFitView::loadHistoryFromFile()
|
||
{
|
||
if (!QFileInfo::exists(_historyFilePath))
|
||
return;
|
||
|
||
QFile file(_historyFilePath);
|
||
if (!file.open(QIODevice::ReadOnly))
|
||
return;
|
||
|
||
QByteArray data = file.readAll();
|
||
QJsonDocument doc = QJsonDocument::fromJson(data);
|
||
if (!doc.isArray())
|
||
return;
|
||
|
||
_fitHistoryList.clear();
|
||
QJsonArray arr = doc.array();
|
||
for (const auto& val : arr) {
|
||
if (val.isObject())
|
||
_fitHistoryList.append(PeakFitHistoryItem::fromJson(val.toObject()));
|
||
}
|
||
}
|
||
|
||
void EnergyCountPeakFitView::saveHistoryToFile()
|
||
{
|
||
QJsonArray arr;
|
||
for (const auto& item : _fitHistoryList) {
|
||
arr.append(item.toJson());
|
||
}
|
||
QJsonDocument doc(arr);
|
||
QFile file(_historyFilePath);
|
||
if (file.open(QIODevice::WriteOnly)) {
|
||
file.write(doc.toJson());
|
||
}
|
||
}
|
||
|
||
void EnergyCountPeakFitView::saveCurrentFitToHistory()
|
||
{
|
||
if (_currentFitRecords.isEmpty()) return;
|
||
|
||
//只取最新的一次拟合结果(列表中的最后一条)
|
||
const auto& latestRecord = _currentFitRecords.last();
|
||
int latestRectIndex = _currentFitRecords.size() - 1; // 对应的框选区域索引
|
||
|
||
PeakFitHistoryItem item;
|
||
item.timestamp = QDateTime::currentDateTime();
|
||
|
||
// 仅构建当前这一次的曲线数据
|
||
FitCurveData curve;
|
||
curve.amplitude = latestRecord.params(0);
|
||
curve.sigma = latestRecord.params(1);
|
||
curve.sigmoidH = latestRecord.params(2);
|
||
curve.sigmoidW = latestRecord.params(3);
|
||
curve.baseline = latestRecord.params(4);
|
||
curve.center = latestRecord.params(5);
|
||
curve.xMin = latestRecord.xMin;
|
||
curve.xMax = latestRecord.xMax;
|
||
curve.fwhm = latestRecord.result.fwhm;
|
||
curve.area = latestRecord.result.area;
|
||
|
||
// 关联对应的框选区域(最新的那个)
|
||
if (latestRectIndex < _selectionRectItems.size()) {
|
||
curve.selectionRect = _selectionRectItems[latestRectIndex]->rect();
|
||
}
|
||
item.curveList.append(curve); // 仅添加这一条曲线
|
||
|
||
_fitHistoryList.append(item);
|
||
saveHistoryToFile();
|
||
}
|
||
|
||
void EnergyCountPeakFitView::displayFitFromHistory(const PeakFitHistoryItem& item)
|
||
{
|
||
// 遍历历史中的所有曲线,逐一重建
|
||
for (const auto& curve : item.curveList) {
|
||
// 1. 生成X轴数据
|
||
const int numPoints = 200;
|
||
QVector<double> xs, ys, ysTw;
|
||
double step = (curve.xMax - curve.xMin) / (numPoints - 1);
|
||
|
||
// 2. 构建参数向量
|
||
arma::vec p(6);
|
||
p(0) = curve.amplitude;
|
||
p(1) = curve.sigma;
|
||
p(2) = curve.sigmoidH;
|
||
p(3) = curve.sigmoidW;
|
||
p(4) = curve.baseline;
|
||
p(5) = curve.center;
|
||
|
||
// 3. 生成拟合曲线数据
|
||
for (int i = 0; i < numPoints; ++i) {
|
||
double x = curve.xMin + i * step;
|
||
xs.append(x);
|
||
ys.append(PhotonPeakModel(x, p));
|
||
}
|
||
|
||
// 4. 创建并添加拟合曲线
|
||
QwtPlotCurve* fitCurve = new QwtPlotCurve(QStringLiteral(u"历史拟合数据"));
|
||
fitCurve->setPen(QPen(Qt::blue, 2));
|
||
fitCurve->setSamples(xs, ys);
|
||
fitCurve->setItemAttribute(QwtPlotItem::Legend, false);
|
||
fitCurve->attach(_plot);
|
||
_fitCurves.append(fitCurve);
|
||
|
||
// 5. 生成本底曲线数据
|
||
arma::vec xVec(numPoints);
|
||
for (int i = 0; i < numPoints; ++i) xVec(i) = xs[i];
|
||
arma::vec yTw = PhotonPeakModelTuowei(xVec, p);
|
||
for (int i = 0; i < yTw.size(); ++i) ysTw.append(yTw(i));
|
||
|
||
// 6. 创建并添加本底曲线
|
||
QwtPlotCurve* bgCurve = new QwtPlotCurve(QStringLiteral(u"历史本底数据"));
|
||
bgCurve->setPen(QPen(Qt::yellow, 2, Qt::DashLine));
|
||
bgCurve->setSamples(xs, ysTw);
|
||
bgCurve->setItemAttribute(QwtPlotItem::Legend, false);
|
||
bgCurve->attach(_plot);
|
||
_fitCurves.append(bgCurve);
|
||
// 7. 恢复并绘制框选区域
|
||
if (!curve.selectionRect.isNull()) {
|
||
PlotRectItem* histRect = new PlotRectItem("HistorySelection");
|
||
|
||
//显式绑定坐标轴,确保坐标映射正确
|
||
histRect->setAxes(QwtPlot::xBottom, QwtPlot::yLeft);
|
||
|
||
histRect->setRect(curve.selectionRect);
|
||
histRect->setBrush(QBrush(Qt::transparent));
|
||
// 历史框选区域用红色虚线区分
|
||
histRect->setPen(QPen(Qt::red, 2, Qt::DashLine));
|
||
histRect->setPeakData(curve.center, curve.fwhm, curve.area, curve.baseline);
|
||
|
||
histRect->attach(_plot);
|
||
_selectionRectItems.append(histRect);
|
||
}
|
||
}
|
||
|
||
_plot->replot();
|
||
}
|
||
|
||
void EnergyCountPeakFitView::onActionSaveCurrentFit()
|
||
{
|
||
saveCurrentFitToHistory();
|
||
}
|
||
|
||
void EnergyCountPeakFitView::onActionShowFitHistory()
|
||
{
|
||
bool hasCurveData = false;
|
||
for (const auto& item : _fitHistoryList) {
|
||
if (!item.curveList.isEmpty()) {
|
||
hasCurveData = true;
|
||
break;
|
||
}
|
||
}
|
||
if (!hasCurveData) {
|
||
QMessageBox::information(this,
|
||
QStringLiteral(u"峰拟合结果"),
|
||
QStringLiteral(u"暂无历史拟合数据,请先进行拟合并保存。"));
|
||
return;
|
||
}
|
||
|
||
QDialog dlg(this);
|
||
dlg.setWindowTitle(QStringLiteral(u"峰拟合结果历史"));
|
||
dlg.setMinimumSize(1000, 500);
|
||
QVBoxLayout* layout = new QVBoxLayout(&dlg);
|
||
|
||
QStandardItemModel* model = new QStandardItemModel(&dlg);
|
||
QStringList headers;
|
||
headers << "" << "振幅(A)" << "高斯宽度(delt)" << "Sigmoid幅度(H)" << "Sigmoid宽度(W)" << "基线(C)" << "峰中心(P)";
|
||
model->setHorizontalHeaderLabels(headers);
|
||
|
||
QVector<QPair<int, int>> rowToDataIndices;
|
||
|
||
model->blockSignals(true);
|
||
for (int historyIdx = 0; historyIdx < _fitHistoryList.size(); ++historyIdx) {
|
||
const auto& historyItem = _fitHistoryList[historyIdx];
|
||
for (int curveIdx = 0; curveIdx < historyItem.curveList.size(); ++curveIdx) {
|
||
const auto& curve = historyItem.curveList[curveIdx];
|
||
QList<QStandardItem*> rowItems;
|
||
|
||
QStandardItem* checkItem = new QStandardItem();
|
||
checkItem->setCheckable(true);
|
||
checkItem->setEditable(false);
|
||
bool isCurrentlyDisplayed = false;
|
||
for (const auto& ref : _displayedHistoryCurves) {
|
||
if (ref.historyIndex == historyIdx && ref.curveIndex == curveIdx) {
|
||
isCurrentlyDisplayed = true;
|
||
break;
|
||
}
|
||
}
|
||
checkItem->setCheckState(isCurrentlyDisplayed ? Qt::Checked : Qt::Unchecked);
|
||
rowItems.append(checkItem);
|
||
|
||
// 2. 振幅 (A)
|
||
QStandardItem* itemA = new QStandardItem(QString::number(curve.amplitude, 'f', 4));
|
||
itemA->setEditable(false);
|
||
rowItems.append(itemA);
|
||
|
||
// 3. Sigma (delt)
|
||
QStandardItem* itemDelt = new QStandardItem(QString::number(curve.sigma, 'f', 4));
|
||
itemDelt->setEditable(false);
|
||
rowItems.append(itemDelt);
|
||
|
||
// 4. SigmoidH (H)
|
||
QStandardItem* itemH = new QStandardItem(QString::number(curve.sigmoidH, 'f', 4));
|
||
itemH->setEditable(false);
|
||
rowItems.append(itemH);
|
||
|
||
// 5. SigmoidW (W)
|
||
QStandardItem* itemW = new QStandardItem(QString::number(curve.sigmoidW, 'f', 4));
|
||
itemW->setEditable(false);
|
||
rowItems.append(itemW);
|
||
|
||
// 6. 本底 (C)
|
||
QStandardItem* itemC = new QStandardItem(QString::number(curve.baseline, 'f', 4));
|
||
itemC->setEditable(false);
|
||
rowItems.append(itemC);
|
||
|
||
// 7. 峰位 (P)
|
||
QStandardItem* itemP = new QStandardItem(QString::number(curve.center, 'f', 4));
|
||
itemP->setEditable(false);
|
||
rowItems.append(itemP);
|
||
|
||
model->appendRow(rowItems);
|
||
rowToDataIndices.append(qMakePair(historyIdx, curveIdx));
|
||
}
|
||
}
|
||
model->blockSignals(false);
|
||
|
||
QHBoxLayout* topBtnLayout = new QHBoxLayout();
|
||
QPushButton* btnSelectAll = new QPushButton(QStringLiteral(u"全选"), &dlg);
|
||
QPushButton* btnInvertSelection = new QPushButton(QStringLiteral(u"反选"), &dlg);
|
||
topBtnLayout->addWidget(btnSelectAll);
|
||
topBtnLayout->addWidget(btnInvertSelection);
|
||
topBtnLayout->addStretch();
|
||
layout->addLayout(topBtnLayout);
|
||
|
||
QTableView* tableView = new QTableView(&dlg);
|
||
tableView->setModel(model);
|
||
tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||
tableView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||
tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||
tableView->horizontalHeader()->setStretchLastSection(true);
|
||
tableView->verticalHeader()->setVisible(false);
|
||
tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
|
||
layout->addWidget(tableView);
|
||
|
||
QPushButton* btnDelete = new QPushButton(QStringLiteral(u"删除选中记录"), &dlg);
|
||
QPushButton* btnClose = new QPushButton(QStringLiteral(u"关闭"), &dlg);
|
||
QHBoxLayout* bottomBtnLayout = new QHBoxLayout();
|
||
bottomBtnLayout->addStretch();
|
||
bottomBtnLayout->addWidget(btnDelete);
|
||
bottomBtnLayout->addWidget(btnClose);
|
||
layout->addLayout(bottomBtnLayout);
|
||
|
||
// --- 全选按钮 ---
|
||
connect(btnSelectAll, &QPushButton::clicked, [&]() {
|
||
model->blockSignals(true);
|
||
for (int row = 0; row < model->rowCount(); ++row) {
|
||
model->item(row, 0)->setCheckState(Qt::Checked);
|
||
}
|
||
model->blockSignals(false);
|
||
tableView->viewport()->update();
|
||
|
||
QList<FitCurveData> selectedCurves;
|
||
QList<DisplayedCurveRef> selectedRefs;
|
||
for (int row = 0; row < model->rowCount(); ++row) {
|
||
if (model->item(row, 0)->checkState() == Qt::Checked) {
|
||
QPair<int, int> indices = rowToDataIndices[row];
|
||
selectedCurves.append(_fitHistoryList[indices.first].curveList[indices.second]);
|
||
DisplayedCurveRef ref;
|
||
ref.historyIndex = indices.first;
|
||
ref.curveIndex = indices.second;
|
||
selectedRefs.append(ref);
|
||
}
|
||
}
|
||
displaySelectedCurves(selectedCurves, selectedRefs);
|
||
});
|
||
|
||
// --- 反选按钮 ---
|
||
connect(btnInvertSelection, &QPushButton::clicked, [&]() {
|
||
model->blockSignals(true);
|
||
for (int row = 0; row < model->rowCount(); ++row) {
|
||
QStandardItem* item = model->item(row, 0);
|
||
item->setCheckState(item->checkState() == Qt::Checked ? Qt::Unchecked : Qt::Checked);
|
||
}
|
||
model->blockSignals(false);
|
||
tableView->viewport()->update();
|
||
|
||
QList<FitCurveData> selectedCurves;
|
||
QList<DisplayedCurveRef> selectedRefs;
|
||
for (int row = 0; row < model->rowCount(); ++row) {
|
||
if (model->item(row, 0)->checkState() == Qt::Checked) {
|
||
QPair<int, int> indices = rowToDataIndices[row];
|
||
selectedCurves.append(_fitHistoryList[indices.first].curveList[indices.second]);
|
||
DisplayedCurveRef ref;
|
||
ref.historyIndex = indices.first;
|
||
ref.curveIndex = indices.second;
|
||
selectedRefs.append(ref);
|
||
}
|
||
}
|
||
displaySelectedCurves(selectedCurves, selectedRefs);
|
||
});
|
||
|
||
// --- 复选框状态改变 ---
|
||
connect(model, &QStandardItemModel::itemChanged, [&](QStandardItem* /*item*/) {
|
||
QList<FitCurveData> selectedCurves;
|
||
QList<DisplayedCurveRef> selectedRefs;
|
||
for (int row = 0; row < model->rowCount(); ++row) {
|
||
if (model->item(row, 0)->checkState() == Qt::Checked) {
|
||
QPair<int, int> indices = rowToDataIndices[row];
|
||
selectedCurves.append(_fitHistoryList[indices.first].curveList[indices.second]);
|
||
DisplayedCurveRef ref;
|
||
ref.historyIndex = indices.first;
|
||
ref.curveIndex = indices.second;
|
||
selectedRefs.append(ref);
|
||
}
|
||
}
|
||
displaySelectedCurves(selectedCurves, selectedRefs);
|
||
});
|
||
|
||
// --- 删除选中记录按钮(核心精准删除逻辑) ---
|
||
connect(btnDelete, &QPushButton::clicked, [&]() {
|
||
// 1. 收集要删除的历史索引
|
||
QSet<int> historyIndicesToDelete;
|
||
for (int row = 0; row < model->rowCount(); ++row) {
|
||
if (model->item(row, 0)->checkState() == Qt::Checked) {
|
||
QPair<int, int> indices = rowToDataIndices[row];
|
||
historyIndicesToDelete.insert(indices.first);
|
||
}
|
||
}
|
||
|
||
if (historyIndicesToDelete.isEmpty()) {
|
||
QMessageBox::warning(&dlg, QStringLiteral(u"提示"), QStringLiteral(u"请先勾选要删除的记录。"));
|
||
return;
|
||
}
|
||
|
||
// 2. 确认删除
|
||
QString msg = QStringLiteral(u"确定要删除选中的 %1 条历史记录吗?").arg(historyIndicesToDelete.size());
|
||
QMessageBox::StandardButton reply = QMessageBox::question(&dlg, QStringLiteral(u"确认删除"), msg, QMessageBox::Yes | QMessageBox::No);
|
||
if (reply != QMessageBox::Yes) return;
|
||
|
||
// 3. 先从界面上精准删除显示(从后往前删)
|
||
QList<int> sortedDisplayIndices;
|
||
for (int i = 0; i < _displayedHistoryCurves.size(); ++i) {
|
||
if (historyIndicesToDelete.contains(_displayedHistoryCurves[i].historyIndex)) {
|
||
sortedDisplayIndices.append(i);
|
||
}
|
||
}
|
||
std::sort(sortedDisplayIndices.begin(), sortedDisplayIndices.end(), std::greater<int>());
|
||
|
||
for (int dispIdx : sortedDisplayIndices) {
|
||
const auto& ref = _displayedHistoryCurves[dispIdx];
|
||
|
||
// 删除曲线(先删本底,再删拟合)
|
||
if (ref.curveStartIndex + 1 < _fitCurves.size()) {
|
||
_fitCurves[ref.curveStartIndex + 1]->detach();
|
||
delete _fitCurves[ref.curveStartIndex + 1];
|
||
_fitCurves.removeAt(ref.curveStartIndex + 1);
|
||
|
||
_fitCurves[ref.curveStartIndex]->detach();
|
||
delete _fitCurves[ref.curveStartIndex];
|
||
_fitCurves.removeAt(ref.curveStartIndex);
|
||
}
|
||
|
||
// 删除框选
|
||
if (ref.rectIndex < _selectionRectItems.size()) {
|
||
_selectionRectItems[ref.rectIndex]->detach();
|
||
delete _selectionRectItems[ref.rectIndex];
|
||
_selectionRectItems.removeAt(ref.rectIndex);
|
||
}
|
||
|
||
// 移除引用
|
||
_displayedHistoryCurves.removeAt(dispIdx);
|
||
}
|
||
|
||
// 4. 收集剩余显示的数据(用于重建)
|
||
QList<FitCurveData> remainingCurves;
|
||
QList<DisplayedCurveRef> remainingRefs;
|
||
for (const auto& ref : _displayedHistoryCurves) {
|
||
remainingCurves.append(_fitHistoryList[ref.historyIndex].curveList[ref.curveIndex]);
|
||
remainingRefs.append(ref);
|
||
}
|
||
|
||
// 5. 更新剩余引用的 historyIndex(处理索引偏移)
|
||
QList<int> sortedHistoryIndices = historyIndicesToDelete.values();
|
||
std::sort(sortedHistoryIndices.begin(), sortedHistoryIndices.end(), std::greater<int>());
|
||
|
||
for (auto& ref : remainingRefs) {
|
||
int offset = 0;
|
||
for (int delIdx : sortedHistoryIndices) {
|
||
if (delIdx < ref.historyIndex) offset++;
|
||
}
|
||
ref.historyIndex -= offset;
|
||
}
|
||
|
||
// 6. 删除历史数据
|
||
for (int historyIdx : sortedHistoryIndices) {
|
||
_fitHistoryList.removeAt(historyIdx);
|
||
}
|
||
|
||
// 7. 重建界面显示(确保索引正确)
|
||
clearFitCurves();
|
||
clearAllSelectionRects();
|
||
_displayedHistoryCurves.clear();
|
||
|
||
if (!remainingCurves.isEmpty()) {
|
||
displaySelectedCurves(remainingCurves, remainingRefs);
|
||
} else {
|
||
_plot->replot();
|
||
}
|
||
|
||
// 8. 保存并刷新
|
||
saveHistoryToFile();
|
||
dlg.accept();
|
||
onActionShowFitHistory();
|
||
});
|
||
|
||
connect(btnClose, &QPushButton::clicked, &dlg, &QDialog::accept);
|
||
dlg.exec();
|
||
}
|
||
|
||
void EnergyCountPeakFitView::onActionDeleteHoveredRect()
|
||
{
|
||
if (!_hoveredRectItem) {
|
||
QMessageBox::information(this, QStringLiteral(u"提示"),
|
||
QStringLiteral(u"请先将鼠标移动到要删除的框选区域上(区域边框会变为实线),然后再点击此菜单。"));
|
||
return;
|
||
}
|
||
|
||
//使用新接口获取类型和索引
|
||
QString rectType = _hoveredRectItem->selectionType();
|
||
int rectIndex = _hoveredRectItem->selectionIndex();
|
||
|
||
if (rectType == "current") {
|
||
if (rectIndex < 0 || rectIndex >= _currentFitRecords.size()) {
|
||
QMessageBox::warning(this, QStringLiteral(u"错误"), QStringLiteral(u"框选区域索引无效。"));
|
||
return;
|
||
}
|
||
|
||
int curveStartIdx = rectIndex * 2;
|
||
int historyIndex = _currentFitRecords[rectIndex].historyIndex;
|
||
|
||
// 1. 删除拟合曲线和本底曲线
|
||
if (curveStartIdx + 1 < _fitCurves.size()) {
|
||
_fitCurves[curveStartIdx + 1]->detach();
|
||
delete _fitCurves[curveStartIdx + 1];
|
||
_fitCurves.removeAt(curveStartIdx + 1);
|
||
|
||
_fitCurves[curveStartIdx]->detach();
|
||
delete _fitCurves[curveStartIdx];
|
||
_fitCurves.removeAt(curveStartIdx);
|
||
}
|
||
|
||
// 2. 删除当前拟合记录
|
||
_currentFitRecords.removeAt(rectIndex);
|
||
|
||
// 3. 删除框选区域
|
||
_hoveredRectItem->detach();
|
||
delete _hoveredRectItem;
|
||
_selectionRectItems.removeAt(rectIndex);
|
||
_hoveredRectItem = nullptr;
|
||
|
||
// 4. 删除对应的历史记录
|
||
if (historyIndex >= 0 && historyIndex < _fitHistoryList.size()) {
|
||
_fitHistoryList.removeAt(historyIndex);
|
||
saveHistoryToFile();
|
||
|
||
// 5. 更新剩余当前记录的历史索引
|
||
for (auto& record : _currentFitRecords) {
|
||
if (record.historyIndex > historyIndex) {
|
||
record.historyIndex--;
|
||
}
|
||
}
|
||
|
||
// 6. 更新历史显示记录的索引
|
||
for (auto& ref : _displayedHistoryCurves) {
|
||
if (ref.historyIndex > historyIndex) {
|
||
ref.historyIndex--;
|
||
}
|
||
if (ref.curveStartIndex > curveStartIdx) {
|
||
ref.curveStartIndex -= 2;
|
||
}
|
||
if (ref.rectIndex > rectIndex) {
|
||
ref.rectIndex--;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 7. 更新剩余当前框选的索引
|
||
for (int i = rectIndex; i < _selectionRectItems.size(); ++i) {
|
||
PlotRectItem* item = _selectionRectItems[i];
|
||
if (item->selectionType() == "current") {
|
||
item->setSelectionIndex(i);
|
||
} else if (item->selectionType() == "history") {
|
||
// 更新历史框选的索引
|
||
for (int j = 0; j < _displayedHistoryCurves.size(); ++j) {
|
||
if (_displayedHistoryCurves[j].rectIndex == i) {
|
||
item->setSelectionIndex(j);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
} else if (rectType == "history") {
|
||
if (rectIndex < 0 || rectIndex >= _displayedHistoryCurves.size()) {
|
||
QMessageBox::warning(this, QStringLiteral(u"错误"), QStringLiteral(u"历史框选区域索引无效。"));
|
||
return;
|
||
}
|
||
|
||
const DisplayedCurveRef& ref = _displayedHistoryCurves[rectIndex];
|
||
int historyIndex = ref.historyIndex;
|
||
int curveStartIdx = ref.curveStartIndex;
|
||
int rectIdx = ref.rectIndex;
|
||
|
||
// 1. 删除拟合曲线和本底曲线
|
||
if (curveStartIdx + 1 < _fitCurves.size()) {
|
||
_fitCurves[curveStartIdx + 1]->detach();
|
||
delete _fitCurves[curveStartIdx + 1];
|
||
_fitCurves.removeAt(curveStartIdx + 1);
|
||
|
||
_fitCurves[curveStartIdx]->detach();
|
||
delete _fitCurves[curveStartIdx];
|
||
_fitCurves.removeAt(curveStartIdx);
|
||
}
|
||
|
||
// 2. 删除框选区域
|
||
_hoveredRectItem->detach();
|
||
delete _hoveredRectItem;
|
||
_selectionRectItems.removeAt(rectIdx);
|
||
_hoveredRectItem = nullptr;
|
||
|
||
// 3. 从显示引用列表中移除
|
||
_displayedHistoryCurves.removeAt(rectIndex);
|
||
|
||
// 4. 删除对应的历史记录
|
||
if (historyIndex >= 0 && historyIndex < _fitHistoryList.size()) {
|
||
_fitHistoryList.removeAt(historyIndex);
|
||
saveHistoryToFile();
|
||
|
||
// 5. 更新剩余历史显示记录的索引
|
||
for (auto& r : _displayedHistoryCurves) {
|
||
if (r.historyIndex > historyIndex) {
|
||
r.historyIndex--;
|
||
}
|
||
if (r.curveStartIndex > curveStartIdx) {
|
||
r.curveStartIndex -= 2;
|
||
}
|
||
if (r.rectIndex > rectIdx) {
|
||
r.rectIndex--;
|
||
}
|
||
}
|
||
|
||
// 6. 更新当前记录的历史索引
|
||
for (auto& record : _currentFitRecords) {
|
||
if (record.historyIndex > historyIndex) {
|
||
record.historyIndex--;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 7. 更新剩余历史框选的索引
|
||
for (int i = rectIndex; i < _displayedHistoryCurves.size(); ++i) {
|
||
const DisplayedCurveRef& r = _displayedHistoryCurves[i];
|
||
PlotRectItem* item = _selectionRectItems[r.rectIndex];
|
||
item->setSelectionIndex(i);
|
||
}
|
||
|
||
// 8. 更新当前框选的索引
|
||
for (int i = 0; i < _currentFitRecords.size(); ++i) {
|
||
PlotRectItem* item = _selectionRectItems[i];
|
||
if (item->selectionType() == "current" && item->selectionIndex() > rectIdx) {
|
||
item->setSelectionIndex(item->selectionIndex() - 1);
|
||
}
|
||
}
|
||
}
|
||
|
||
_plot->replot();
|
||
}
|
||
|
||
void EnergyCountPeakFitView::onActionRefitCurrentRect()
|
||
{
|
||
// 检查是否有悬停的框选区域
|
||
if (!_hoveredRectItem) {
|
||
QMessageBox::information(this, QStringLiteral(u"提示"),
|
||
QStringLiteral(u"请先将鼠标移动到要重新拟合的框选区域上(区域边框会变为实线),然后再点击此菜单。"));
|
||
return;
|
||
}
|
||
|
||
// 获取框选类型
|
||
QString rectType = _hoveredRectItem->selectionType();
|
||
int rectIndex = _hoveredRectItem->selectionIndex();
|
||
|
||
if (rectType == "current") {
|
||
int rectIdxInList = _selectionRectItems.indexOf(_hoveredRectItem);
|
||
if (rectIdxInList == -1 || rectIdxInList >= _currentFitRecords.size()) {
|
||
QMessageBox::warning(this, QStringLiteral(u"错误"),
|
||
QStringLiteral(u"未找到对应区域的拟合记录,无法重新拟合。"));
|
||
return;
|
||
}
|
||
|
||
const CurrentFitRecord& originalRecord = _currentFitRecords[rectIdxInList];
|
||
arma::vec originalParams = originalRecord.params;
|
||
|
||
FitParams defaultParams;
|
||
defaultParams.A = originalParams(0);
|
||
defaultParams.delt = originalParams(1);
|
||
defaultParams.H = originalParams(2);
|
||
defaultParams.W = originalParams(3);
|
||
defaultParams.C = originalParams(4);
|
||
defaultParams.P = originalParams(5);
|
||
|
||
PeakFitParamsDialog paramDlg(defaultParams, this);
|
||
if (paramDlg.exec() != QDialog::Accepted) {
|
||
return;
|
||
}
|
||
|
||
FitParams userParams = paramDlg.getParams();
|
||
arma::vec newParams(6);
|
||
newParams(0) = userParams.A;
|
||
newParams(1) = userParams.delt;
|
||
newParams(2) = userParams.H;
|
||
newParams(3) = userParams.W;
|
||
newParams(4) = userParams.C;
|
||
newParams(5) = userParams.P;
|
||
|
||
double newFwhm = newParams(1) * 2.355;
|
||
AdaptiveSimpsonIntegrate::FitFunction fitFunc;
|
||
fitFunc.A = newParams(0);
|
||
fitFunc.delt = newParams(1);
|
||
fitFunc.H = newParams(2);
|
||
fitFunc.W = newParams(3);
|
||
fitFunc.C = newParams(4);
|
||
fitFunc.P = newParams(5);
|
||
double lower = fitFunc.P - 3.0 * fitFunc.delt;
|
||
double upper = fitFunc.P + 3.0 * fitFunc.delt;
|
||
double newArea = AdaptiveSimpsonIntegrate::integrate(fitFunc, lower, upper);
|
||
|
||
PeakFitResult newResult;
|
||
newResult.center = newParams(5);
|
||
newResult.amplitude = newParams(0);
|
||
newResult.sigma = newParams(1);
|
||
newResult.fwhm = newFwhm;
|
||
newResult.area = newArea;
|
||
newResult.baseline = newParams(4);
|
||
newResult.sigmoidH = newParams(2);
|
||
newResult.sigmoidW = newParams(3);
|
||
|
||
int curveStartIdx = rectIdxInList * 2;
|
||
if (curveStartIdx + 1 < _fitCurves.size()) {
|
||
_fitCurves[curveStartIdx]->detach();
|
||
_fitCurves[curveStartIdx + 1]->detach();
|
||
delete _fitCurves[curveStartIdx];
|
||
delete _fitCurves[curveStartIdx + 1];
|
||
_fitCurves.removeAt(curveStartIdx + 1);
|
||
_fitCurves.removeAt(curveStartIdx);
|
||
}
|
||
|
||
arma::vec xVec;
|
||
QList<QwtPlotCurve*> curves = _plot->GetCurveList();
|
||
if (!curves.isEmpty()) {
|
||
QwtPlotCurve* originalCurve = curves.first();
|
||
QVector<double> origX;
|
||
for (size_t i = 0; i < originalCurve->dataSize(); ++i) {
|
||
origX.push_back(originalCurve->sample(i).x());
|
||
}
|
||
QVector<double> roiX;
|
||
for (int i = 0; i < origX.size(); ++i) {
|
||
if (origX[i] >= originalRecord.xMin && origX[i] <= originalRecord.xMax) {
|
||
roiX.push_back(origX[i]);
|
||
}
|
||
}
|
||
xVec.set_size(roiX.size());
|
||
for (int i = 0; i < roiX.size(); ++i) {
|
||
xVec(i) = roiX[i];
|
||
}
|
||
}
|
||
|
||
QList<QwtPlotCurve*> newCurves = createFitCurve(newResult, xVec);
|
||
for (int i = 0; i < newCurves.size(); ++i) {
|
||
_fitCurves.insert(curveStartIdx + i, newCurves[i]);
|
||
}
|
||
|
||
_hoveredRectItem->setPeakData(newResult.center, newResult.fwhm, newResult.area, newResult.baseline);
|
||
|
||
CurrentFitRecord newRecord;
|
||
newRecord.result = newResult;
|
||
newRecord.params = newParams;
|
||
newRecord.xMin = originalRecord.xMin;
|
||
newRecord.xMax = originalRecord.xMax;
|
||
newRecord.historyIndex = originalRecord.historyIndex; // 保留历史索引
|
||
_currentFitRecords.replace(rectIdxInList, newRecord);
|
||
|
||
_plot->replot();
|
||
|
||
} else if (rectType == "history") {
|
||
if (rectIndex < 0 || rectIndex >= _displayedHistoryCurves.size()) {
|
||
QMessageBox::warning(this, QStringLiteral(u"错误"),
|
||
QStringLiteral(u"未找到对应历史区域的拟合记录,无法重新拟合。"));
|
||
return;
|
||
}
|
||
|
||
// 1. 获取历史数据引用
|
||
const DisplayedCurveRef& ref = _displayedHistoryCurves[rectIndex];
|
||
int historyIdx = ref.historyIndex;
|
||
int curveIdxInHistory = ref.curveIndex;
|
||
int curveStartIdx = ref.curveStartIndex;
|
||
int rectIdxInList = ref.rectIndex;
|
||
|
||
if (historyIdx < 0 || historyIdx >= _fitHistoryList.size()) return;
|
||
if (curveIdxInHistory < 0 || curveIdxInHistory >= _fitHistoryList[historyIdx].curveList.size()) return;
|
||
|
||
// 2. 从历史记录中提取原始参数
|
||
const FitCurveData& originalCurveData = _fitHistoryList[historyIdx].curveList[curveIdxInHistory];
|
||
|
||
FitParams defaultParams;
|
||
defaultParams.A = originalCurveData.amplitude;
|
||
defaultParams.delt = originalCurveData.sigma;
|
||
defaultParams.H = originalCurveData.sigmoidH;
|
||
defaultParams.W = originalCurveData.sigmoidW;
|
||
defaultParams.C = originalCurveData.baseline;
|
||
defaultParams.P = originalCurveData.center;
|
||
|
||
// 3. 弹出参数编辑对话框
|
||
PeakFitParamsDialog paramDlg(defaultParams, this);
|
||
if (paramDlg.exec() != QDialog::Accepted) {
|
||
return;
|
||
}
|
||
|
||
// 4. 获取用户修改后的参数
|
||
FitParams userParams = paramDlg.getParams();
|
||
arma::vec newParams(6);
|
||
newParams(0) = userParams.A;
|
||
newParams(1) = userParams.delt;
|
||
newParams(2) = userParams.H;
|
||
newParams(3) = userParams.W;
|
||
newParams(4) = userParams.C;
|
||
newParams(5) = userParams.P;
|
||
|
||
// 5. 计算新的 FWHM 和峰面积
|
||
double newFwhm = newParams(1) * 2.355;
|
||
AdaptiveSimpsonIntegrate::FitFunction fitFunc;
|
||
fitFunc.A = newParams(0);
|
||
fitFunc.delt = newParams(1);
|
||
fitFunc.H = newParams(2);
|
||
fitFunc.W = newParams(3);
|
||
fitFunc.C = newParams(4);
|
||
fitFunc.P = newParams(5);
|
||
double lower = fitFunc.P - 3.0 * fitFunc.delt;
|
||
double upper = fitFunc.P + 3.0 * fitFunc.delt;
|
||
double newArea = AdaptiveSimpsonIntegrate::integrate(fitFunc, lower, upper);
|
||
|
||
// 6. 构建新的结果结构体
|
||
PeakFitResult newResult;
|
||
newResult.center = newParams(5);
|
||
newResult.amplitude = newParams(0);
|
||
newResult.sigma = newParams(1);
|
||
newResult.fwhm = newFwhm;
|
||
newResult.area = newArea;
|
||
newResult.baseline = newParams(4);
|
||
newResult.sigmoidH = newParams(2);
|
||
newResult.sigmoidW = newParams(3);
|
||
|
||
// 7. 删除旧的拟合曲线和本底曲线
|
||
if (curveStartIdx + 1 < _fitCurves.size()) {
|
||
_fitCurves[curveStartIdx]->detach();
|
||
_fitCurves[curveStartIdx + 1]->detach();
|
||
delete _fitCurves[curveStartIdx];
|
||
delete _fitCurves[curveStartIdx + 1];
|
||
_fitCurves.removeAt(curveStartIdx + 1);
|
||
_fitCurves.removeAt(curveStartIdx);
|
||
}
|
||
|
||
// 8. 从原始数据中提取该区域的 X 值(用于重新生成曲线)
|
||
arma::vec xVec;
|
||
QList<QwtPlotCurve*> curves = _plot->GetCurveList();
|
||
if (!curves.isEmpty()) {
|
||
QwtPlotCurve* originalCurve = curves.first();
|
||
QVector<double> origX;
|
||
for (size_t i = 0; i < originalCurve->dataSize(); ++i) {
|
||
origX.push_back(originalCurve->sample(i).x());
|
||
}
|
||
QVector<double> roiX;
|
||
for (int i = 0; i < origX.size(); ++i) {
|
||
if (origX[i] >= originalCurveData.xMin && origX[i] <= originalCurveData.xMax) {
|
||
roiX.push_back(origX[i]);
|
||
}
|
||
}
|
||
xVec.set_size(roiX.size());
|
||
for (int i = 0; i < roiX.size(); ++i) {
|
||
xVec(i) = roiX[i];
|
||
}
|
||
}
|
||
|
||
// 9. 生成新的拟合曲线并插入到原位置
|
||
QList<QwtPlotCurve*> newCurves = createFitCurve(newResult, xVec);
|
||
for (int i = 0; i < newCurves.size(); ++i) {
|
||
_fitCurves.insert(curveStartIdx + i, newCurves[i]);
|
||
}
|
||
|
||
// 10. 更新框选区域的峰数据标签
|
||
_hoveredRectItem->setPeakData(newResult.center, newResult.fwhm, newResult.area, newResult.baseline);
|
||
|
||
// 11. 更新内存中的历史数据
|
||
FitCurveData newCurveData = originalCurveData; // 复制旧数据
|
||
newCurveData.amplitude = newParams(0);
|
||
newCurveData.sigma = newParams(1);
|
||
newCurveData.sigmoidH = newParams(2);
|
||
newCurveData.sigmoidW = newParams(3);
|
||
newCurveData.baseline = newParams(4);
|
||
newCurveData.center = newParams(5);
|
||
newCurveData.fwhm = newFwhm;
|
||
newCurveData.area = newArea;
|
||
// 替换历史列表中的数据
|
||
_fitHistoryList[historyIdx].curveList.replace(curveIdxInHistory, newCurveData);
|
||
|
||
// 12. 自动保存到文件
|
||
saveHistoryToFile();
|
||
|
||
// 13. 重绘图表
|
||
_plot->replot();
|
||
|
||
QMessageBox::information(this, QStringLiteral(u"提示"), QStringLiteral(u"重新拟合成功,历史数据已更新。"));
|
||
}
|
||
}
|
||
|
||
//检测鼠标是否悬停在框选区域上
|
||
void EnergyCountPeakFitView::updateHoverState(const QPoint& mousePos) {
|
||
// 将鼠标像素坐标转换为 plot 坐标
|
||
const QwtScaleMap xMap = _plot->canvasMap(QwtPlot::xBottom);
|
||
const QwtScaleMap yMap = _plot->canvasMap(QwtPlot::yLeft);
|
||
const double x = xMap.invTransform(mousePos.x());
|
||
const double y = yMap.invTransform(mousePos.y());
|
||
const QPointF plotPos(x, y);
|
||
|
||
PlotRectItem* newHoveredItem = nullptr;
|
||
|
||
// 倒序遍历(优先检测最上层的区域)
|
||
for (auto it = _selectionRectItems.rbegin(); it != _selectionRectItems.rend(); ++it) {
|
||
PlotRectItem* item = *it;
|
||
if (item->rect().contains(plotPos)) {
|
||
newHoveredItem = item;
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 更新悬停状态
|
||
if (newHoveredItem != _hoveredRectItem) {
|
||
// 取消之前的悬停
|
||
if (_hoveredRectItem) {
|
||
_hoveredRectItem->setHovered(false);
|
||
}
|
||
// 设置新的悬停
|
||
_hoveredRectItem = newHoveredItem;
|
||
if (_hoveredRectItem) {
|
||
_hoveredRectItem->setHovered(true);
|
||
}
|
||
// 重绘
|
||
_plot->replot();
|
||
}
|
||
}
|
||
|
||
void EnergyCountPeakFitView::displaySelectedCurves(const QList<FitCurveData> &curves, const QList<DisplayedCurveRef>& refs)
|
||
{
|
||
// 先清除之前的拟合曲线和框选区域
|
||
clearFitCurves();
|
||
clearAllSelectionRects();
|
||
|
||
//重建显示引用列表,记录索引
|
||
_displayedHistoryCurves.clear();
|
||
|
||
// 遍历显示曲线
|
||
for (int i = 0; i < curves.size(); ++i) {
|
||
const auto& curve = curves[i];
|
||
const auto& ref = refs[i];
|
||
|
||
// 1. 生成X轴数据
|
||
const int numPoints = 200;
|
||
QVector<double> xs, ys, ysTw;
|
||
double step = (curve.xMax - curve.xMin) / (numPoints - 1);
|
||
|
||
// 2. 构建参数向量
|
||
arma::vec p(6);
|
||
p(0) = curve.amplitude;
|
||
p(1) = curve.sigma;
|
||
p(2) = curve.sigmoidH;
|
||
p(3) = curve.sigmoidW;
|
||
p(4) = curve.baseline;
|
||
p(5) = curve.center;
|
||
|
||
// 3. 生成拟合曲线数据
|
||
for (int j = 0; j < numPoints; ++j) {
|
||
double x = curve.xMin + j * step;
|
||
xs.append(x);
|
||
ys.append(PhotonPeakModel(x, p));
|
||
}
|
||
|
||
//记录当前曲线和框选的起始索引
|
||
DisplayedCurveRef newRef;
|
||
newRef.historyIndex = ref.historyIndex;
|
||
newRef.curveIndex = ref.curveIndex;
|
||
newRef.curveStartIndex = _fitCurves.size();
|
||
newRef.rectIndex = _selectionRectItems.size();
|
||
|
||
// 4. 创建并添加拟合曲线
|
||
QwtPlotCurve* fitCurve = new QwtPlotCurve(QStringLiteral(u"历史拟合数据"));
|
||
fitCurve->setPen(QPen(Qt::blue, 2));
|
||
fitCurve->setSamples(xs, ys);
|
||
fitCurve->setItemAttribute(QwtPlotItem::Legend, false);
|
||
fitCurve->attach(_plot);
|
||
_fitCurves.append(fitCurve);
|
||
|
||
// 5. 生成本底曲线数据
|
||
arma::vec xVec(numPoints);
|
||
for (int j = 0; j < numPoints; ++j) xVec(j) = xs[j];
|
||
arma::vec yTw = PhotonPeakModelTuowei(xVec, p);
|
||
for (int j = 0; j < yTw.size(); ++j) ysTw.append(yTw(j));
|
||
|
||
// 6. 创建并添加本底曲线
|
||
QwtPlotCurve* bgCurve = new QwtPlotCurve(QStringLiteral(u"历史本底数据"));
|
||
bgCurve->setPen(QPen(Qt::yellow, 2, Qt::DashLine));
|
||
bgCurve->setSamples(xs, ysTw);
|
||
bgCurve->setItemAttribute(QwtPlotItem::Legend, false);
|
||
bgCurve->attach(_plot);
|
||
_fitCurves.append(bgCurve);
|
||
|
||
// 7. 恢复并绘制框选区域
|
||
if (!curve.selectionRect.isNull()) {
|
||
PlotRectItem* histRect = new PlotRectItem("HistorySelection");
|
||
histRect->setAxes(QwtPlot::xBottom, QwtPlot::yLeft);
|
||
histRect->setRect(curve.selectionRect);
|
||
histRect->setBrush(QBrush(Qt::transparent));
|
||
histRect->setPen(QPen(Qt::red, 2, Qt::DashLine));
|
||
histRect->setPeakData(curve.center, curve.fwhm, curve.area, curve.baseline);
|
||
|
||
histRect->setSelectionType("history");
|
||
histRect->setSelectionIndex(_displayedHistoryCurves.size());
|
||
|
||
histRect->attach(_plot);
|
||
_selectionRectItems.append(histRect);
|
||
}
|
||
|
||
// 8. 将记录了索引的 newRef 加入列表
|
||
_displayedHistoryCurves.append(newRef);
|
||
}
|
||
_plot->replot();
|
||
}
|