EnergySpectrumAnalyer/src/NuclideAnalysisView/NuclideAnalysisView.cpp

1016 lines
33 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

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

#include "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;
}
// 从整张谱中自动寻找三个最强的峰并计算FWHMSVD寻峰
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::matFindPeaksBySvd 需要矩阵格式)
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);
}