基本思路:继承itk::Command类,重写Excute函数,利用itk的观察者模式获取耗时操作的进度数据,然后转化成Qt的信号发送出去。
而要在Excute函数内发送信号,就得利用clientData将报告进度的Reporter类(继承自QObject)对象指针传到Excute函数内部,所以选择itk::CStyleCommand来继承。代码如下:
class FIVISION_DECLSPEC ProgressCommand : public itk::CStyleCommand
{
public:
itkNewMacro(ProgressCommand);
void Execute(itk::Object *caller, const itk::EventObject &event) override
{
Execute((const itk::Object *)caller, event);
}
void Execute(const itk::Object *caller, const itk::EventObject &event) override
{
if (!itk::ProgressEvent().CheckEvent(&event)) {
return;
}
const auto *processObject = dynamic_cast<const itk::ProcessObject *>(caller);
if (!processObject) {
return;
}
ItkProgressReporter *reporter = (ItkProgressReporter *)m_ClientData;
reporter->progressChanged(processObject->GetProgress());
}
};
用于报告进度的ItkProgressReporter类,继承自QObject,它有一个progressChanged信号,发送一个float值。在添加观察者之前,把它的指针作为clientData设置给ProgressCommand类,然后在Excute函数内进行类型转换并发送信号。ItkProgressReporter类实现如下:
class FIVISION_DECLSPEC ItkProgressReporter : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(ItkProgressReporter);
private:
ProgressCommand::Pointer m_command;
signals:
void progressChanged(float);
public:
ItkProgressReporter(QObject *parent = nullptr) : QObject(parent)
{
m_command = ProgressCommand::New();
m_command->SetClientData(this);
}
~ItkProgressReporter() {}
ProgressCommand::Pointer getCommand() { return m_command; }
};
以读取三维DIOCM图像为例,使用时,用ItkProgressReporter内的command指针给reader添加观察者,观察itk::ProgressEvent事件,然后连接ItkProgressReporter的progressChanged信号即可。
m_imageIO = itk::GDCMImageIO::New();
m_reader = itk::ImageSeriesReader<Image3DType>::New();
m_reader->SetImageIO(m_imageIO);
m_reproter = new ItkProgressReporter();
m_reader->AddObserver(itk::ProgressEvent(), m_reproter->getCommand());
connect(m_reproter, &ItkProgressReporter::progressChanged, this, &ImageSeriesReader3D::progressChanged);
为了不卡住主线程,还可以将读图操作放到子线程执行,这里基于QThread写了一个QuickItkImageSeriesReader3D类,因为项目前端架构用的Qml,所以多了一些C++注册自定义Qml类型的代码。基本思路是按照Qt官方推荐的多线程写法,将需要在子线程内执行的操作封装在Worker类里面,再写一个Controller类通过信号槽来控制子线程的行为。这里的QuickItkImageSeriesReader3D类对应Controller,ImageSeriesReader3D类对应Worker。
完整代码如下:
fiQuickItkImageSeriesReader3D.h文件:
#ifndef _FI_QUICK_ITK_IMAGESERIESREADER3D_H_
#define _FI_QUICK_ITK_IMAGESERIESREADER3D_H_
#include "fiItkProgressReporter.h"
#include "fiQuickItkImage3D.h"
#include "fiVisionGlobal.h"
#include <QList>
#include <QThread>
#include <itkChangeInformationImageFilter.h>
#include <itkGDCMImageIO.h>
#include <itkGDCMSeriesFileNames.h>
#include <itkImageSeriesReader.h>
class ImageSeriesReader3D;
class QuickItkImageSeriesReader3D : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(QuickItkImageSeriesReader3D);
Q_PROPERTY(QuickItkImage3D *output READ getOutput NOTIFY outputChanged);
Q_PROPERTY(QStringList fileNames READ getFileNames WRITE setFileNames NOTIFY fileNamesChanged);
Q_PROPERTY(QString inputDirectory READ getInputDirectory WRITE setInputDirectory NOTIFY inputDirectoryChanged);
Q_PROPERTY(float progress READ getProgress NOTIFY progressChanged);
QML_ELEMENT
private:
ImageSeriesReader3D *m_imageReader;
QThread m_thread;
QuickItkImage3D *m_outputImageData{nullptr};
protected:
float m_progress{0};
signals:
void outputChanged();
void fileNamesChanged(QStringList);
void inputDirectoryChanged(QString);
void doRead(QuickItkImage3D *);
void progressChanged(float);
public:
Q_INVOKABLE QuickItkImageSeriesReader3D(QObject *parent = nullptr);
~QuickItkImageSeriesReader3D();
Q_INVOKABLE QuickItkImage3D *getOutput();
Q_INVOKABLE bool setFileNames(QStringList fileNames);
Q_INVOKABLE QStringList getFileNames();
Q_INVOKABLE bool setInputDirectory(QString inputDirectory);
Q_INVOKABLE QString getInputDirectory();
Q_INVOKABLE float getProgress();
private slots:
bool setProgress(float progress);
};
class ImageSeriesReader3D : public QObject
{
Q_OBJECT
private:
itk::ImageSeriesReader<Image3DType>::Pointer m_reader{nullptr};
itk::GDCMImageIO::Pointer m_imageIO{nullptr};
itk::GDCMSeriesFileNames::Pointer m_seriesFileNames{nullptr};
ItkProgressReporter *m_reproter{nullptr};
protected:
QStringList m_fileNames;
QString m_inputDirectory;
bool m_isInputDirectory{false};
signals:
void outputChanged();
void progressChanged(float);
public slots:
bool update(QuickItkImage3D *outputImage);
public:
ImageSeriesReader3D(QObject *parent = nullptr);
~ImageSeriesReader3D() {}
public:
bool setFileNames(QStringList fileNames);
QStringList getFileNames();
bool setInputDirectory(QString inputDirectory);
QString getInputDirectory();
};
#endif // !_FI_QUICK_ITK_IMAGESERIESREADER3D_H_
fiQuickItkImageSeriesReader3D.cpp文件:
#include "fiQuickItkImageSeriesReader3D.h"
QuickItkImageSeriesReader3D::QuickItkImageSeriesReader3D(QObject *parent) : QObject(parent)
{
m_outputImageData = new QuickItkImage3D(nullptr, this);
m_imageReader = new ImageSeriesReader3D();
m_imageReader->moveToThread(&m_thread);
connect(&m_thread, &QThread::finished, m_imageReader, &QObject::deleteLater);
connect(this, &QuickItkImageSeriesReader3D::doRead, m_imageReader, &ImageSeriesReader3D::update);
connect(m_imageReader, &ImageSeriesReader3D::outputChanged, this, &QuickItkImageSeriesReader3D::outputChanged);
connect(m_imageReader, &ImageSeriesReader3D::progressChanged, this, &QuickItkImageSeriesReader3D::setProgress);
m_thread.start();
}
QuickItkImageSeriesReader3D::~QuickItkImageSeriesReader3D()
{
m_thread.quit();
m_thread.wait();
}
QuickItkImage3D *QuickItkImageSeriesReader3D::getOutput()
{
return m_outputImageData;
}
bool QuickItkImageSeriesReader3D::setFileNames(QStringList fileNames)
{
if (fileNames == m_imageReader->getFileNames())
return false;
m_imageReader->setFileNames(fileNames);
emit fileNamesChanged(fileNames);
emit doRead(m_outputImageData);
return true;
}
QStringList QuickItkImageSeriesReader3D::getFileNames()
{
return m_imageReader->getFileNames();
}
bool QuickItkImageSeriesReader3D::setInputDirectory(QString inputDirectory)
{
if (inputDirectory == m_imageReader->getInputDirectory())
return false;
m_imageReader->setInputDirectory(inputDirectory);
emit inputDirectoryChanged(inputDirectory);
emit doRead(m_outputImageData);
return true;
}
QString QuickItkImageSeriesReader3D::getInputDirectory()
{
return m_imageReader->getInputDirectory();
}
float QuickItkImageSeriesReader3D::getProgress()
{
return m_progress;
}
bool QuickItkImageSeriesReader3D::setProgress(float progress)
{
m_progress = progress;
emit this->progressChanged(progress);
return true;
}
//=======================ImageSeriesReader3D=========================//
ImageSeriesReader3D::ImageSeriesReader3D(QObject *parent) : QObject(parent)
{
m_reader = itk::ImageSeriesReader<Image3DType>::New();
m_imageIO = itk::GDCMImageIO::New();
m_seriesFileNames = itk::GDCMSeriesFileNames::New();
m_reproter = new ItkProgressReporter();
m_reader->SetImageIO(m_imageIO);
m_reader->UseStreamingOff();
m_reader->AddObserver(itk::ProgressEvent(), m_reproter->getCommand());
connect(m_reproter, &ItkProgressReporter::progressChanged, this, &ImageSeriesReader3D::progressChanged);
}
bool ImageSeriesReader3D::setFileNames(QStringList fileNames)
{
m_fileNames = fileNames;
m_isInputDirectory = false;
return true;
}
QStringList ImageSeriesReader3D::getFileNames()
{
return m_fileNames;
}
bool ImageSeriesReader3D::setInputDirectory(QString inputDirectory)
{
m_inputDirectory = inputDirectory;
m_isInputDirectory = true;
return true;
}
QString ImageSeriesReader3D::getInputDirectory()
{
return m_inputDirectory;
}
bool ImageSeriesReader3D::update(QuickItkImage3D *outputImage)
{
std::vector<std::string> fileNamesVector;
if (m_isInputDirectory) {
m_seriesFileNames->SetInputDirectory(m_inputDirectory.toStdString());
fileNamesVector = m_seriesFileNames->GetInputFileNames();
} else {
for (const QString &qstr : m_fileNames) {
fileNamesVector.push_back(qstr.toStdString());
}
}
if (fileNamesVector.empty()) {
return false;
}
m_reader->SetFileNames(fileNamesVector);
try {
m_reader->Update();
} catch (itk::ExceptionObject &e) {
std::cout << "ImageSeriesReader3D::update(): itk::Exception: " << std::endl;
std::cout << e.what() << std::endl;
return false;
}
outputImage->setStorage(m_reader->GetOutput());
emit this->outputChanged();
return true;
}
fiQuickItkImage3D.h文件:
#ifndef _QUICK_ITK_fiIMAGE3D_H_
#define _QUICK_ITK_fiIMAGE3D_H_
#include "fiVisionGlobal.h"
#include <QObject>
#include <QtQml/qqml.h>
using SSPixelType = signed short;
const unsigned short Image3DDimention = 3;
using Image3DType = itk::Image<SSPixelType, Image3DDimention>;
class QuickItkImage3D : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(QuickItkImage3D);
QML_ELEMENT
QML_UNCREATABLE("QuickItkImage3D is uncreatable.")
private:
Image3DType::Pointer m_storage{nullptr};
protected:
QuickItkImage3D(QObject *parent = nullptr)
: QuickItkImage3D(Image3DType::New(), parent) {}
public:
QuickItkImage3D(Image3DType *object, QObject *parent = nullptr)
: QObject(parent) { m_storage = object; }
~QuickItkImage3D() {}
void setStorage(Image3DType *storage) { m_storage = storage; }
Image3DType *getStorage() { return this->m_storage; }
QString getNameOfClass() { return QString(m_storage->GetNameOfClass()); }
};
#endif // !_QUICK_ITK_fiIMAGE2D_H_
fiItkProgressReporter.h文件:
#ifndef _FI_ITK_PROGRESS_REPORTER_H_
#define _FI_ITK_PROGRESS_REPORTER_H_
#include <QObject>
#include <itkCommand.h>
class ProgressCommand : public itk::CStyleCommand
{
public:
itkNewMacro(ProgressCommand);
void Execute(itk::Object *caller, const itk::EventObject &event) override
{
Execute((const itk::Object *)caller, event);
}
void Execute(const itk::Object *caller, const itk::EventObject &event) override;
};
class ItkProgressReporter : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(ItkProgressReporter);
private:
ProgressCommand::Pointer m_command;
signals:
void progressChanged(float);
public:
ItkProgressReporter(QObject *parent = nullptr) : QObject(parent)
{
m_command = ProgressCommand::New();
m_command->SetClientData(this);
}
~ItkProgressReporter() {}
ProgressCommand::Pointer getCommand() { return m_command; }
};
#endif // !_FI_ITK_EVENT_DISPATCHER_H_
fiItkProgressReporter.cpp文件:
#include "fiItkProgressReporter.h"
void ProgressCommand::Execute(const itk::Object *caller, const itk::EventObject &event)
{
if (!itk::ProgressEvent().CheckEvent(&event)) {
return;
}
const auto *processObject = dynamic_cast<const itk::ProcessObject *>(caller);
if (!processObject) {
return;
}
ItkProgressReporter *reporter = (ItkProgressReporter *)m_ClientData;
reporter->progressChanged(processObject->GetProgress());
// std::cout << "Progress: " << processObject->GetProgress() << std::endl;
}