Qt编程:IPProcessGrid 类

   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能够高效地处理复杂的图像处理管道,同时保持灵活性和响应性。

 

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容