From ad111584f7ebf232a77d670f0b006c4c7edc216e Mon Sep 17 00:00:00 2001 From: anxinglong <2910824064@qq.com> Date: Thu, 9 Jul 2026 17:42:39 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9EROI=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E7=95=8C=E9=9D=A2=EF=BC=8C=E5=B0=86ROI=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E4=BF=9D=E5=AD=98=E8=87=B3configure=E4=B8=8B=E7=9A=84regionOfI?= =?UTF-8?q?nterest=E6=96=87=E4=BB=B6=E5=A4=B9=E4=B8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/MainWindow.cpp | 7 + src/MainWindow.h | 2 + src/RegionOfInterest/RegionOfInterest.cpp | 382 +++++++++++++++++++++- src/RegionOfInterest/RegionOfInterest.h | 39 +++ src/RegionOfInterest/RegionOfInterest.ui | 152 ++++++++- src/src.pro | 16 +- 6 files changed, 589 insertions(+), 9 deletions(-) diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 62e63e5..80a520c 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -37,6 +37,7 @@ #include "EnergyCountPlotView.h" #include "ParticleCountPlotView.h" #include "reportwidget.h" +#include "RegionOfInterest.h" #include #include "csv.h" #include @@ -733,6 +734,12 @@ void MainWindow::on_action_device_connect_cfg_triggered() m_deviceMgr->exec(); } +void MainWindow::on_action_roi_triggered() +{ + RegionOfInterest *roi = new RegionOfInterest(); + roi->show(); +} + void MainWindow::onParticleDatarefresh() { MeasureAnalysisProjectModel *pro_model = ProjectList::Instance()->GetCurrentProjectModel(); diff --git a/src/MainWindow.h b/src/MainWindow.h index 2bddd6c..9db8fa3 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -89,6 +89,8 @@ private slots: void on_EnergyCountTimer(); //设备连接管理 void on_action_device_connect_cfg_triggered(); + //感兴趣区 + void on_action_roi_triggered(); void onParticleDatarefresh(); void onAddressCountViewNeedRefresh(); diff --git a/src/RegionOfInterest/RegionOfInterest.cpp b/src/RegionOfInterest/RegionOfInterest.cpp index b17f847..cbd3cb4 100644 --- a/src/RegionOfInterest/RegionOfInterest.cpp +++ b/src/RegionOfInterest/RegionOfInterest.cpp @@ -1,14 +1,394 @@ #include "RegionOfInterest.h" #include "ui_RegionOfInterest.h" - +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include RegionOfInterest::RegionOfInterest(QWidget *parent) : QWidget(parent) , ui(new Ui::RegionOfInterest) + , m_tempStartMarker(nullptr) + , m_tempEndMarker(nullptr) { ui->setupUi(this); + this->_plot = new CustomQwtPlot(this); + ui->layout_Curve->addWidget(_plot); + setupPlot(); + ui->tableWidget->setColumnCount(6); + QStringList headers = {"序号","描述文本", "起始1", "结束1", "起始2", "结束2"}; + ui->tableWidget->verticalHeader()->setVisible(false); + QHeaderView* hHeader = ui->tableWidget->horizontalHeader(); + hHeader->setSectionResizeMode(QHeaderView::Stretch); + ui->tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);// 整行选择 + ui->tableWidget->setSelectionMode(QAbstractItemView::SingleSelection);// 单选模式 + ui->tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); + connect(ui->tableWidget,&QTableWidget::cellClicked,this,&RegionOfInterest::on_tableWidget_cellClicked); + loadROIFromFile(); } RegionOfInterest::~RegionOfInterest() { delete ui; } + +void RegionOfInterest::setupPlot() +{ + _plot->setCanvasBackground(Qt::white); + QwtPlotCanvas* canvas = qobject_cast(_plot->canvas()); + canvas->setFrameStyle(QFrame::NoFrame); + QFont font = this->font(); + font.setBold(false); + // 设置轴自动缩放 + _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); + _plot->canvas()->installEventFilter(this);// 给画布安装事件过滤器,拦截鼠标事件 +} + +void RegionOfInterest::refreshROILines() +{ + // 获取画布所有绘图项 + QList allItems = _plot->itemList(); + QListIterator it(allItems); + while (it.hasNext()) + { + QwtPlotItem* item = it.next(); + if (item->rtti() == QwtPlotItem::Rtti_PlotMarker) + { + QwtPlotMarker* marker = static_cast(item); + marker->detach(); + delete marker; + } + } + // 遍历所有ROI数据绘制竖线 + for(const ROIItem& roi : m_roiList) + { + // 第一组:起始1、结束1 红色竖线,必须存在才插入,所以一定绘制 + createVLine(roi.s1, Qt::red, 1.2); + createVLine(roi.e1, Qt::red, 1.2); + + // 第二组:起始2、结束2 有有效数值才绘制,无值跳过 + bool secondGroupValid = !(qIsNaN(roi.s2) || qIsNaN(roi.e2) || (roi.s2 == 0 && roi.e2 == 0)); + if (secondGroupValid) + { + createVLine(roi.s2, Qt::blue, 1.2); + createVLine(roi.e2, Qt::blue, 1.2); + } + } + _plot->replot(); +} + +QwtPlotMarker *RegionOfInterest::createVLine(double x, Qt::GlobalColor color, double lineWidth) +{ + QwtPlotMarker* marker = new QwtPlotMarker(); + marker->setXValue(x); + marker->setLineStyle(QwtPlotMarker::VLine); + QPen pen(color); + pen.setWidthF(lineWidth); + marker->setLinePen(pen); + marker->attach(_plot); + return marker; +} + +QJsonArray RegionOfInterest::readAllROIJson() +{ + QString roiPath = QApplication::applicationDirPath() + "/configure/RegionOfInterest/roiData.json"; + QJsonArray arr; + QFile file(roiPath); + if (!file.exists()) + return arr; + if (!file.open(QIODevice::ReadOnly)) + return arr; + + QByteArray jsonData = file.readAll(); + file.close(); + QJsonParseError err; + QJsonDocument doc = QJsonDocument::fromJson(jsonData, &err); + if (err.error != QJsonParseError::NoError || !doc.isArray()) + return arr; + return doc.array(); +} + +void RegionOfInterest::saveAllRoiToJson() +{ + QString roiPath = QApplication::applicationDirPath() + "/configure/RegionOfInterest/roiData.json"; + + QJsonArray roiArr; + // 遍历内存中完整ROI列表,序列化 + for (const ROIItem& item : m_roiList) + { + QJsonObject obj; + obj["rowNo"] = item.rowNo; + obj["desc"] = item.desc; + obj["s1"] = item.s1; + obj["e1"] = item.e1; + bool secondValid = !(qIsNaN(item.s2) || qIsNaN(item.e2)); + obj["isSecondValid"] = secondValid; + obj["s2"] = secondValid ? item.s2 : 0.0; + obj["e2"] = secondValid ? item.e2 : 0.0; + roiArr.append(obj); + } + + QFile file(roiPath); + if (!file.open(QIODevice::WriteOnly)) + { + QMessageBox::warning(this, "保存失败", "文件无法写入:" + file.errorString()); + return; + } + QJsonDocument doc(roiArr); + file.write(doc.toJson(QJsonDocument::Indented)); + file.close(); +} + +void RegionOfInterest::on_pBtn_insert_clicked() +{ + QString text = ui->plainTextEdit->toPlainText(); + QString start1 = ui->lineEdit_start1->text(); + QString end1 = ui->lineEdit_end1->text(); + QString start2 = ui->lineEdit_start2->text(); + QString end2 = ui->lineEdit_end2->text(); + int rowCount = ui->tableWidget->rowCount(); + + if (start1.isEmpty() || end1.isEmpty()) + { + QMessageBox::warning(this, "提示", "起始1、结束1不能为空!"); + return; + } + + ui->tableWidget->insertRow(rowCount); + ui->tableWidget->setItem(rowCount, 0, new QTableWidgetItem(QString::number(rowCount + 1))); + ui->tableWidget->setItem(rowCount, 1, new QTableWidgetItem(text)); + ui->tableWidget->setItem(rowCount, 2, new QTableWidgetItem(start1)); + ui->tableWidget->setItem(rowCount, 3, new QTableWidgetItem(end1)); + ui->tableWidget->setItem(rowCount, 4, new QTableWidgetItem(start2)); + ui->tableWidget->setItem(rowCount, 5, new QTableWidgetItem(end2)); + + ROIItem newRoi; + newRoi.rowNo = rowCount + 1; + newRoi.desc = text; + newRoi.s1 = start1.toDouble(); + newRoi.e1 = end1.toDouble(); + newRoi.s2 = start2.toDouble(); + newRoi.e2 = end2.toDouble(); + m_roiList.append(newRoi); + + // 刷新绘图竖线 + refreshROILines(); + + saveAllRoiToJson(); +} + + +void RegionOfInterest::on_pBtn_update_clicked() +{ + int selectRow = ui->tableWidget->currentRow(); + if(selectRow < 0) + { + QMessageBox::information(this, "提示", "请先在表格中点击选择要修改的行!"); + return; + } + QString text = ui->plainTextEdit->toPlainText(); + QString start1 = ui->lineEdit_start1->text(); + QString end1 = ui->lineEdit_end1->text(); + QString start2 = ui->lineEdit_start2->text(); + QString end2 = ui->lineEdit_end2->text(); + + ui->tableWidget->item(selectRow, 1)->setText(text); + ui->tableWidget->item(selectRow, 2)->setText(start1); + ui->tableWidget->item(selectRow, 3)->setText(end1); + ui->tableWidget->item(selectRow, 4)->setText(start2); + ui->tableWidget->item(selectRow, 5)->setText(end2); + + // 更新对应ROI列表数据 + int targetSeq = selectRow + 1; + for(int i = 0; i < m_roiList.size(); i++) + { + if(m_roiList[i].rowNo == targetSeq) + { + m_roiList[i].desc = text; + m_roiList[i].s1 = start1.toDouble(); + m_roiList[i].e1 = end1.toDouble(); + m_roiList[i].s2 = start2.toDouble(); + m_roiList[i].e2 = end2.toDouble(); + break; + } + } + refreshROILines(); + saveAllRoiToJson(); +} + + +void RegionOfInterest::on_pBtn_clear_clicked() +{ + if(ui->tableWidget->rowCount() <= 0) + { + QMessageBox::information(this, "提示", "表格已经为空!"); + return; + } + // 弹窗确认防止误删 + int ret = QMessageBox::question(this, "确认", "确定要清空表格所有数据吗?", QMessageBox::Yes | QMessageBox::No); + if(ret == QMessageBox::Yes) + { + ui->tableWidget->setRowCount(0); + m_roiList.clear(); // 清空数据缓存 + refreshROILines(); // 清除所有竖线 + saveAllRoiToJson(); + } +} + +void RegionOfInterest::on_tableWidget_cellClicked(int row, int col) +{ + Q_UNUSED(col); + // 读取表格该行内容 + QString text = ui->tableWidget->item(row, 1)->text(); + QString s1 = ui->tableWidget->item(row, 2)->text(); + QString e1 = ui->tableWidget->item(row, 3)->text(); + QString s2 = ui->tableWidget->item(row, 4)->text(); + QString e2 = ui->tableWidget->item(row, 5)->text(); + + // 回填到输入控件 + ui->plainTextEdit->setPlainText(text); + ui->lineEdit_start1->setText(s1); + ui->lineEdit_end1->setText(e1); + ui->lineEdit_start2->setText(s2); + ui->lineEdit_end2->setText(e2); +} + +bool RegionOfInterest::eventFilter(QObject *obj, QEvent *event) +{ + // 只处理绘图画布的鼠标事件 + if (obj == _plot->canvas()) + { + QMouseEvent* mouseEv = dynamic_cast(event); + if (!mouseEv) + return QWidget::eventFilter(obj, event); + + // 鼠标按下 + if (event->type() == QEvent::MouseButtonPress) + { + m_bMouseSelect = true; + m_selectStartX = _plot->invTransform(QwtPlot::xBottom, mouseEv->pos().x()); + // 创建临时虚线标记 + QPen dashPen(Qt::red); + dashPen.setStyle(Qt::DashLine); + dashPen.setWidthF(1.5); + + m_tempStartMarker = new QwtPlotMarker(); + m_tempStartMarker->setXValue(m_selectStartX); + m_tempStartMarker->setLineStyle(QwtPlotMarker::VLine); + m_tempStartMarker->setLinePen(dashPen); + m_tempStartMarker->attach(_plot); + + m_tempEndMarker = new QwtPlotMarker(); + m_tempEndMarker->setXValue(m_selectStartX); + m_tempEndMarker->setLineStyle(QwtPlotMarker::VLine); + m_tempEndMarker->setLinePen(dashPen); + m_tempEndMarker->attach(_plot); + _plot->replot(); + } + // 鼠标移动:实时更新结束竖线位置 + else if (event->type() == QEvent::MouseMove && m_bMouseSelect) + { + m_selectEndX = _plot->invTransform(QwtPlot::xBottom, mouseEv->pos().x()); + m_tempEndMarker->setXValue(m_selectEndX); + _plot->replot(); + } + // 鼠标松开 + else if (event->type() == QEvent::MouseButtonRelease) + { + if (!m_bMouseSelect) + return QWidget::eventFilter(obj, event); + m_bMouseSelect = false; + + m_selectEndX = _plot->invTransform(QwtPlot::xBottom, mouseEv->pos().x()); + + // 销毁临时虚线 + if(m_tempStartMarker) + { + m_tempStartMarker->detach(); + delete m_tempStartMarker; + m_tempStartMarker = nullptr; + } + if(m_tempEndMarker) + { + m_tempEndMarker->detach(); + delete m_tempEndMarker; + m_tempEndMarker = nullptr; + } + _plot->replot(); + double s = qMin(m_selectStartX, m_selectEndX); + double e = qMax(m_selectStartX, m_selectEndX); + + // 回填到起始1、结束1输入框 + ui->lineEdit_start1->setText(QString::number(s, 'f', 2)); + ui->lineEdit_end1->setText(QString::number(e, 'f', 2)); + } + } + return QWidget::eventFilter(obj, event); +} + +void RegionOfInterest::loadROIFromFile() +{ + QString roiPath = QApplication::applicationDirPath() + "/configure/RegionOfInterest/roiData.json"; + if (!QFile::exists(roiPath)) + { + QMessageBox::information(this, "提示", "暂无保存的ROI文件"); + return; + } + QJsonArray arr = readAllROIJson(); + if (arr.isEmpty()) + { + QMessageBox::information(this, "提示", "文件内无ROI数据"); + return; + } + // 清空当前界面与内存 + ui->tableWidget->setRowCount(0); + m_roiList.clear(); + + for (int i = 0; i < arr.size(); i++) + { + QJsonObject obj = arr[i].toObject(); + ROIItem item; + item.rowNo = obj["rowNo"].toInt(); + item.desc = obj["desc"].toString(); + item.s1 = obj["s1"].toDouble(); + item.e1 = obj["e1"].toDouble(); + bool valid2 = obj["isSecondValid"].toBool(); + if (valid2) + { + item.s2 = obj["s2"].toDouble(); + item.e2 = obj["e2"].toDouble(); + } + else + { + item.s2 = qQNaN(); + item.e2 = qQNaN(); + } + m_roiList.append(item); + + int row = ui->tableWidget->rowCount(); + ui->tableWidget->insertRow(row); + QString s2Str = valid2 ? QString::number(item.s2) : ""; + QString e2Str = valid2 ? QString::number(item.e2) : ""; + ui->tableWidget->setItem(row, 0, new QTableWidgetItem(QString::number(item.rowNo))); + ui->tableWidget->setItem(row, 1, new QTableWidgetItem(item.desc)); + ui->tableWidget->setItem(row, 2, new QTableWidgetItem(QString::number(item.s1))); + ui->tableWidget->setItem(row, 3, new QTableWidgetItem(QString::number(item.e1))); + ui->tableWidget->setItem(row, 4, new QTableWidgetItem(s2Str)); + ui->tableWidget->setItem(row, 5, new QTableWidgetItem(e2Str)); + } + refreshROILines(); +} + diff --git a/src/RegionOfInterest/RegionOfInterest.h b/src/RegionOfInterest/RegionOfInterest.h index e6c827d..85bfd7f 100644 --- a/src/RegionOfInterest/RegionOfInterest.h +++ b/src/RegionOfInterest/RegionOfInterest.h @@ -2,11 +2,24 @@ #define REGIONOFINTEREST_H #include +#include +#include +#include +#include +#include "CustomQwtPlot.h" namespace Ui { class RegionOfInterest; } +struct ROIItem +{ + int rowNo; // 表格序号 + QString desc; // 描述文本 + double s1, e1; // 第一组区间:起始1、结束1 + double s2, e2; // 第二组区间:起始2、结束2 +}; + class RegionOfInterest : public QWidget { Q_OBJECT @@ -14,9 +27,35 @@ class RegionOfInterest : public QWidget public: explicit RegionOfInterest(QWidget *parent = nullptr); ~RegionOfInterest(); + void setupPlot(); + void refreshROILines(); // 刷新画布所有ROI竖线 + bool eventFilter(QObject *obj, QEvent *event) override; + void loadROIFromFile();//加载ROI配置 + +private: + QwtPlotMarker* createVLine(double x, Qt::GlobalColor color, double lineWidth = 1.0); // 创建垂直标记竖线 + QJsonArray readAllROIJson();//读取本地全部ROI数组 + void saveAllRoiToJson(); // 覆盖保存所有内存ROI到JSON + +private slots: + void on_pBtn_insert_clicked(); + + void on_pBtn_update_clicked(); + + void on_pBtn_clear_clicked(); + + void on_tableWidget_cellClicked(int row, int col); private: Ui::RegionOfInterest *ui; + CustomQwtPlot* _plot = nullptr; + QList m_roiList; // 存储所有ROI数据 + bool m_bMouseSelect = false; + double m_selectStartX = 0.0; + double m_selectEndX = 0.0; + + QwtPlotMarker* m_tempStartMarker = nullptr; + QwtPlotMarker* m_tempEndMarker = nullptr; }; #endif // REGIONOFINTEREST_H diff --git a/src/RegionOfInterest/RegionOfInterest.ui b/src/RegionOfInterest/RegionOfInterest.ui index 9e59fc0..6477edd 100644 --- a/src/RegionOfInterest/RegionOfInterest.ui +++ b/src/RegionOfInterest/RegionOfInterest.ui @@ -6,13 +6,157 @@ 0 0 - 711 - 505 + 1000 + 600 - - Form + + + 1000 + 600 + + + 感兴趣区 + + + + + + 描述 + + + + + + + + + + + + + + 起始点1: + + + + + + + + + + 终止点1: + + + + + + + + + + 起始点2: + + + + + + + + + + 终止点2: + + + + + + + + + + 插入 + + + + + + + 修改 + + + + + + + 清空ROI + + + + + + + + + + + + 序号 + + + + + 描述 + + + + + 起始点1 + + + + + 终止点1 + + + + + 起始点2 + + + + + 终止点2 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + diff --git a/src/src.pro b/src/src.pro index bb7769b..5ee10ff 100644 --- a/src/src.pro +++ b/src/src.pro @@ -44,7 +44,9 @@ INCLUDEPATH += \ $${PWD}/CoincidenceEventTimeView\ $${PWD}/NuclideAnalysisView \ $${PWD}/BackgroundTaskListView \ - $${PWD}/analysisReport + $${PWD}/analysisReport \ + $${PWD}/RegionOfInterest + @@ -69,7 +71,9 @@ DEPENDPATH += \ $${PWD}/CoincidenceEventTimeView\ $${PWD}/NuclideAnalysisView \ $${PWD}/BackgroundTaskListView \ - $${PWD}/analysisReport + $${PWD}/analysisReport \ + $${PWD}/RegionOfInterest + @@ -131,6 +135,7 @@ SOURCES += \ analysisReport/reportgenerator.cpp \ analysisReport/reportwidget.cpp \ analysisReport/spectrumdata.cpp \ + RegionOfInterest/RegionOfInterest.cpp \ main.cpp HEADERS += \ @@ -193,7 +198,8 @@ HEADERS += \ AntiConformEnergySpectrumView/AntiConformEnergySpectrumView.h \ analysisReport/reportgenerator.h \ analysisReport/reportwidget.h \ - analysisReport/spectrumdata.h + analysisReport/spectrumdata.h \ + RegionOfInterest/RegionOfInterest.h FORMS += \ 2DSpectralCompliance/TwoDSpectralCompliance.ui \ @@ -215,7 +221,9 @@ FORMS += \ ThreeDimensionalConformityAnalysisView/ParticleDataStatistics.ui \ ThreeDimensionalConformityAnalysisView/ThreeDDisplay.ui \ ThreeDimensionalConformityAnalysisView/ConformityAnalysis.ui \ - NuclideLib/NuclideLib.ui + NuclideLib/NuclideLib.ui \ + RegionOfInterest/RegionOfInterest.ui + DEFINES += ENABLE_DEBUG contains(DEFINES, ENABLE_DEBUG) {