1016 lines
33 KiB
C++
1016 lines
33 KiB
C++
#include "NuclideAnalysisView.h"
|
||
#include "CustomQwtPlot.h"
|
||
#include "csv.h"
|
||
#include <GlobalDefine.h>
|
||
#include <QFileInfo>
|
||
#include <QwtPlotCurve>
|
||
#include <QHBoxLayout>
|
||
#include "MeasureAnalysisProjectModel.h"
|
||
#include <QDir>
|
||
#include <QwtPlotCanvas>
|
||
#include <QwtText>
|
||
#include <QPen>
|
||
#include <QJsonDocument>
|
||
#include <QJsonObject>
|
||
#include <QJsonArray>
|
||
#include <QMessageBox>
|
||
#include <QHeaderView>
|
||
#include "AddNuclideDialog.h"
|
||
// 更新选择框显示
|
||
static void updateSelectionMarkers(QwtPlot* plot,
|
||
QwtPlotMarker*& leftMarker,
|
||
QwtPlotMarker*& rightMarker,
|
||
double x1, double x2)
|
||
{
|
||
double x_left = qMin(x1, x2);
|
||
double x_right = qMax(x1, x2);
|
||
|
||
// 左边界
|
||
if (!leftMarker) {
|
||
leftMarker = new QwtPlotMarker();
|
||
leftMarker->setLineStyle(QwtPlotMarker::VLine);
|
||
leftMarker->setLinePen(QPen(Qt::blue, 1, Qt::DashLine));
|
||
leftMarker->attach(plot);
|
||
}
|
||
leftMarker->setValue(x_left, 0);
|
||
|
||
// 右边界
|
||
if (!rightMarker) {
|
||
rightMarker = new QwtPlotMarker();
|
||
rightMarker->setLineStyle(QwtPlotMarker::VLine);
|
||
rightMarker->setLinePen(QPen(Qt::blue, 1, Qt::DashLine));
|
||
rightMarker->attach(plot);
|
||
}
|
||
rightMarker->setValue(x_right, 0);
|
||
|
||
plot->replot();
|
||
}
|
||
|
||
// 清除选择框
|
||
static void clearSelectionMarkers(QwtPlotMarker*& leftMarker,
|
||
QwtPlotMarker*& rightMarker)
|
||
{
|
||
if (leftMarker) {
|
||
leftMarker->detach();
|
||
delete leftMarker;
|
||
leftMarker = nullptr;
|
||
}
|
||
if (rightMarker) {
|
||
rightMarker->detach();
|
||
delete rightMarker;
|
||
rightMarker = nullptr;
|
||
}
|
||
}
|
||
|
||
NuclideAnalysisView::NuclideAnalysisView(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();
|
||
}
|
||
|
||
NuclideAnalysisView::~NuclideAnalysisView()
|
||
{
|
||
LOG_DEBUG(QStringLiteral(u"%1析构.").arg(this->GetViewName()));
|
||
}
|
||
|
||
void NuclideAnalysisView::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);
|
||
}
|
||
|
||
void NuclideAnalysisView::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);
|
||
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
bool NuclideAnalysisView::eventFilter(QObject *obj, QEvent *event)
|
||
{
|
||
if (obj == _plot->canvas()) {
|
||
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
|
||
|
||
// 鼠标按下:记录起始位置
|
||
if (event->type() == QEvent::MouseButtonPress) {
|
||
if (mouseEvent->button() == Qt::LeftButton) {
|
||
m_bSelecting = true;
|
||
m_selectStartX = _plot->invTransform(QwtPlot::xBottom, mouseEvent->pos().x());
|
||
m_selectStartPos = mouseEvent->pos();
|
||
updateSelectionMarkers(_plot, m_selectLeftMarker, m_selectRightMarker,
|
||
m_selectStartX, m_selectStartX);
|
||
return true;
|
||
}
|
||
}
|
||
|
||
// 鼠标移动:更新选择框
|
||
if (event->type() == QEvent::MouseMove) {
|
||
if (m_bSelecting) {
|
||
double xValue = _plot->invTransform(QwtPlot::xBottom, mouseEvent->pos().x());
|
||
updateSelectionMarkers(_plot, m_selectLeftMarker, m_selectRightMarker,
|
||
m_selectStartX, xValue);
|
||
return true;
|
||
}
|
||
}
|
||
|
||
// 鼠标释放
|
||
if (event->type() == QEvent::MouseButtonRelease) {
|
||
if (mouseEvent->button() == Qt::LeftButton && m_bSelecting) {
|
||
m_bSelecting = false;
|
||
|
||
double xEnd = _plot->invTransform(QwtPlot::xBottom, mouseEvent->pos().x());
|
||
int dx = mouseEvent->pos().x() - m_selectStartPos.x();
|
||
int dy = mouseEvent->pos().y() - m_selectStartPos.y();
|
||
double moveDistance = sqrt(dx*dx + dy*dy);
|
||
|
||
// 清除选择框
|
||
clearSelectionMarkers(m_selectLeftMarker, m_selectRightMarker);
|
||
_plot->replot();
|
||
if (moveDistance > 10)
|
||
{
|
||
double x1 = qMin(m_selectStartX, xEnd);
|
||
double x2 = qMax(m_selectStartX, xEnd);
|
||
|
||
if (x2 - x1 > 1.0 && HasSpectrumData())
|
||
{
|
||
PeakFitResultNuclide result = FitPeakByRange(x1, x2, m_spectrumX, m_spectrumY);
|
||
if (result.success)
|
||
{
|
||
// 追加到累积结果
|
||
m_allFitResults.append(result);
|
||
DrawPeaks(m_allFitResults);
|
||
queryNuclidesForPeak(result);
|
||
}
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
|
||
return MeasureAnalysisView::eventFilter(obj, event);
|
||
}
|
||
|
||
void NuclideAnalysisView::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);
|
||
// 设置轴自动缩放
|
||
_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);
|
||
// 给canvas安装事件过滤器,捕获鼠标点击
|
||
_plot->canvas()->installEventFilter(this);
|
||
}
|
||
|
||
void NuclideAnalysisView::setupMenu()
|
||
{
|
||
this->setContextMenuPolicy(Qt::CustomContextMenu);
|
||
connect(this, &NuclideAnalysisView::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_plot_config = this->_menu->addAction(QStringLiteral(u"图表配置"));
|
||
action_plot_config->setObjectName("plot_config");
|
||
connect(action_plot_config, &QAction::triggered, this, &NuclideAnalysisView::onActionPlotConfigure);
|
||
this->_menu->addSeparator();
|
||
|
||
// QAction* action_plot_select = this->_menu->addAction(QStringLiteral(u"在谱线上选择"));
|
||
// action_plot_select->setObjectName("plot_select");
|
||
// connect(action_plot_select, &QAction::triggered, this, &NuclideAnalysisView::onActionPlotSelect);
|
||
// this->_menu->addSeparator();
|
||
|
||
QAction* action_plot_clear = this->_menu->addAction(QStringLiteral(u"清除"));
|
||
action_plot_clear->setObjectName("plot_clear");
|
||
connect(action_plot_clear, &QAction::triggered, this, &NuclideAnalysisView::onActionPlotClear);
|
||
this->_menu->addSeparator();
|
||
|
||
setupNuclideDialog();
|
||
|
||
}
|
||
|
||
void NuclideAnalysisView::loadDataFromFile(const QString& data_name, const QString& filename)
|
||
{
|
||
// 加载新数据前清空旧数据
|
||
_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);
|
||
if(_x_max < energy)
|
||
_x_max = energy;
|
||
if(_y_max < energy_count)
|
||
_y_max = energy_count;
|
||
}
|
||
|
||
_plot->SetAxisInitRange(QwtPlot::xBottom,0.0f,_x_max);
|
||
_plot->SetAxisInitRange(QwtPlot::yLeft,0.0f,_y_max);
|
||
|
||
// 绘制原始数据曲线
|
||
QwtPlotCurve* curve = new QwtPlotCurve(QStringLiteral(u"原始数据"));
|
||
curve->setPen(QPen(Qt::gray, 2)); // 原始数据统一灰色
|
||
curve->setSamples(x, y);
|
||
_plot->AddCurve(curve,false);
|
||
m_spectrumX = x;
|
||
m_spectrumY = y;
|
||
// QVector<PeakFitResultNuclide> results =FitThreePeaksFromSpectrum(x,y);
|
||
// DrawPeaks(results,y);
|
||
}
|
||
|
||
void NuclideAnalysisView::onActionPlotConfigure()
|
||
{
|
||
|
||
}
|
||
|
||
void NuclideAnalysisView::onActionPlotSelect()
|
||
{
|
||
m_bSelectHS = true;
|
||
}
|
||
|
||
void NuclideAnalysisView::onActionPlotClear()
|
||
{
|
||
if (!_plot) {
|
||
return;
|
||
}
|
||
|
||
for (int i = 0; i < m_peakMarkers.size(); ++i) {
|
||
if (m_peakMarkers[i]) {
|
||
m_peakMarkers[i]->detach();
|
||
delete m_peakMarkers[i];
|
||
}
|
||
}
|
||
m_peakMarkers.clear();
|
||
|
||
if (_vLineMarker) {
|
||
_vLineMarker->detach();
|
||
delete _vLineMarker;
|
||
_vLineMarker = nullptr;
|
||
}
|
||
|
||
if (m_selectLeftMarker) {
|
||
m_selectLeftMarker->detach();
|
||
delete m_selectLeftMarker;
|
||
m_selectLeftMarker = nullptr;
|
||
}
|
||
if (m_selectRightMarker) {
|
||
m_selectRightMarker->detach();
|
||
delete m_selectRightMarker;
|
||
m_selectRightMarker = nullptr;
|
||
}
|
||
|
||
for (QwtPlotMarker* marker : m_selectedNuclideMarkers) {
|
||
if (marker) {
|
||
marker->detach();
|
||
delete marker;
|
||
}
|
||
}
|
||
m_selectedNuclideMarkers.clear();
|
||
|
||
m_allFitResults.clear();
|
||
|
||
m_nuclideTable->setRowCount(0);
|
||
|
||
m_bSelecting = false;
|
||
m_bSelectHS = false;
|
||
|
||
_plot->replot();
|
||
}
|
||
|
||
|
||
void NuclideAnalysisView::addVerticalLine(double xValue, const QColor& color)
|
||
{
|
||
if(!m_bSelectHS)
|
||
return;
|
||
m_bSelectHS = false;
|
||
if (!_vLineMarker) {
|
||
_vLineMarker = new QwtPlotMarker();
|
||
_vLineMarker->setLineStyle(QwtPlotMarker::VLine);
|
||
_vLineMarker->setLinePen(QPen(color, 1, Qt::SolidLine));
|
||
_vLineMarker->attach(_plot);
|
||
} else {
|
||
_vLineMarker->setLinePen(QPen(color, 1, Qt::SolidLine));
|
||
}
|
||
_vLineMarker->setValue(xValue, 0);
|
||
_plot->replot();
|
||
}
|
||
|
||
// 用光子峰模型拟合并计算单个峰的半高宽
|
||
PeakFitResultNuclide NuclideAnalysisView::FitPhotonPeakCalcFwhm(const QVector<double>& x, const QVector<double>& y)
|
||
{
|
||
PeakFitResultNuclide result;
|
||
result.success = false;
|
||
result.center = 0.0;
|
||
result.amplitude = 0.0;
|
||
result.sigma = 0.0;
|
||
result.fwhm = 0.0;
|
||
result.area = 0.0;
|
||
result.baseline = 0.0;
|
||
result.sigmoidH = 0.0;
|
||
result.sigmoidW = 0.0;
|
||
|
||
// 参数校验
|
||
if (x.size() < 6 || y.size() < 6 || x.size() != y.size()) {
|
||
return result;
|
||
}
|
||
|
||
int n = x.size();
|
||
arma::vec arma_x(n);
|
||
arma::vec arma_y(n);
|
||
for (int i = 0; i < n; ++i) {
|
||
arma_x(i) = x[i];
|
||
arma_y(i) = y[i];
|
||
}
|
||
|
||
arma::vec p0 = EstimatePhotonPeakModelInitialParams(arma_x, arma_y);
|
||
|
||
arma::vec p_fit = NolinearLeastSquaresCurveFit::Lsqcurvefit(PhotonPeakModel, arma_x, arma_y, p0);
|
||
|
||
if (p_fit.n_elem >= 6) {
|
||
double A = p_fit(0); // 高斯振幅
|
||
double delt = p_fit(1); // 高斯宽度(sigma)
|
||
double H = p_fit(2); // Sigmoid 高度
|
||
double W = p_fit(3); // Sigmoid 宽度
|
||
double C = p_fit(4); // 常数本底
|
||
double P = p_fit(5); // 峰位
|
||
|
||
double fwhm = delt * 2.355;
|
||
|
||
double area = A * delt * 2.5066; // sqrt(2π) ≈ 2.5066
|
||
|
||
result.success = true;
|
||
result.center = P;
|
||
result.amplitude = A;
|
||
result.sigma = delt;
|
||
result.fwhm = fwhm;
|
||
result.area = area;
|
||
result.baseline = C;
|
||
result.sigmoidH = H;
|
||
result.sigmoidW = W;
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
// 根据x范围截取数据并拟合计FWHM
|
||
PeakFitResultNuclide NuclideAnalysisView::FitPeakByRange(double x_start, double x_end,
|
||
const QVector<double>& x, const QVector<double>& y)
|
||
{
|
||
PeakFitResultNuclide result;
|
||
result.success = false;
|
||
|
||
if (x.size() != y.size() || x_start >= x_end) {
|
||
return result;
|
||
}
|
||
|
||
// 截取指定范围内的数据
|
||
QVector<double> x_peak, y_peak;
|
||
for (int i = 0; i < x.size(); ++i) {
|
||
if (x[i] >= x_start && x[i] <= x_end) {
|
||
x_peak.append(x[i]);
|
||
y_peak.append(y[i]);
|
||
}
|
||
}
|
||
|
||
// 调用拟合函数
|
||
result = FitPhotonPeakCalcFwhm(x_peak, y_peak);
|
||
|
||
return result;
|
||
}
|
||
|
||
// 从整张谱中自动寻找三个最强的峰并计算FWHM(SVD寻峰)
|
||
QVector<PeakFitResultNuclide> NuclideAnalysisView::FitThreePeaksFromSpectrum(
|
||
const QVector<double>& x,
|
||
const QVector<double>& y,
|
||
double height_threshold,
|
||
int min_distance)
|
||
{
|
||
QVector<PeakFitResultNuclide> results;
|
||
|
||
if (x.size() < 10 || y.size() < 10 || x.size() != y.size()) {
|
||
return results;
|
||
}
|
||
|
||
// 将 QVector 转换为 arma::mat(FindPeaksBySvd 需要矩阵格式)
|
||
int n = x.size();
|
||
arma::mat spec_data(n, 2);
|
||
for (int i = 0; i < n; ++i) {
|
||
spec_data(i, 0) = x[i]; // 第一列:x(能量/道址)
|
||
spec_data(i, 1) = y[i]; // 第二列:y(计数)
|
||
}
|
||
|
||
FindPeaksBySvd peakFinder;
|
||
std::vector<FindPeaksBySvd::PeakInfo> peaks;
|
||
try {
|
||
peaks = peakFinder.FindPeaks(spec_data, 40); // step_w = 7
|
||
} catch (const std::string& e) {
|
||
return results;
|
||
}
|
||
|
||
if (peaks.empty()) {
|
||
return results;
|
||
}
|
||
|
||
// std::sort(peaks.begin(), peaks.end(),
|
||
// [](const FindPeaksBySvd::PeakInfo& a, const FindPeaksBySvd::PeakInfo& b) {
|
||
// return a.height > b.height;
|
||
// });
|
||
|
||
// if (peaks.size() > 3) {
|
||
// peaks.resize(3);
|
||
// }
|
||
|
||
std::sort(peaks.begin(), peaks.end(),
|
||
[](const FindPeaksBySvd::PeakInfo& a, const FindPeaksBySvd::PeakInfo& b) {
|
||
return a.pos < b.pos;
|
||
});
|
||
|
||
for (size_t i = 0; i < peaks.size(); ++i) {
|
||
const FindPeaksBySvd::PeakInfo& peak = peaks[i];
|
||
|
||
double peak_pos = peak.pos;
|
||
double window = peak.width;
|
||
if (window < 10.0) window = 10.0;
|
||
|
||
double x_start = peak_pos - window;
|
||
double x_end = peak_pos + window;
|
||
|
||
if (x_start < x.first()) x_start = x.first();
|
||
if (x_end > x.last()) x_end = x.last();
|
||
|
||
QVector<double> x_peak, y_peak;
|
||
for (int j = 0; j < x.size(); ++j) {
|
||
if (x[j] >= x_start && x[j] <= x_end) {
|
||
x_peak.append(x[j]);
|
||
y_peak.append(y[j]);
|
||
}
|
||
}
|
||
|
||
PeakFitResultNuclide result = FitPhotonPeakCalcFwhm(x_peak, y_peak);
|
||
results.append(result);
|
||
}
|
||
|
||
return results;
|
||
}
|
||
|
||
// 绘制检测到的峰(矩形表示峰位和峰宽)
|
||
void NuclideAnalysisView::DrawPeaks(const QVector<PeakFitResultNuclide>& peaks)
|
||
{
|
||
if (!_plot || peaks.isEmpty()) {
|
||
return;
|
||
}
|
||
|
||
// 先清除之前的标记
|
||
ClearPeakMarkers();
|
||
|
||
// 获取Y轴最大值,用于放置标签
|
||
double y_max = 0;
|
||
if (!m_spectrumY.isEmpty()) {
|
||
y_max = *std::max_element(m_spectrumY.begin(), m_spectrumY.end());
|
||
}
|
||
|
||
// 为每个峰创建矩形标记
|
||
for (int i = 0; i < peaks.size(); ++i) {
|
||
if (!peaks[i].success) {
|
||
continue;
|
||
}
|
||
|
||
double center = peaks[i].center;
|
||
double fwhm = peaks[i].fwhm;
|
||
double amplitude = peaks[i].amplitude;
|
||
double baseline = peaks[i].baseline;
|
||
|
||
QColor color = Qt::red;
|
||
|
||
|
||
// 半透明填充色
|
||
QColor fillColor = color;
|
||
fillColor.setAlpha(5); // 透明度 0-255
|
||
|
||
// 1. 矩形(表示峰位和峰宽)
|
||
// 矩形范围:x = [center - fwhm/2, center + fwhm/2]
|
||
// y = [baseline, baseline + amplitude]
|
||
double x_left = center - fwhm / 2.0;
|
||
double x_right = center + fwhm / 2.0;
|
||
double y_bottom = 0.0;
|
||
double y_top = y_max;
|
||
|
||
// 用 QwtPlotCurve 绘制闭合矩形
|
||
QPolygonF rectPoints;
|
||
rectPoints << QPointF(x_left, y_bottom);
|
||
rectPoints << QPointF(x_right, y_bottom);
|
||
rectPoints << QPointF(x_right, y_top);
|
||
rectPoints << QPointF(x_left, y_top);
|
||
rectPoints << QPointF(x_left, y_bottom); // 闭合
|
||
|
||
QwtPlotCurve* rectCurve = new QwtPlotCurve();
|
||
rectCurve->setSamples(rectPoints);
|
||
rectCurve->setPen(QPen(color, 1, Qt::SolidLine));
|
||
rectCurve->setBrush(QBrush(fillColor));
|
||
rectCurve->attach(_plot);
|
||
m_peakMarkers.append(static_cast<QwtPlotItem*>(rectCurve));
|
||
|
||
// 2. 峰位竖线(中心线,虚线)
|
||
QwtPlotMarker* centerLine = new QwtPlotMarker();
|
||
centerLine->setLineStyle(QwtPlotMarker::VLine);
|
||
centerLine->setLinePen(QPen(color, 1, Qt::DotLine));
|
||
centerLine->setValue(center, 0);
|
||
centerLine->attach(_plot);
|
||
m_peakMarkers.append(centerLine);
|
||
|
||
// 3. 文本标签
|
||
QString labelText = QString("位置: %1 keV\nFWHM: %2 keV\n峰面积: %3")
|
||
.arg(center, 0, 'f', 1)
|
||
.arg(fwhm, 0, 'f', 2)
|
||
.arg(peaks[i].area, 0, 'f', 0);
|
||
|
||
QwtPlotMarker* textLabel = new QwtPlotMarker();
|
||
textLabel->setLabel(QwtText(labelText));
|
||
textLabel->setLabelAlignment(Qt::AlignLeft | Qt::AlignTop);
|
||
textLabel->setLabelOrientation(Qt::Horizontal);
|
||
|
||
double label_y = y_top + y_max * 0.02;
|
||
if (label_y > y_max * 0.9) {
|
||
label_y = y_top - y_max * 0.15;
|
||
textLabel->setLabelAlignment(Qt::AlignLeft | Qt::AlignTop);
|
||
}
|
||
|
||
textLabel->setValue(x_left, label_y);
|
||
textLabel->attach(_plot);
|
||
m_peakMarkers.append(textLabel);
|
||
}
|
||
|
||
_plot->replot();
|
||
}
|
||
|
||
// 清除所有峰标记
|
||
void NuclideAnalysisView::ClearPeakMarkers()
|
||
{
|
||
if (!_plot) {
|
||
return;
|
||
}
|
||
|
||
// 分离并删除所有峰标记
|
||
for (int i = 0; i < m_peakMarkers.size(); ++i) {
|
||
if (m_peakMarkers[i]) {
|
||
m_peakMarkers[i]->detach();
|
||
delete m_peakMarkers[i];
|
||
}
|
||
}
|
||
m_peakMarkers.clear();
|
||
|
||
_plot->replot();
|
||
}
|
||
|
||
bool NuclideAnalysisView::HasSpectrumData() const
|
||
{
|
||
return !m_spectrumX.isEmpty();
|
||
}
|
||
|
||
std::vector<FindPeaksBySvd::PeakInfo> NuclideAnalysisView::GetSvdPeaks(const QVector<double> &x, const QVector<double> &y, int step_w)
|
||
{
|
||
std::vector<FindPeaksBySvd::PeakInfo> peaks;
|
||
|
||
if (x.size() < 10 || y.size() < 10 || x.size() != y.size()) {
|
||
return peaks;
|
||
}
|
||
|
||
int n = x.size();
|
||
arma::mat spec_data(n, 2);
|
||
for (int i = 0; i < n; ++i) {
|
||
spec_data(i, 0) = x[i];
|
||
spec_data(i, 1) = y[i];
|
||
}
|
||
|
||
FindPeaksBySvd peakFinder;
|
||
try {
|
||
peaks = peakFinder.FindPeaks(spec_data, step_w);
|
||
} catch (const std::string& e) {
|
||
|
||
}
|
||
|
||
return peaks;
|
||
}
|
||
|
||
void NuclideAnalysisView::setupNuclideDialog()
|
||
{
|
||
m_nuclideDialog = new QDialog(this);
|
||
m_nuclideDialog->setWindowTitle(QStringLiteral(u"匹配核素射线"));
|
||
m_nuclideDialog->resize(700, 400);
|
||
|
||
QVBoxLayout* layout = new QVBoxLayout(m_nuclideDialog);
|
||
layout->setContentsMargins(6, 6, 6, 6);
|
||
|
||
QHBoxLayout* topBarLayout = new QHBoxLayout();
|
||
topBarLayout->addStretch();
|
||
m_addBtn = new QPushButton(QStringLiteral(u"新增核素"), m_nuclideDialog);
|
||
m_addBtn->setFixedWidth(80);
|
||
connect(m_addBtn, &QPushButton::clicked, this, &NuclideAnalysisView::onAddNuclide);
|
||
topBarLayout->addWidget(m_addBtn);
|
||
m_saveBtn = new QPushButton(QStringLiteral(u"保存"), m_nuclideDialog);
|
||
m_saveBtn->setFixedWidth(80);
|
||
connect(m_saveBtn, &QPushButton::clicked, this, &NuclideAnalysisView::onSaveSelectedNuclides);
|
||
topBarLayout->addWidget(m_saveBtn);
|
||
layout->addLayout(topBarLayout);
|
||
|
||
m_nuclideTable = new QTableWidget(m_nuclideDialog);
|
||
QStringList headers = { "核素", "射线类型", "能量(keV)", "操作" };
|
||
m_nuclideTable->setColumnCount(headers.size());
|
||
m_nuclideTable->setHorizontalHeaderLabels(headers);
|
||
m_nuclideTable->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||
m_nuclideTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||
m_nuclideTable->setAlternatingRowColors(true);
|
||
|
||
m_nuclideTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
|
||
m_nuclideTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
|
||
m_nuclideTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch);
|
||
m_nuclideTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed);
|
||
m_nuclideTable->setColumnWidth(3, 120);
|
||
layout->addWidget(m_nuclideTable);
|
||
connect(m_nuclideTable, &QTableWidget::itemSelectionChanged,
|
||
this, &NuclideAnalysisView::onNuclideTableSelectionChanged);
|
||
}
|
||
|
||
void NuclideAnalysisView::queryNuclidesForPeak(const PeakFitResultNuclide& peak)
|
||
{
|
||
for (QwtPlotMarker* marker : m_selectedNuclideMarkers) {
|
||
if (marker) {
|
||
marker->detach();
|
||
delete marker;
|
||
}
|
||
}
|
||
m_selectedNuclideMarkers.clear();
|
||
|
||
if (!peak.success) return;
|
||
|
||
if (!openNuclideDatabase()) {
|
||
m_nuclideTable->setRowCount(1);
|
||
m_nuclideTable->setItem(0, 0, new QTableWidgetItem(QStringLiteral(u"数据库未打开")));
|
||
m_nuclideDialog->show();
|
||
return;
|
||
}
|
||
|
||
double eMin = peak.center - peak.fwhm;
|
||
double eMax = peak.center + peak.fwhm;
|
||
double center = peak.center;
|
||
|
||
auto& db = SqliteManager::instance();
|
||
|
||
QString sql = "SELECT ni.NUCLIDE_NAME, nri.RAY_TYPE, nri.RAY_MEV, "
|
||
"nri.RAY_BRANCH_RATIO, nri.RAY_MEV_UNCERTAINTY "
|
||
"FROM nuclideRayInfo nri "
|
||
"INNER JOIN nuclideLib ni ON nri.NUCLIDE_ID = ni.ID;";
|
||
|
||
auto allRows = db.selectRows(sql);
|
||
|
||
struct MatchItem {
|
||
QString nuclideName;
|
||
QString rayType;
|
||
QString energyDisplay;
|
||
double sortValue;
|
||
};
|
||
QVector<MatchItem> matches;
|
||
|
||
for (const auto& row : allRows) {
|
||
QString mevStr = row["RAY_MEV"].toString().trimmed();
|
||
if (mevStr.isEmpty()) continue;
|
||
|
||
QStringList energyList = mevStr.split(',');
|
||
for (const QString& eStr : energyList) {
|
||
QString trimmed = eStr.trimmed();
|
||
if (trimmed.isEmpty()) continue;
|
||
|
||
bool matched = false;
|
||
QString displayStr;
|
||
double sortVal = 0.0;
|
||
|
||
if (trimmed.contains('-')) {
|
||
// 范围形式:如 "11.386-12.032"
|
||
QStringList rangeParts = trimmed.split('-');
|
||
if (rangeParts.size() == 2) {
|
||
bool ok1 = false, ok2 = false;
|
||
double low = rangeParts[0].trimmed().toDouble(&ok1);
|
||
double high = rangeParts[1].trimmed().toDouble(&ok2);
|
||
if (ok1 && ok2 && low <= high) {
|
||
// 范围与搜索窗口有重叠就算匹配
|
||
if (high >= eMin && low <= eMax) {
|
||
matched = true;
|
||
displayStr = trimmed;
|
||
// 取范围内最接近峰位的值用于排序
|
||
if (center < low) sortVal = low;
|
||
else if (center > high) sortVal = high;
|
||
else sortVal = center;
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
// 单值形式
|
||
bool ok = false;
|
||
double energy = trimmed.toDouble(&ok);
|
||
if (ok && energy >= eMin && energy <= eMax) {
|
||
matched = true;
|
||
displayStr = trimmed;
|
||
sortVal = energy;
|
||
}
|
||
}
|
||
|
||
if (matched) {
|
||
MatchItem item;
|
||
item.nuclideName = row["NUCLIDE_NAME"].toString();
|
||
item.rayType = row["RAY_TYPE"].toString();
|
||
item.energyDisplay = displayStr;
|
||
item.sortValue = sortVal;
|
||
matches.append(item);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 按与峰位的接近程度排序
|
||
std::sort(matches.begin(), matches.end(),
|
||
[center](const MatchItem& a, const MatchItem& b) {
|
||
return qAbs(a.sortValue - center) < qAbs(b.sortValue - center);
|
||
});
|
||
|
||
// //单峰显示
|
||
// m_nuclideTable->setRowCount(matches.size());
|
||
// for (int i = 0; i < matches.size(); ++i) {
|
||
// const auto& m = matches[i];
|
||
// m_nuclideTable->setItem(i, 0, new QTableWidgetItem(m.nuclideName));
|
||
// m_nuclideTable->setItem(i, 1, new QTableWidgetItem(m.rayType));
|
||
// m_nuclideTable->setItem(i, 2, new QTableWidgetItem(m.energyDisplay));
|
||
// createRowOperationWidgets(i);
|
||
// }
|
||
|
||
// if (matches.isEmpty()) {
|
||
// m_nuclideTable->setRowCount(1);
|
||
// m_nuclideTable->setItem(0, 0, new QTableWidgetItem(QStringLiteral(u"未找到匹配核素")));
|
||
// }
|
||
|
||
//多峰显示
|
||
if (m_nuclideTable->rowCount() > 0) {
|
||
QTableWidgetItem* firstItem = m_nuclideTable->item(0, 0);
|
||
if (firstItem) {
|
||
QString txt = firstItem->text();
|
||
if (txt == QStringLiteral(u"未找到匹配核素") ||
|
||
txt == QStringLiteral(u"数据库未打开")) {
|
||
m_nuclideTable->removeRow(0);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 新匹配结果追加到表格末尾
|
||
int startRow = m_nuclideTable->rowCount();
|
||
for (int i = 0; i < matches.size(); ++i) {
|
||
int row = startRow + i;
|
||
m_nuclideTable->insertRow(row);
|
||
const auto& m = matches[i];
|
||
m_nuclideTable->setItem(row, 0, new QTableWidgetItem(m.nuclideName));
|
||
m_nuclideTable->setItem(row, 1, new QTableWidgetItem(m.rayType));
|
||
m_nuclideTable->setItem(row, 2, new QTableWidgetItem(m.energyDisplay));
|
||
createRowOperationWidgets(row);
|
||
}
|
||
|
||
// 表格为空且没匹配到时,才显示提示
|
||
if (m_nuclideTable->rowCount() == 0 && matches.isEmpty()) {
|
||
m_nuclideTable->setRowCount(1);
|
||
m_nuclideTable->setItem(0, 0, new QTableWidgetItem(QStringLiteral(u"未找到匹配核素")));
|
||
}
|
||
|
||
m_nuclideDialog->show();
|
||
m_nuclideDialog->raise();
|
||
m_nuclideDialog->activateWindow();
|
||
}
|
||
|
||
bool NuclideAnalysisView::openNuclideDatabase()
|
||
{
|
||
auto& db = SqliteManager::instance();
|
||
if (db.isOpen()) {
|
||
return true;
|
||
}
|
||
QString dbPath = QCoreApplication::applicationDirPath() + "/nuclideLib.db";
|
||
|
||
if (dbPath.isEmpty()) {
|
||
qWarning() << "核素数据库路径未设置";
|
||
return false;
|
||
}
|
||
return db.openDatabase(dbPath, "nuclide_conn");
|
||
}
|
||
|
||
void NuclideAnalysisView::createRowOperationWidgets(int row)
|
||
{
|
||
QWidget* operationWidget = new QWidget(m_nuclideTable);
|
||
QHBoxLayout* hLayout = new QHBoxLayout(operationWidget);
|
||
hLayout->setContentsMargins(4, 2, 4, 2);
|
||
hLayout->setSpacing(8);
|
||
|
||
// 复选框
|
||
QCheckBox* checkBox = new QCheckBox(operationWidget);
|
||
checkBox->setObjectName("rowCheckBox");
|
||
hLayout->addWidget(checkBox);
|
||
hLayout->addStretch();
|
||
|
||
// 删除按钮
|
||
QPushButton* deleteBtn = new QPushButton(QStringLiteral(u"删除"), operationWidget);
|
||
deleteBtn->setFixedWidth(50);
|
||
deleteBtn->setProperty("rowIndex", row);
|
||
connect(deleteBtn, &QPushButton::clicked, this, &NuclideAnalysisView::onDeleteRow);
|
||
hLayout->addWidget(deleteBtn);
|
||
|
||
m_nuclideTable->setCellWidget(row, 3, operationWidget);
|
||
}
|
||
|
||
void NuclideAnalysisView::onDeleteRow()
|
||
{
|
||
QPushButton* btn = qobject_cast<QPushButton*>(sender());
|
||
if (!btn) return;
|
||
|
||
int row = btn->property("rowIndex").toInt();
|
||
if (row >= 0 && row < m_nuclideTable->rowCount()) {
|
||
m_nuclideTable->removeRow(row);
|
||
// 更新后续行的rowIndex属性
|
||
for (int i = row; i < m_nuclideTable->rowCount(); ++i) {
|
||
QWidget* widget = m_nuclideTable->cellWidget(i, 3);
|
||
if (widget) {
|
||
QPushButton* delBtn = widget->findChild<QPushButton*>();
|
||
if (delBtn) {
|
||
delBtn->setProperty("rowIndex", i);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void NuclideAnalysisView::onSaveSelectedNuclides()
|
||
{
|
||
QJsonArray nuclideArray;
|
||
|
||
for (int i = 0; i < m_nuclideTable->rowCount(); ++i) {
|
||
QWidget* widget = m_nuclideTable->cellWidget(i, 3);
|
||
if (widget) {
|
||
QCheckBox* checkBox = widget->findChild<QCheckBox*>();
|
||
if (checkBox && checkBox->isChecked()) {
|
||
QTableWidgetItem* nuclideItem = m_nuclideTable->item(i, 0);
|
||
QTableWidgetItem* rayTypeItem = m_nuclideTable->item(i, 1);
|
||
QTableWidgetItem* energyItem = m_nuclideTable->item(i, 2);
|
||
|
||
if (nuclideItem) {
|
||
QJsonObject obj;
|
||
obj["nuclide"] = nuclideItem->text();
|
||
if (rayTypeItem) {
|
||
obj["ray_type"] = rayTypeItem->text();
|
||
}
|
||
if (energyItem) {
|
||
obj["energy_keV"] = energyItem->text();
|
||
}
|
||
nuclideArray.append(obj);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (nuclideArray.isEmpty()) {
|
||
QMessageBox::information(m_nuclideDialog, QStringLiteral(u"提示"),
|
||
QStringLiteral(u"请先勾选要保存的核素"));
|
||
return;
|
||
}
|
||
|
||
QString savePath = _workspace + "/selected_nuclides.json";
|
||
QFile file(savePath);
|
||
if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||
QJsonDocument doc(nuclideArray);
|
||
file.write(doc.toJson(QJsonDocument::Indented));
|
||
file.close();
|
||
} else {
|
||
QMessageBox::warning(m_nuclideDialog, QStringLiteral(u"保存失败"),
|
||
QStringLiteral(u"无法写入文件:%1").arg(savePath));
|
||
}
|
||
}
|
||
|
||
void NuclideAnalysisView::onAddNuclide()
|
||
{
|
||
AddNuclideDialog dlg(m_nuclideDialog);
|
||
if (dlg.exec() == QDialog::Accepted) {
|
||
QList<QStringList>& selectRays = dlg.getSelectRay();
|
||
for (const QStringList& ray : selectRays) {
|
||
if (ray.size() >= 3) {
|
||
QString nuclideName = ray.at(1);
|
||
QString energy = ray.at(2);
|
||
int row = m_nuclideTable->rowCount();
|
||
m_nuclideTable->insertRow(row);
|
||
m_nuclideTable->setItem(row, 0, new QTableWidgetItem(nuclideName));
|
||
m_nuclideTable->setItem(row, 1, new QTableWidgetItem(QStringLiteral(u"γ")));
|
||
m_nuclideTable->setItem(row, 2, new QTableWidgetItem(energy));
|
||
createRowOperationWidgets(row);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void NuclideAnalysisView::onNuclideTableSelectionChanged()
|
||
{
|
||
// 清除旧竖线
|
||
for (QwtPlotMarker* marker : m_selectedNuclideMarkers) {
|
||
if (marker) {
|
||
marker->detach();
|
||
delete marker;
|
||
}
|
||
}
|
||
m_selectedNuclideMarkers.clear();
|
||
|
||
// 获取选中行
|
||
QList<QTableWidgetSelectionRange> ranges = m_nuclideTable->selectedRanges();
|
||
if (ranges.isEmpty()) {
|
||
_plot->replot();
|
||
return;
|
||
}
|
||
|
||
// 遍历选中行,解析能量画竖线
|
||
for (const QTableWidgetSelectionRange& range : ranges) {
|
||
for (int row = range.topRow(); row <= range.bottomRow(); ++row) {
|
||
QTableWidgetItem* item = m_nuclideTable->item(row, 2);
|
||
if (!item) continue;
|
||
|
||
QString str = item->text().trimmed();
|
||
if (str.contains('-')) {
|
||
// 范围能量,画两条线
|
||
QStringList parts = str.split('-');
|
||
if (parts.size() == 2) {
|
||
bool ok1, ok2;
|
||
double low = parts[0].trimmed().toDouble(&ok1);
|
||
double high = parts[1].trimmed().toDouble(&ok2);
|
||
if (ok1 && ok2) {
|
||
addNuclideVLine(low);
|
||
addNuclideVLine(high);
|
||
}
|
||
}
|
||
} else {
|
||
// 单能量,画一条线
|
||
bool ok;
|
||
double e = str.toDouble(&ok);
|
||
if (ok) addNuclideVLine(e);
|
||
}
|
||
}
|
||
}
|
||
_plot->replot();
|
||
}
|
||
|
||
void NuclideAnalysisView::addNuclideVLine(double energy)
|
||
{
|
||
QwtPlotMarker* marker = new QwtPlotMarker();
|
||
marker->setLineStyle(QwtPlotMarker::VLine);
|
||
marker->setLinePen(QPen(Qt::green, 2, Qt::SolidLine));
|
||
marker->setValue(energy, 0);
|
||
marker->attach(_plot);
|
||
m_selectedNuclideMarkers.append(marker);
|
||
} |