IPProcessGrid
类是 ImagePlay 图像处理软件的核心组件之一,负责管理和渲染图像处理流程图,并控制处理流程的执行。它继承自 QGraphicsView
,并实现了IPLPropertyChangedEventHandler
和 IPLOutputsChangedEventHandler
接口,用于响应属性变化和输出变化事件。
//#############################################################################
//
// This file is part of ImagePlay.
//
// ImagePlay is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ImagePlay is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ImagePlay. If not, see <http://www.gnu.org/licenses/>.
//
//#############################################################################
#ifndef IPPROCESSGRID_H
#define IPPROCESSGRID_H
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QDragEnterEvent>
#include <QMainWindow>
#include <QStatusBar>
#include <QScrollBar>
#include <QQueue>
#include <QElapsedTimer>
#include <QApplication>
#include "IPProcessStep.h"
#include "IPProcessGridScene.h"
#include "IPProcessThread.h"
#include "IPZoomWidget.h"
class IPProcessGridScene;
class MainWindow;
//-----------------------------------------------------------------------------
//!IPProcessGrid represents the process graph
/*!
* This custom QGraphicsView manages the proceses, connections and renders
* everything as a process graph.
* All items are stored in IPProcessGridScene.
* The graph traversation and process exectution is also handled here.
*/
class IPProcessGrid : public QGraphicsView, public IPLPropertyChangedEventHandler, public IPLOutputsChangedEventHandler
{
Q_OBJECT
public:
explicit IPProcessGrid (QWidget *parent = 0);
void zoomIn ();
void zoomOut ();
void zoomBy (float scaleChange);
void zoomTo (float scale);
static bool sortTreeDepthLessThan (IPProcessStep* s1, IPProcessStep* s2);
void buildQueue ();
int executeThread (IPLProcess* process, IPLImage *image = NULL, int inputIndex = 0, bool useOpenCV = false);
void propagateNeedsUpdate (IPLProcess* process);
void propagateResultReady (IPLProcess *process, bool resultReady);
void propertyChanged (IPLProcess *);
void outputsChanged (IPLProcess *);
void setSequenceIndex (int index);
void setSequenceRunning (bool status) { _isSequenceRunning = status; }
void setMainWindow (MainWindow* mainWindow) { _mainWindow = mainWindow; }
void requestUpdate ();
MainWindow* mainWindow () { return _mainWindow; }
IPProcessGridScene* scene () { return _scene; }
void stopExecution () { _stopExecution = true; }
bool isRunning () { return _isRunning; }
signals:
void sequenceChanged (int index, int count);
public slots:
void execute (bool forcedUpdate = false);
void terminate ();
void updateProgress (int);
void sceneRectChanged (const QRectF & rect);
private:
void fitLargeSceneRect();
IPProcessGridScene* _scene; //!< Scene
float _scale; //!< Scale for zooming
MainWindow* _mainWindow; //!< MainWindow
bool _isRunning; //!< Is running
bool _updateNeeded;
IPProcessStep* _currentStep; //!< Currently active step, settings shown on the left side
QList<IPProcessStep*> _processList; //!< Ordered process list
int _sequenceCount; //!< Image sequence count
int _sequenceIndex; //!< Current image sequence index
int _lastSequenceIndex; //!< Last image sequence index
bool _isSequenceRunning; //!< Is sequence running
bool _lastProcessSuccess; //!< Last process was successful
bool _stopExecution; //!< Used to stop the execution early
bool _longProcess; //!< Unmeasurable processes must update GUI regularly
IPProcessThread* _thread; //!< Reference to the current thread
// QWidget interface
protected:
virtual void wheelEvent (QWheelEvent *) override;
virtual void resizeEvent (QResizeEvent *) override;
virtual void keyPressEvent (QKeyEvent *) override;
virtual void keyReleaseEvent (QKeyEvent *) override;
virtual void showEvent (QShowEvent *) override;
};
#endif // IPPROCESSGRID_H
//#############################################################################
//
// This file is part of ImagePlay.
//
// ImagePlay is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ImagePlay is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ImagePlay. If not, see <http://www.gnu.org/licenses/>.
//
//#############################################################################
#include "IPProcessGrid.h"
IPProcessGrid::IPProcessGrid(QWidget *parent) : QGraphicsView(parent)
{
_scene = new IPProcessGridScene(this);
setScene(_scene);
setAlignment(Qt::AlignLeft | Qt::AlignTop);
setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
//setCacheMode(QGraphicsView::CacheBackground);
centerOn(0,0);
setDragMode(QGraphicsView::RubberBandDrag);
setMouseTracking(true);
_scale = 1.0;
_isRunning = false;
_isSequenceRunning = true;
_currentStep = NULL;
_stopExecution = false;
_sequenceCount = 0;
_sequenceIndex = 0;
_lastSequenceIndex = 0;
_lastProcessSuccess = false;
_updateNeeded = true;
_thread = NULL;
// add a dummy object to allow correct placement of new objects with drag&drop
scene()->addItem(new QGraphicsRectItem(0,0,0,0));
connect(_scene, &QGraphicsScene::sceneRectChanged, this, &IPProcessGrid::sceneRectChanged);
}
bool IPProcessGrid::sortTreeDepthLessThan(IPProcessStep* s1, IPProcessStep* s2)
{
return s1->treeDepth() < s2->treeDepth();
}
//breath first search
void IPProcessGrid::buildQueue()
{
qDebug() << "IPProcessGrid::buildQueue";
QQueue<IPProcessStep*> tmpQueue;
_processList.clear();
// find source processes
int branchID = 0;
for(auto it = _scene->steps()->begin(); it < _scene->steps()->end(); ++it)
{
IPProcessStep* step = (IPProcessStep*) *it;
IPLProcess* process = step->process();
// attach property changed event handler
process->registerPropertyChangedEventHandler(this);
process->registerOutputsChangedEventHandler(this);
if(process->isSource())
{
step->setTreeDepth(0);
step->setBranchID(branchID++);
_processList.push_back(step);
tmpQueue.enqueue(step);
}
}
// add all other process steps with BFS
int counter = 0;
int limit = 100;
while(!tmpQueue.isEmpty() && counter < limit)
{
IPProcessStep* step = tmpQueue.dequeue();
for(auto it = step->edgesOut()->begin(); it < step->edgesOut()->end(); ++it)
{
IPProcessEdge* edge = (IPProcessEdge*) *it;
IPProcessStep* nextStep = edge->to();
// set depth
nextStep->setTreeDepth(step->treeDepth()+1);
// set branch ID
nextStep->setBranchID(step->branchID());
// add to queue and list
tmpQueue.enqueue(nextStep);
// unique
if(!_processList.contains(nextStep))
{
_processList.push_back(nextStep);
}
}
}
// sort by depth
qSort(_processList.begin(), _processList.end(), IPProcessGrid::sortTreeDepthLessThan);
// et voila, we have the execution order
// move the tabs in the right order
_mainWindow->imageViewer()->sortTabs();
}
int IPProcessGrid::executeThread(IPLProcess* process, IPLImage *image /*= NULL*/, int inputIndex /*= 0*/, bool useOpenCV /*= false*/)
{
qDebug() << "IPProcessGrid::executeThread: " << QString::fromStdString(process->className());
QElapsedTimer timer;
timer.start();
// create new thread
_thread = new IPProcessThread(process, image, inputIndex, useOpenCV);
connect(_thread, &IPProcessThread::progressUpdated, this, &IPProcessGrid::updateProgress);
_mainWindow->setThreadRunning(true);
_mainWindow->imageViewer()->zoomWidget()->zoomUpdateMutex()->lock();
process->setResultReady(false);
process->resetMessages();
_thread->start();
while(!_thread->isFinished())
{
if(_longProcess)
_currentStep->update();
QApplication::processEvents();
}
process->setResultReady(_thread->success());
_mainWindow->setThreadRunning(false);
_mainWindow->imageViewer()->zoomWidget()->zoomUpdateMutex()->unlock();
_lastProcessSuccess = _thread->success();
delete _thread;
_thread = NULL;
// return duration
return timer.elapsed();
}
void IPProcessGrid::propagateNeedsUpdate(IPLProcess* process)
{
qDebug() << "IPProcessGrid::propagateNeedsUpdate: " << QString::fromStdString(process->className());
QQueue<IPProcessStep*> tmpQueue;
// find step from process
for(auto it = _scene->steps()->begin(); it < _scene->steps()->end(); ++it)
{
IPProcessStep* step = (IPProcessStep*) *it;
IPLProcess* tmpProcess = step->process();
if(tmpProcess == process)
{
step->process()->requestUpdate();
tmpQueue.enqueue(step);
break;
}
}
// add all following processes via BFS
int counter = 0;
int limit = 100;
while(!tmpQueue.isEmpty() && counter < limit)
{
// set status
IPProcessStep* step = tmpQueue.dequeue();
for(auto it = step->edgesOut()->begin(); it < step->edgesOut()->end(); ++it)
{
IPProcessEdge* edge = (IPProcessEdge*) *it;
IPProcessStep* nextStep = edge->to();
nextStep->process()->requestUpdate();
// add to queue and list
tmpQueue.enqueue(nextStep);
}
}
}
void IPProcessGrid::propagateResultReady(IPLProcess* process, bool resultReady)
{
qDebug() << "IPProcessGrid::propagateResultReady: " << QString::fromStdString(process->className()) << ", " << (resultReady ? "true" : "false");
QQueue<IPProcessStep*> tmpQueue;
// find step from process
for(auto it = _scene->steps()->begin(); it < _scene->steps()->end(); ++it)
{
IPProcessStep* step = (IPProcessStep*) *it;
IPLProcess* tmpProcess = step->process();
if(tmpProcess == process)
{
step->process()->setResultReady(resultReady);
tmpQueue.enqueue(step);
break;
}
}
// add all following processes via BFS
int counter = 0;
int limit = 100;
while(!tmpQueue.isEmpty() && counter < limit)
{
// set status
IPProcessStep* step = tmpQueue.dequeue();
for(auto it = step->edgesOut()->begin(); it < step->edgesOut()->end(); ++it)
{
IPProcessEdge* edge = (IPProcessEdge*) *it;
IPProcessStep* nextStep = edge->to();
nextStep->process()->setResultReady(resultReady);
// add to queue and list
tmpQueue.enqueue(nextStep);
}
}
_mainWindow->imageViewer()->updateImage();
}
/*!
* rief IPProcessGrid::execute
*/
void IPProcessGrid::execute(bool forcedUpdate /* = false*/)
{
// if no processes yet, then exit
if(_scene->steps()->size() < 1)
{
return;
}
// if already running or nothing has changed, exit
if(_isRunning || !_updateNeeded)
{
return;
}
// prevent user changes during execution
_mainWindow->lockScene();
_isRunning = true;
_sequenceCount = 0;
buildQueue();
int totalDurationMs = 0;
// execute the processes
int counter = 0;
int limit = 10000;
bool blockFailLoop = false;
QList<IPProcessStep*> afterProcessingList;
QListIterator<IPProcessStep *> it(_processList);
while (it.hasNext() && counter < limit)
{
if(_stopExecution)
return;
IPProcessStep* step = it.next();
_currentStep = step;
// make sure the progress bar gets filled
updateProgress(1);
// source processes don't have inputs
if(step->process()->isSource())
{
// execute thread
if(step->process()->updateNeeded() || forcedUpdate)
{
step->process()->resetMessages();
step->process()->beforeProcessing();
int durationMs = executeThread(step->process());
if ( !_lastProcessSuccess ) blockFailLoop = true;
// afterProcessing will be called later
afterProcessingList.append(step);
totalDurationMs += durationMs;
step->setDuration(durationMs);
// update error messages
_mainWindow->updateProcessMessages();
}
}
else
{
if(step->process()->updateNeeded() || forcedUpdate)
{
// execute process once for every input
for(int i=0; i < step->edgesIn()->size(); i++)
{
IPProcessEdge* edge = step->edgesIn()->at(i);
int indexFrom = edge->indexFrom();
int indexTo = edge->indexTo();
IPProcessStep* stepFrom = edge->from();
IPLImage* result = static_cast<IPLImage*>(stepFrom->process()->getResultData(indexFrom));
// invalid result, stopp the execution
if(!result)
{
QString msg("Invalid operation at step: ");
msg.append(QString::fromStdString(stepFrom->process()->title()));
_mainWindow->showMessage(msg, MainWindow::MESSAGE_ERROR);
break;
}
// execute thread
step->process()->resetMessages();
step->process()->beforeProcessing();
int durationMs = executeThread(step->process(), result, indexTo, mainWindow()->useOpenCV());
if ( !_lastProcessSuccess ) blockFailLoop = true;
// afterProcessing will be called later
afterProcessingList.append(step);
totalDurationMs += durationMs;
step->setDuration(durationMs);
// update error messages
_mainWindow->updateProcessMessages();
}
}
}
// make sure the progress bar gets filled
updateProgress(100);
counter++;
}
if(_stopExecution)
return;
// call afterProcessing of all steps which were executed this time
// processes like the camera might request another execution
QListIterator<IPProcessStep *> it2(_processList);
while (it2.hasNext())
{
IPProcessStep* step = it2.next();
step->process()->setUpdateNeeded(false);
}
QListIterator<IPProcessStep *> it3(_processList);
while (it3.hasNext())
{
IPProcessStep* step = it3.next();
step->updateThumbnail();
step->process()->afterProcessing();
}
_updateNeeded = false;
// check to see if any of these items changed while running,
// set _updateNeeded to true if any still need it
// this can happen if a slider is still being dragged after
// a process is started
// blockFailLoop prevents an infinite loop if a process is failing
if ( !blockFailLoop ){
QListIterator<IPProcessStep *> itp(_processList);
while (itp.hasNext())
{
IPProcessStep* step = itp.next();
if (step->process()->updateNeeded() )
{
_updateNeeded = true;
break;
}
}
}
// update images
_mainWindow->imageViewer()->updateImage();
_mainWindow->imageViewer()->showProcessDuration(totalDurationMs);
// update process graph
_mainWindow->updateGraphicsView();
_mainWindow->unlockScene();
_isRunning = false;
_currentStep = NULL;
}
void IPProcessGrid::terminate()
{
qDebug() << "IPProcessGrid::terminate";
if(_thread)
_thread->terminate();
}
void IPProcessGrid::updateProgress(int progress)
{
// enable spinning progress for long operations
_longProcess = (progress < 0);
if(_currentStep)
{
_currentStep->setProgress(progress);
}
}
void IPProcessGrid::sceneRectChanged(const QRectF &rect)
{
fitLargeSceneRect();
}
/*!
* rief IPProcessGrid::zoomIn
*/
void IPProcessGrid::zoomIn()
{
zoomBy(0.1f);
}
/*!
* rief IPProcessGrid::zoomOut
*/
void IPProcessGrid::zoomOut()
{
zoomBy(-0.1f);
}
/*!
* rief IPProcessGrid::zoomBy
* param scaleChange
*/
void IPProcessGrid::zoomBy(float scaleChange)
{
zoomTo(_scale + scaleChange);
}
/*!
* rief IPProcessGrid::zoomTo
* param newScale
*/
void IPProcessGrid::zoomTo(float newScale)
{
newScale = newScale > 3.0 ? 3.0 : newScale;
newScale = newScale < 0.5 ? 0.5 : newScale;
_scale = newScale;
QMatrix matrix;
matrix.scale(_scale, _scale);
setMatrix(matrix);
// update statusbar
_mainWindow->statusBar()->showMessage(QString("Zoom: %1%").arg(QString::number(_scale*100, 'f', 0)));
}
/*!
* rief IPProcessGrid::wheelEvent
* param event
*/
void IPProcessGrid::wheelEvent(QWheelEvent* event)
{
#ifdef Q_OS_MACX
// Qt handles mac trackpads and magic mouse events very badly,
// it is better to just ignore them for now...
return;
#endif
if(event->angleDelta().y() > 0)
zoomIn();
else
zoomOut();
}
/*!
* rief IPProcessGrid::showEvent
*/
void IPProcessGrid::showEvent(QShowEvent *e)
{
//set the scene rect to allow more space when zooming
if ( !e->spontaneous() ){
fitLargeSceneRect();
}
}
/*!
* rief IPProcessGrid::resizeEvent
*/
void IPProcessGrid::resizeEvent(QResizeEvent *)
{
fitLargeSceneRect();
}
/*!
* rief IPProcessGrid::fitLargeSceneRect
*/
void IPProcessGrid::fitLargeSceneRect()
{
qreal width = this->width()*2;
qreal height = this->height()*2;
qreal x = 0;
qreal y = 0;
if ( scene()->sceneRect().width() > width )
width = scene()->sceneRect().width();
if ( scene()->sceneRect().height() > height )
height = scene()->sceneRect().height();
if ( scene()->sceneRect().x() < x )
x = scene()->sceneRect().x();
if ( scene()->sceneRect().y() < y )
y = scene()->sceneRect().y();
setSceneRect(x,y,width,height);
}
/*!
* rief IPProcessGrid::keyPressEvent
* param event
*/
void IPProcessGrid::keyPressEvent(QKeyEvent *event)
{
Qt::KeyboardModifiers modifiers = event->modifiers();
if(event->key() == Qt::Key_Space)
{
setDragMode(QGraphicsView::ScrollHandDrag);
}
if(event->key() == Qt::Key_F && modifiers&Qt::ControlModifier)
{
_mainWindow->hideProcessSettings();
_mainWindow->setFilterFocus();
}
if(event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace)
{
_scene->deleteSelectedItems();
}
if(event->key() == Qt::Key_Escape)
{
_mainWindow->hideProcessSettings();
}
}
/*!
* rief IPProcessGrid::keyReleaseEvent
* param event
*/
void IPProcessGrid::keyReleaseEvent(QKeyEvent* event)
{
Qt::KeyboardModifiers modifiers = QGuiApplication::keyboardModifiers();
if(event->key() == Qt::Key_Space)
{
setDragMode(QGraphicsView::RubberBandDrag);
}
}
void IPProcessGrid::propertyChanged(IPLProcess* process)
{
//process->requestUpdate();
propagateNeedsUpdate(process);
_updateNeeded = true;
_mainWindow->updateProcessMessages();
}
void IPProcessGrid::outputsChanged(IPLProcess *)
{
_mainWindow->imageViewer()->updateOutputs();
}
void IPProcessGrid::setSequenceIndex(int index)
{
_lastSequenceIndex = _sequenceIndex;
_sequenceIndex = index;
}
void IPProcessGrid::requestUpdate()
{
_updateNeeded = true;
}
主要功能
1.1 流程图管理
管理 IPProcessStep
(处理步骤)和 IPProcessEdge
(连接线)的图形化显示。
使用 IPProcessGridScene
(继承自 QGraphicsScene
)作为底层场景,负责渲染所有图形元素。
支持 缩放、平移 和 选择 交互操作。
1.2 流程执行
构建执行队列(buildQueue
):使用 广度优先搜索 (BFS) 确定处理步骤的执行顺序。
执行处理流程(execute
):
检查是否需要更新(_updateNeeded
)。
按顺序执行每个处理步骤(IPProcessStep
)。
使用 IPProcessThread
在单独线程中运行处理逻辑,避免阻塞 UI。
终止执行(terminate
):强制停止当前处理流程。
1.3 状态管理
更新传播:
propagateNeedsUpdate
:当某个处理步骤需要更新时,通知后续步骤。
propagateResultReady
:当某个处理步骤完成时,更新后续步骤的状态。
事件处理:
propertyChanged
:当处理步骤的属性变化时触发更新。
outputsChanged
:当处理步骤的输出变化时更新显示。
1.4 用户交互
缩放:
zoomIn
/ zoomOut
/ zoomBy
/ zoomTo
控制视图缩放。
通过 wheelEvent
支持鼠标滚轮缩放。
键盘控制:
空格键:切换拖动模式(ScrollHandDrag
/ RubberBandDrag
)。
Delete / Backspace:删除选中的处理步骤或连接线。
Ctrl+F:聚焦到过滤器搜索框。
Esc:隐藏处理设置面板。
2. 关键成员变量
变量 | 类型 | 说明 |
---|---|---|
_scene |
IPProcessGridScene* |
管理所有图形项的场景 |
_scale |
float |
当前缩放比例(默认 1.0 ) |
_mainWindow |
MainWindow* |
主窗口引用,用于更新 UI |
_isRunning |
bool |
标记处理流程是否正在执行 |
_updateNeeded |
bool |
标记是否需要重新执行流程 |
_currentStep |
IPProcessStep* |
当前正在执行的处理步骤 |
_processList |
QList<IPProcessStep*> |
按执行顺序存储的处理步骤 |
_sequenceIndex |
int |
当前图像序列索引(用于多帧处理) |
_isSequenceRunning |
bool |
标记图像序列是否正在播放 |
_stopExecution |
bool |
标记是否提前终止执行 |
_thread |
IPProcessThread* |
当前执行线程 |
关键方法解析
3.1 buildQueue()
void IPProcessGrid::buildQueue()
作用:构建处理步骤的执行顺序。
实现逻辑:
使用 BFS(广度优先搜索) 遍历流程图。
从 源节点(无输入的节点) 开始,按层次遍历后续节点。
为每个步骤设置 treeDepth
(处理深度)和 branchID
(分支 ID)。
最终按 treeDepth
排序,确保处理顺序正确。
3.2 execute(bool forcedUpdate)
void IPProcessGrid::execute(bool forcedUpdate = false)
作用:执行整个处理流程。
流程:
检查是否正在运行或无需更新(!_isRunning && _updateNeeded
)。
锁定 UI(_mainWindow->lockScene()
)。
调用 buildQueue()
确定执行顺序。
遍历 _processList
,逐个执行处理步骤:
源节点:直接调用 executeThread
。
非源节点:获取输入数据后调用 executeThread
。
更新 UI(_mainWindow->imageViewer()->updateImage()
)。
解锁 UI(_mainWindow->unlockScene()
)。
3.3 executeThread(IPLProcess*, IPLImage*, int, bool)
int IPProcessGrid::executeThread(IPLProcess* process, IPLImage* image, int inputIndex, bool useOpenCV)
作用:在单独线程中执行单个处理步骤。
流程:
创建 IPProcessThread
线程。
连接 progressUpdated
信号,更新进度条。
启动线程,等待完成。
返回执行时间(用于性能统计)。
3.4 propagateNeedsUpdate(IPLProcess*)
void IPProcessGrid::propagateNeedsUpdate(IPLProcess* process)
作用:当某个步骤需要更新时,通知后续所有依赖步骤。
实现:
使用 BFS 遍历后续步骤,设置 requestUpdate()
标志。
设计模式
观察者模式:
通过 IPLPropertyChangedEventHandler
和 IPLOutputsChangedEventHandler
监听属性变化。
命令模式:
execute()
方法封装了整个处理流程的执行逻辑。
多线程处理:
使用 IPProcessThread
避免阻塞 UI。
典型使用场景
用户添加新处理步骤:
触发 propertyChanged
→ propagateNeedsUpdate
→ requestUpdate
→ execute
。
用户调整参数:
类似流程,确保后续步骤重新计算。
执行整个流程:
调用 execute()
,按 _processList
顺序执行所有步骤。
总结
IPProcessGrid
是 ImagePlay 的 流程引擎核心,负责:
可视化:管理处理步骤和连接线的显示。
执行控制:按正确顺序执行处理逻辑。
状态管理:确保数据一致性和实时更新。
用户交互:支持缩放、选择和键盘控制。
它的设计使得 ImagePlay 能够高效、灵活地处理图像,同时保持 UI 响应流畅。
ImagePlay 算子执行流程详解
ImagePlay 中的算子(Process)执行流程是一个精心设计的系统,用于管理和执行图像处理管道。以下是该执行流程的详细分析:
整体执行流程
用户触发执行:
通过界面操作(如点击执行按钮)或参数修改
触发
IPProcessGrid::execute()
方法准备工作:
检查执行状态(防止重复执行)
锁定界面防止用户修改
调用
buildQueue()
构建执行队列执行处理步骤:
遍历
_processList
中的每个步骤对于每个步骤调用
executeThread()
处理输入输出数据传递
收尾工作:
更新界面显示
解锁界面
清理资源
执行队列构建 (buildQueue()
)
void IPProcessGrid::buildQueue()
{
// 使用BFS算法构建执行队列
QQueue<IPProcessStep*> tmpQueue;
_processList.clear();
// 第一步:找出所有源节点(没有输入的节点)
int branchID = 0;
for(auto it = _scene->steps()->begin(); it < _scene->steps()->end(); ++it) {
IPProcessStep* step = (IPProcessStep*) *it;
if(step->process()->isSource()) {
step->setTreeDepth(0);
step->setBranchID(branchID++);
_processList.push_back(step);
tmpQueue.enqueue(step);
}
}
// 第二步:BFS遍历构建完整队列
while(!tmpQueue.isEmpty()) {
IPProcessStep* step = tmpQueue.dequeue();
// 处理所有输出边连接的节点
for(auto it = step->edgesOut()->begin(); it < step->edgesOut()->end(); ++it) {
IPProcessEdge* edge = (IPProcessEdge*) *it;
IPProcessStep* nextStep = edge->to();
nextStep->setTreeDepth(step->treeDepth()+1);
nextStep->setBranchID(step->branchID());
tmpQueue.enqueue(nextStep);
if(!_processList.contains(nextStep)) {
_processList.push_back(nextStep);
}
}
}
// 第三步:按处理深度排序
qSort(_processList.begin(), _processList.end(), IPProcessGrid::sortTreeDepthLessThan);
}
单个算子的执行 (executeThread()
)
int IPProcessGrid::executeThread(IPLProcess* process, IPLImage *image, int inputIndex, bool useOpenCV)
{
// 创建并配置执行线程
_thread = new IPProcessThread(process, image, inputIndex, useOpenCV);
// 连接进度信号
connect(_thread, &IPProcessThread::progressUpdated,
this, &IPProcessGrid::updateProgress);
// 准备执行环境
_mainWindow->setThreadRunning(true);
process->setResultReady(false);
process->resetMessages();
// 启动线程并等待完成
_thread->start();
while(!_thread->isFinished()) {
QApplication::processEvents(); // 保持UI响应
}
// 处理执行结果
process->setResultReady(_thread->success());
_lastProcessSuccess = _thread->success();
// 清理线程
delete _thread;
_thread = NULL;
return timer.elapsed(); // 返回执行时间
}
数据流管理
输入数据获取:
对于非源算子,从输入连接获取前驱节点的输出数据
IPLImage* result = static_cast<IPLImage*>(stepFrom->process()->getResultData(indexFrom));
输出数据处理:
算子执行完成后,结果存储在 IPLProcess::_resultData
中
通过 getResultData()
方法供后续算子使用
数据传递机制:
使用智能指针管理图像数据生命周期
数据在算子间传递时采用引用计数,避免不必要的拷贝
执行控制机制
更新传播机制:
当算子参数改变时,调用 propertyChanged()
触发 propagateNeedsUpdate()
向后传播更新需求
最终导致整个受影响子图的重新执行
执行状态管理:
_isRunning
标记执行状态
_stopExecution
用于提前终止执行
_updateNeeded
标记是否需要重新执行
错误处理:
每个算子执行后检查 _lastProcessSuccess
失败时终止后续执行并显示错误信息
性能优化
增量执行:
只有标记为需要更新的算子才会被执行
通过 updateNeeded
机制实现智能更新
多线程处理:
每个算子在独立线程中执行
主线程保持UI响应
缓存机制:
算子结果缓存避免重复计算
参数未改变时直接使用缓存结果
特殊处理
源算子处理:
没有输入,直接产生数据(如摄像头、图像文件读取)
多输入算子:
遍历所有输入连接,依次处理每个输入
分支流程:
通过 branchID
标记不同执行分支
保证分支间的正确执行顺序
这个执行流程设计使得ImagePlay能够高效地处理复杂的图像处理管道,同时保持灵活性和响应性。
暂无评论内容