新增ROI配置界面,将ROI配置保存至configure下的regionOfInterest文件夹下

This commit is contained in:
anxinglong 2026-07-09 17:42:39 +08:00
parent cafcb2839a
commit ad111584f7
6 changed files with 589 additions and 9 deletions

View File

@ -37,6 +37,7 @@
#include "EnergyCountPlotView.h"
#include "ParticleCountPlotView.h"
#include "reportwidget.h"
#include "RegionOfInterest.h"
#include <array>
#include "csv.h"
#include <fstream>
@ -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();

View File

@ -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();

View File

@ -1,14 +1,394 @@
#include "RegionOfInterest.h"
#include "ui_RegionOfInterest.h"
#include <QMessageBox>
#include <QwtPlotItem>
#include <QtMath>
#include <QEvent>
#include <QMouseEvent>
#include <QHeaderView>
#include <QAbstractItemView>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonDocument>
#include <QFile>
#include <QDir>
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<QwtPlotCanvas*>(_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<QwtPlotItem*> allItems = _plot->itemList();
QListIterator<QwtPlotItem*> it(allItems);
while (it.hasNext())
{
QwtPlotItem* item = it.next();
if (item->rtti() == QwtPlotItem::Rtti_PlotMarker)
{
QwtPlotMarker* marker = static_cast<QwtPlotMarker*>(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<QMouseEvent*>(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();
}

View File

@ -2,11 +2,24 @@
#define REGIONOFINTEREST_H
#include <QWidget>
#include <QwtPlotCanvas>
#include <QwtText>
#include <QwtPlotCurve>
#include <QwtPlotMarker>
#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<ROIItem> 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

View File

@ -6,13 +6,157 @@
<rect>
<x>0</x>
<y>0</y>
<width>711</width>
<height>505</height>
<width>1000</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
<property name="minimumSize">
<size>
<width>1000</width>
<height>600</height>
</size>
</property>
<property name="windowTitle">
<string>感兴趣区</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="1,1,20">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>描述</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QPlainTextEdit" name="plainTextEdit"/>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>起始点1:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit_start1"/>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>终止点1:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit_end1"/>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>起始点2:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit_start2"/>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>终止点2:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit_end2"/>
</item>
<item>
<widget class="QPushButton" name="pBtn_insert">
<property name="text">
<string>插入</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pBtn_update">
<property name="text">
<string>修改</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pBtn_clear">
<property name="text">
<string>清空ROI</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="5,3">
<item>
<widget class="QTableWidget" name="tableWidget">
<column>
<property name="text">
<string>序号</string>
</property>
</column>
<column>
<property name="text">
<string>描述</string>
</property>
</column>
<column>
<property name="text">
<string>起始点1</string>
</property>
</column>
<column>
<property name="text">
<string>终止点1</string>
</property>
</column>
<column>
<property name="text">
<string>起始点2</string>
</property>
</column>
<column>
<property name="text">
<string>终止点2</string>
</property>
</column>
</widget>
</item>
<item>
<widget class="QWidget" name="widget" native="true">
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QVBoxLayout" name="layout_Curve"/>
</item>
</layout>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>

View File

@ -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) {