文章目录

  • 开发环境
  • Visual Studio Qt 选项
  • 创建 Qt Widgets 程序
  • 设计界面
  • 配置 DCMTK 路径
  • 编写代码
    • 图像数据模型类
    • 序列数据模型类
    • 读取 DICOM 文件
    • 图像显示视图类
      • 提升窗口部件
    • 界面交互
  • 编译程序
  • 运行程序
  • 源码下载
  • 参考

开发环境

  • DCMTK 3.6.4
  • Qt 5.14.2
  • Visual Studio 2015
  • Qt Visual Studio Tools 2.6.0

DCMTK 使用 《编译 DCMTK DLL(DCMTK 3.6.4 + VS2015 + Win10)》 一文所生成的 DLL。

Qt 下载地址:http://download.qt.io/archive/qt/
从 Qt 5.15 版本开始,开源版的 Qt 已经不提供离线安装包了,仅支持在线安装。
本文使用 Qt 5.14.2,是最后一个提供离线安装包的开源版。
Qt 5.14.2 离线安装包的下载页面:
http://download.qt.io/archive/qt/5.14/5.14.2/qt-opensource-windows-x86-5.14.2.exe.mirrorlist
可选择国内镜像下载,可提高下载速度。
安装 qt-opensource-windows-x86-5.14.2.exe 时会要求登录 Qt 账号,如果没有 Qt 账号或不想登录,可先断网再安装,就不会要求登录了。

Qt Visual Studio Tools 2.6.0 下载地址:http://download.qt.io/archive/vsaddin/2.6.0/

Visual Studio Qt 选项

启动 Visual Studio,点击菜单【Qt VS Tools > Qt Options】,打开 Qt 选项窗口。确保存在如图设置。


如果 Visual Studio 没有 【Qt VS Tools】菜单,说明没有安装 Qt Visual Studio Tools。

创建 Qt Widgets 程序

点击菜单【文件 > 新建 > 项目】,打开“新建项目”窗口,选择 Qt Widgets Application


输入项目名称,点击【确定】后,进入 Qt Widgets Application Wizard 界面。



点击【Finish】按钮创建项目。创建完成的项目如下图:

设计界面

双击 DcmtkDemo.ui 文件,打开 Qt 设计师。

向窗体上拖拽一个 Vertical Layout 控件和一个 Graphics View 控件,Vertical Layout 放左侧,Graphics View 放右侧。
按住 Ctrl 键,同时选中 Vertical LayoutGraphics View,然后点击工具条上的【使用分裂器水平布局】按钮。如下图:


然后点击窗体空白处,再点击工具条上的【水平布局】按钮,使控件充满整个窗体。如下图:


再依次向 Vertical Layout 里拖拽 Push ButtonList WidgetProgress Bar 三个控件,从上到下顺序排列。

  • 设置 Push ButtonobjectNamebtnOpenFoldertext打开文件夹
  • 设置 List WidgetobjectNamelstSeriesList
  • 设置 Progress Barvalue0,取消勾选 textVisible
  • 选中 Vertical Layout,点击右键,选择菜单【变型为 > QFrame】,设置 minimumSize 宽度为 300maximumSize 宽度为 500


设计完成的界面结构如下图:

配置 DCMTK 路径

编写代码前,应先配置好 DCMTK 的 include 路径,免得编码时提示找不到 DCMTK 头文件。

DCMTK 由《编译 DCMTK DLL(DCMTK 3.6.4 + VS2015 + Win10)》生成,安装路径位于 D:\dcmtk-3.6.4-install

DcmtkDemo 项目名称上点击鼠标右键,在弹出菜单上选择 [属性],打开项目属性页对话框。选择[配置属性 > VC++ 目录],在[包含目录]里添加 D:\dcmtk-3.6.4-install\include


一般在添加 include 目录时,也会顺手把 lib 文件添加上。但 lib 文件编译程序前加上就可以,此处先不添加。

编写代码

代码主要由下面几部分组成:

  • 界面交互代码
  • DICOM 文件读取代码
  • 图像数据模型类
  • 序列数据模型类
  • 图像显示视图类

图像数据模型类

我们将定义一个 ImageData 类,用于表示图像数据模型。一个 ImageData 对象,表示一幅图像。

ImageData 类是本程序的一个核心类。这个类主要有两个功能:

  • 调用 DCMTK 库,使用 DcmFileFormat 从磁盘读取一个 DICOM 文件,生成 DicomImage 对象。
  • DicomImage 对象转换成 QPixmap 对象,交由图像视图类显示。

ImageData 类有三个核心函数:readDicomFile()dicomImageToPixmap()ucharArrayToPixmap()

  • readDicomFile() 用于读取 DICOM 头信息,及生成 DicomImage 对象。
  • dicomImageToPixmap()ucharArrayToPixmap() 是两个静态函数,通过调用 DicomImage 类的 createWindowsDIB() 函数获得 BMP 位图数据,然后转换成 QPixmap 对象。

ImageData 类头文件定义如下:

#pragma once
#include <QString>
#include <QPixmap>#include "dcmtk/dcmdata/dcfilefo.h"
#include "dcmtk/dcmimgle/dcmimage.h"class ImageData
{public:explicit ImageData(const QString &filename);~ImageData();//判断是否读取成功bool isNormal() const{return _pDcmImage && (_pDcmImage->getStatus() == EIS_Normal);}QString getSeriesUID() const{return _seriesUID;}int getInstanceNumber(){return _instanceNumber.toInt();}//获取窗宽窗位void getWindow(double &center, double &width) const{center = _winCenter; width = _winWidth;}//设置窗宽窗位void setWindow(const double &center, const double &width){_winCenter = center; _winWidth = width;}bool getPixSpacing(double & spacingX, double & spacingY, double & spacingZ) const;bool getPixmap(QPixmap & pixmap); //得到该图像的位图 pixmapstatic bool dicomImageToPixmap(DicomImage & dcmImage, QPixmap & pixmap);static bool ucharArrayToPixmap(uchar *data, int w, int h, int bitSize, QPixmap & pixmap, int biBitCount = 8);private:QString _filename;DcmFileFormat _dcmFile;DicomImage* _pDcmImage;//图像所属序列的唯一标识QString _seriesUID;//图像在序列中的编号(位置)QString _instanceNumber;//像素间距,用于显示比例double _spaceX, _spaceY, _spaceZ;//窗宽、窗位double _winWidth, _winCenter;//图像宽度、高度int _imageWidth, _imageHeight;void readDicomFile(const QString &filename);
};

核心函数定义如下:

void ImageData::readDicomFile(const QString &filename)
{OFCondition oc = _dcmFile.loadFile(OFFilename(filename.toLocal8Bit()));if (oc.bad()){qDebug() << "Fail:" << oc.text();return;}_filename = filename;DcmDataset *dataset = 0;OFCondition result;const char *value = nullptr;if (!(dataset = _dcmFile.getDataset()))return;// 头信息读取result = dataset->findAndGetString(DCM_SeriesInstanceUID, value);if (result.bad())return;_seriesUID = QString::fromLatin1(value);result = dataset->findAndGetString(DCM_InstanceNumber, value);if (result.bad())return;_instanceNumber = QString(value);result = dataset->findAndGetFloat64(DCM_PixelSpacing, _spaceX, 1);if (result.bad())_spaceX = 1;result = dataset->findAndGetFloat64(DCM_PixelSpacing, _spaceY, 0);if (result.bad())_spaceY = 1;result = dataset->findAndGetFloat64(DCM_SliceThickness, _spaceZ);if (result.bad())_spaceZ = 1;result = dataset->findAndGetFloat64(DCM_WindowWidth, _winWidth);result = dataset->findAndGetFloat64(DCM_WindowCenter, _winCenter);// 创建DcmImage/********解压缩**********/std::string losslessTransUID = "1.2.840.10008.1.2.4.70";std::string lossTransUID = "1.2.840.10008.1.2.4.51";std::string losslessP14 = "1.2.840.10008.1.2.4.57";std::string lossyP1 = "1.2.840.10008.1.2.4.50";std::string lossyRLE = "1.2.840.10008.1.2.5";E_TransferSyntax xfer = dataset->getOriginalXfer();const char* transferSyntax = nullptr;_dcmFile.getMetaInfo()->findAndGetString(DCM_TransferSyntaxUID, transferSyntax);if (transferSyntax == losslessTransUID || transferSyntax == lossTransUID ||transferSyntax == losslessP14 || transferSyntax == lossyP1){//对压缩的图像像素进行解压DJDecoderRegistration::registerCodecs();dataset->chooseRepresentation(EXS_LittleEndianExplicit, nullptr);DJDecoderRegistration::cleanup();}else if (transferSyntax == lossyRLE){DcmRLEDecoderRegistration::registerCodecs();dataset->chooseRepresentation(EXS_LittleEndianExplicit, nullptr);DcmRLEDecoderRegistration::cleanup();}_pDcmImage = new DicomImage(&_dcmFile, dataset->getOriginalXfer(), CIF_TakeOverExternalDataset);if (_pDcmImage->getStatus() == EIS_Normal){_imageWidth = _pDcmImage->getWidth();_imageHeight = _pDcmImage->getHeight();if (_winWidth < 1){// 设置窗宽窗位_pDcmImage->setRoiWindow(0, 0, _imageWidth, _imageHeight);// 重新对winCenter, winWidth赋值_pDcmImage->getWindow(_winCenter, _winWidth);}}
}
bool ImageData::dicomImageToPixmap(DicomImage& dcmImage, QPixmap & pixmap)
{bool res = true;void *pDIB = nullptr;int size = 0;if (dcmImage.isMonochrome()){// 灰度图像size = dcmImage.createWindowsDIB(pDIB, 0, 0, 8, 1, 1);if (!pDIB)return false;res = ucharArrayToPixmap((uchar *)pDIB, dcmImage.getWidth(), dcmImage.getHeight(), size, pixmap);}else{// RGB图像size = dcmImage.createWindowsDIB(pDIB, 0, 0, 24, 1, 1);if (!pDIB)return false;res = ucharArrayToPixmap((uchar *)pDIB, dcmImage.getWidth(), dcmImage.getHeight(), size, pixmap, 24);}delete pDIB;return res;
}
bool ImageData::ucharArrayToPixmap(uchar *data, int w, int h, int bitSize, QPixmap & pixmap, int biBitCount)
{//位图文件由四部分依序组成:BITMAPFILEHEADER,BITMAPINFOHEADER,调色板,Image Data。BITMAPFILEHEADER lpfh;// 文件头  固定的14个字节, 描述文件的有关信息BITMAPINFOHEADER lpih;// 固定的40个字节,描述图像的有关信息RGBQUAD palette[256];// 调色板RGBQUAD的大小就是256memset(palette, 0, sizeof(palette));for (int i = 0; i < 256; ++i) {palette[i].rgbBlue = i;palette[i].rgbGreen = i;palette[i].rgbRed = i;}memset(&lpfh, 0, sizeof(BITMAPFILEHEADER));lpfh.bfType = 0x4d42;//'B''M' must be 0x4D42.//the sum bits of BITMAPFILEHEADER,BITMAPINFOHEADER and RGBQUAD;the index byte of the image data.lpfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(palette);memset(&lpih, 0, sizeof(BITMAPINFOHEADER));lpih.biSize = sizeof(BITMAPINFOHEADER); //the size of this struct. it is 40 bytes.lpih.biWidth = w;lpih.biHeight = h;lpih.biCompression = BI_RGB;lpih.biPlanes = 1; //must be 1. void *pDIB = data;int size = bitSize;lpih.biBitCount = biBitCount;//the size of the whole bitmap file.lpfh.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(palette) + size;QByteArray bmp;bmp.append((char*)&lpfh, sizeof(BITMAPFILEHEADER));bmp.append((char*)&lpih, sizeof(BITMAPINFOHEADER));bmp.append((char*)palette, sizeof(palette));bmp.append((char*)pDIB, size);return pixmap.loadFromData(bmp);
}

序列数据模型类

定义一个 SeriesData 类,其主要目的是为了表示属于同一个序列的图像集合。

SeriesData 类头文件定义如下:

#pragma once
#include <QList>
#include <QMap>
#include "ImageData.h"class SeriesData
{public:explicit SeriesData(const QString seriesUID);~SeriesData();static QList<SeriesData*> SeriesList;QMap<int, ImageData*> images;QString getSeriesUID() const{return _seriesUID;}// 窗宽窗位void getDefaultWindow(double &center, double &width) const{center = _defaultCenter; width = _defaultWidth;}void appendImage(ImageData* pImage);private:QString _seriesUID;double _defaultCenter, _defaultWidth;  //默认窗位窗宽
};

读取 DICOM 文件

读取磁盘文件是一个耗时的操作,为了防止界面失去响应,一般这种 IO 操作都放在单独的线程里执行。

本来读取文件只需写一个函数就可以,但为了使用 QObject::moveToThread() 函数实现多线程,我们将定义一个 ReadWorker 类来读取 DICOM 文件。

DcmtkDemo 项目名称上点击鼠标右键,在弹出菜单上选择 [添加 > Add Qt Class…],打开 Add Class 对话框。


选择 Qt Class,输入类名称,然后点击【Add】按钮。


无须改变向导页的信息,点击【Finish】按钮,添加 ReadWorker 类。

ReadWorker 类主要实现了一个槽和两个信号,头文件定义如下:

#pragma once#include <QObject>class ReadWorker : public QObject
{Q_OBJECTpublic:ReadWorker(QString dicomFolder, QObject *parent = nullptr);~ReadWorker();public slots:void readDicomFiles();  //检查文件夹下Dicom序列个数,应在单独的线程运行signals:void progress(int);        //发送进度(0-100)void finish();           //线程结束信号private:QString _dicomFolder;   //包含多个 Dicom 单文件的文件夹,可能有多个序列。
};

readDicomFiles() 定义如下:

void  ReadWorker::readDicomFiles()
{SeriesData::SeriesList.clear();QDir dir(_dicomFolder);QStringList files = dir.entryList(QDir::Files);int filesCount = files.count(); //所有文件的个数ImageData* pImgData = nullptr;  //单Dicom文件int progressValue = 0;QString seriesUID;//遍历每个文件foreach(QString fileName, files){++progressValue;emit progress(100 * progressValue / filesCount/* 转到0-100之间*/);//带路径的文件名fileName = _dicomFolder + "/" + fileName;//使用我们写的ImageData类读取dicompImgData = new ImageData(fileName);if (!pImgData->isNormal()){delete pImgData;pImgData = nullptr;continue;  //读取不成功(不是Dicom文件),跳出直接读取下一个文件}seriesUID = pImgData->getSeriesUID();  //获取该序列的 UIDbool found = false;for (size_t i = 0; i < SeriesData::SeriesList.size(); i++){if (SeriesData::SeriesList.at(i)->getSeriesUID() == seriesUID){SeriesData::SeriesList.at(i)->appendImage(pImgData);found = true;}}if (false == found){SeriesData* pSeriesData = new SeriesData(seriesUID);pSeriesData->appendImage(pImgData);SeriesData::SeriesList.append(pSeriesData);}}emit finish();
}

读取 DICOM 文件过程中,还有两个与界面相关的操作:

  • 读取文件过程中,更新进度条。
  • 读取文件完成后,打开图像。

这两个操作将在“界面交互”一节中介绍。

图像显示视图类

图像视图类封装了图像显示及所有与图像交互的动作,是本程序的核心类。

DcmtkDemo 项目名称上点击鼠标右键,在弹出菜单上选择 [添加 > Add Qt Class…],打开 Add Class 对话框。


选择 Qt Class,输入类名称,然后点击【Add】按钮,进入向导界面。


修改 Base classQGraphicsView,修改 Constructor signatureQWidget *parent,点击【Finish】按钮,添加 DicomView 类。

DicomView 类继承自 QGraphicsView,包含如下控件:

  • 一个 QGraphicsPixmapItem,用于显示图像。
  • 两个 QGraphicsSimpleTextItem,用于显示图像窗宽窗位和当前图像索引。
  • 一个 QSlider,在图像上显示一个滑动条,滑块指示当前图像索引位置,拖拽滑块可切换图像。

另外,重写了 QGraphicsViewresizeEventwheelEvent 两个函数。

重写 resizeEvent 是为了响应窗口缩放事件。当缩放窗口时,缩放图像并使图像居中显示,同时调整窗宽窗位标签和图像索引标签的位置。

重写 wheelEvent 是为了响应鼠标滚轮事件。滚动鼠标滚轮可切换图像,并改变滑块位置。

DicomView 类有两个核心函数:loadSeries()updateView(),分别用于加载序列和更新视图。

DicomView 类头文件定义如下:

#pragma once#include <QGraphicsView>
#include <QSlider>#include "SeriesData.h"class DicomView : public QGraphicsView
{Q_OBJECTpublic:DicomView(QWidget *parent);~DicomView();void loadSeries(QString &seriesUID);void updateView();private:void resizePixmapItem();   //窗口缩放时,缩放图像void repositionAuxItems(); //窗口缩放时,改变标签位置public slots:void setCurFrameItem(int);protected:void resizeEvent(QResizeEvent *event);  //响应窗口缩放事件void wheelEvent(QWheelEvent *event);    //响应滚轮事件private:QGraphicsScene* _pScene;                 //场景QGraphicsPixmapItem* _pPixmapItem;       //图像项 场景中的图像QGraphicsSimpleTextItem* _pWLValueItem;  //文本项 显示当前窗宽窗位值QGraphicsSimpleTextItem* _pCurFrameItem; //文本项 显示当前帧索引QGraphicsWidget* _pGraphicsSlider;       //滑动条QSlider* _pSlider;                       //控制切片位置的控件QGraphicsProxyWidget* _pProxyWidget;QString _seriesUID;SeriesData* _pSeriesData;int _currImageIndex;double _fixFactor;  // xspace/yspace 宽高的比例
};

DicomView 类核心函数定义如下:

void DicomView::loadSeries(QString &seriesUID)
{SeriesData* pSeriesData = nullptr;for (size_t i = 0; i < SeriesData::SeriesList.size(); i++){if (SeriesData::SeriesList.at(i)->getSeriesUID() == seriesUID){pSeriesData = SeriesData::SeriesList.at(i);}}if (nullptr == pSeriesData){QMessageBox::critical(this, QStringLiteral("加载错误"), QStringLiteral("序列不存在。"));return;}if (pSeriesData->images.size() == 0){return;}this->_seriesUID = seriesUID;this->_pSeriesData = pSeriesData;this->_currImageIndex = 0;double xSpacing = 0, ySpacing = 0, zSpacing = 0;if (pSeriesData->images.values().at(this->_currImageIndex)->getPixSpacing(xSpacing, ySpacing, zSpacing)) {if (xSpacing > 0.000001 && ySpacing > 0.000001) {double psX = xSpacing;double psY = ySpacing;_fixFactor = psY / psX;}}_pSlider->setMaximum(_pSeriesData->images.size() - 1);_pSlider->setValue(_currImageIndex);double winWidth, winCenter;_pSeriesData->getDefaultWindow(winCenter, winWidth);_pWLValueItem->setText(tr("W:%1, L:%2").arg(winWidth).arg(winCenter));_pPixmapItem->setPos(0, 0);_pPixmapItem->setRotation(0);_pPixmapItem->resetTransform();updateView();_pScene->update(_pScene->sceneRect());
}
void DicomView::updateView()
{if (nullptr == _pSeriesData){return;}QPixmap pixmap;if (_pSeriesData->images.size() > this->_currImageIndex && _pSeriesData->images.values().at(this->_currImageIndex)->isNormal()) {double winWidth, winCenter;_pSeriesData->getDefaultWindow(winCenter, winWidth);_pSeriesData->images.values().at(this->_currImageIndex)->setWindow(winCenter, winWidth);_pSeriesData->images.values().at(this->_currImageIndex)->getPixmap(pixmap);_pPixmapItem->setPixmap(pixmap);_pPixmapItem->setTransformOriginPoint(_pPixmapItem->boundingRect().center());_pCurFrameItem->setText(tr("%1 / %2").arg(_currImageIndex + 1).arg(_pSeriesData->images.size()));}else {_pPixmapItem->setPixmap(pixmap);_pCurFrameItem->setText("");_pWLValueItem->setText("");}resizePixmapItem();repositionAuxItems();
}

注意:当使用 QGraphicsScene::addWidget() 函数将 QSlider 添加到 QGraphicsView 后,在 5.14.0 版本 Qt 上,会出现程序界面关闭后,程序进程不能退出的现象。后来更换 5.14.2 版本 Qt 后,此问题不再出现。

提升窗口部件

双击 DcmtkDemo.ui 文件,打开 Qt 设计师。

DcmtkDemo 窗口上,鼠标右键点击 QGraphicsView 控件,在弹出菜单上选择【提升为…】:


在打开的 提升的窗口部件 对话框中,在 提升的类名称 框输入 DicomView,同时下面的 头文件 框会自动输入 dicomview.h


点击【添加】按钮,添加到 提升的类 列表,然后点击【提升】按钮,完成提升操作。


提升完成之后,在 对象查看器 中,可见 Graphics View 控件的 已经变成了 DicomView


点击 Qt 设计师的【保存】按钮,对刚才的提升操作进行保存,然后退出 Qt 设计师。

界面交互

与图像相关的交互动作,都已经封装到 DicomView 类里了,剩余的交互动作还有:

  • 点击【打开文件夹】按钮,读取文件。
  • 双击序列列表项,打开图像。

另外,还有读取 DICOM 文件过程中,两个与界面相关的操作:

  • 读取文件过程中,更新进度条。
  • 读取文件完成后,打开图像。

由于界面 UI 定义在 DcmtkDemo 类中,所以界面交互代码自然而然也定义在 DcmtkDemo 类中。

DcmtkDemo.h 文件中添加四个槽函数,对应上面四个动作:

void on_btnOpenFolder_clicked();
void on_lstSeriesList_itemDoubleClicked(QListWidgetItem* item);
void setProgressBarValue(int);
void readDicomFilesCompleted();

DcmtkDemo.cpp 文件中添加这四个函数的定义:

void DcmtkDemo::on_btnOpenFolder_clicked()
{QSettings setting("blackwood-cliff", "DcmtkDemo");  //为了记住上一次的路径QString dirStr = setting.value("OPEN_FOLDER", ".").toString();   //不存在的话为当前应用程序路径dirStr = QFileDialog::getExistingDirectory(this, QStringLiteral("打开文件夹"), dirStr);if (dirStr.isEmpty())return;setting.setValue("OPEN_FOLDER", dirStr);   //记住该路径,以备下次使用ui.btnOpenFolder->setEnabled(false);ui.progressBar->setVisible(true);ui.progressBar->setValue(0);ReadWorker *worker = new ReadWorker(dirStr);QThread *thread = new QThread();connect(thread, SIGNAL(started()), worker, SLOT(readDicomFiles()));   //线程开始后执行worker->readDicomFiles()connect(worker, SIGNAL(progress(int)), this, SLOT(setProgressBarValue(int)));   //worker 发送信号,执行this->setProgressBarValueconnect(worker, SIGNAL(finish()), this, SLOT(readDicomFilesCompleted()));   //读取完毕,执行this->readDicomFilesCompleted()connect(worker, SIGNAL(finish()), worker, SLOT(deleteLater()));       //执行完成,析构workerconnect(worker, SIGNAL(destroyed(QObject*)), thread, SLOT(quit()));   //析构worker 完成, 推出线程connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));     //退出线程, 析构线程worker->moveToThread(thread);   //把worker移动到线程thread->start();                //开始线程ui.lstSeriesList->clear();
}
void DcmtkDemo::on_lstSeriesList_itemDoubleClicked(QListWidgetItem* item)
{ui.graphicsView->loadSeries(item->text());
}
void DcmtkDemo::setProgressBarValue(int progress)
{ui.progressBar->setValue(progress);
}
void DcmtkDemo::readDicomFilesCompleted()
{ui.btnOpenFolder->setEnabled(true);ui.progressBar->setVisible(false);if (SeriesData::SeriesList.size() == 0){QMessageBox::warning(this, "读取完毕", "没有发现 DICOM 文件。");return;}for (size_t i = 0; i < SeriesData::SeriesList.size(); i++){ui.lstSeriesList->addItem(SeriesData::SeriesList.at(i)->getSeriesUID());}//默认显示第一个序列的图像ui.graphicsView->loadSeries(SeriesData::SeriesList.at(0)->getSeriesUID());
}

编译程序

编译之前,应首先设置程序字符集为 使用多字节字符集,否则会出现下面三个错误:

  • C2665 “dcmtk::log4cplus::Logger::getInstance”: 2 个重载中没有一个可以转换所有参数类型
  • C2678 二进制“+”: 没有找到接受“const wchar_t [8]”类型的左操作数的运算符(或没有可接受的转换)
  • C2664 “void dcmtk::log4cplus::Logger::forcedLog(const dcmtk::log4cplus::spi::InternalLoggingEvent &) const”: 无法将参数 3 从“int”转换为“const char *”

DcmtkDemo 项目名称上点击鼠标右键,在弹出菜单上选择 [属性],打开项目属性页对话框。选择[配置属性 > 常规],设置[字符集]为 使用多字节字符集


一般添加 include 目录时,会同时把 lib 文件添加上。由于本文前面添加 DCMTK include 目录时,没有添加 lib 文件,故此时必须先添加 lib 文件,否则编辑时会出现一大堆链接错误(LNK2001 和 LNK2019)。

添加 lib 文件时,一定要注意项目的 平台配置 要与引入的 DCMTK lib 文件保持一致。由于《编译 DCMTK DLL(DCMTK 3.6.4 + VS2015 + Win10)》一文里选择的是 x64 平台,所以本项目也必须选择 x64 平台。至于项目配置,如果编译 Debug 版本程序,就要选择 Debug 版 DCMTK,如果编译 Release 版本程序,就要选择 Release 版 DCMTK。

DcmtkDemo 项目名称上点击鼠标右键,在弹出菜单上选择 [属性],打开项目属性页对话框。选择[配置属性 > 链接器 > 输入],在[附加依赖项]里添加 D:\dcmtk-3.6.4-install\lib\*.lib


DcmtkDemo 项目名称上点击鼠标右键,在弹出菜单上选择 [生成],正常会出现生成成功的提示。

========== 生成: 成功 1 个,失败 0 个,最新 0 个,跳过 0 个 ==========

由于每个电脑的环境不同,也许会出现 LNK1104 无法打开文件“shell32.lib” 的错误。
如果出现这个错误,那一定是找不到 shell32.lib 文件了。shell32.lib 文件包含在 Windows SDK 里。Windows SDK 一般安装在 C:\Program Files (x86)\Windows Kits\10\Lib 文件夹里。如果电脑上安装了多个版本的 Windows SDK 的话,在 C:\Program Files (x86)\Windows Kits\10\Lib 文件夹里,会有多个子文件夹。在 C:\Program Files (x86)\Windows Kits\10\Lib 文件夹里,搜索 shell32.lib 文件,找到之后,记下子文件夹名称,一般是 Windows 10 SDK 的版本号,如 10.0.10586.0

记下 Windows 10 SDK 的版本号后,打开项目属性页对话框,选择[配置属性 > 常规],将[目标平台版本]设置为刚才记下的版本号,点击【确定】关闭对话框,保存程序之后重新生成。

也可以把 目标平台版本 下拉列表里的各项逐个试试,看看哪个能够编译通过。

运行程序

F5Ctrl + F5 运行程序,会提示找不到 DLL 文件。

把需要的 DLL 从 D:\dcmtk-3.6.4-install\bin 下面复制到 D:\DcmtkDemo\x64\Debug 里,然后重新运行程序。

程序最终界面如下:


上面是在 Visual Studio 里运行的,正常情况下我们应该是直接运行 .exe 文件。
如果直接在 D:\DcmtkDemo\x64\Debug 下面运行 DcmtkDemo.exe 文件,会提示找不到 Qt DLL。

我们可以像上面复制 DCMTK DLL 一样,把 Qt DLL 从 C:\Qt\Qt5.14.2\5.14.2\msvc2015_64\bin 文件夹复制到 D:\DcmtkDemo\x64\Debug 里,然后重新运行 DcmtkDemo.exe 文件。但此时会出现下面的错误提示:


可见直接复制 Qt DLL 不是个好办法。

其实对于这个问题,Qt 已经提供了 Qt Windows Deployment Tool

打开 命令提示符 窗口,进入 C:\Qt\Qt5.14.2\5.14.2\msvc2015_64\bin 文件夹,执行 windeployqt.exe D:\DcmtkDemo\x64\Debug\DcmtkDemo.exe 命令。


windeployqt.exe 命令执行完毕后,打开 D:\DcmtkDemo\x64\Debug 文件夹,可见文件夹里不仅增加了几个 DLL 文件,还多了一些子目录。再次运行 DcmtkDemo.exe 文件,程序正常启动。

源码下载

  • 下载程序源码:https://download.csdn.net/download/blackwoodcliff/13189854
  • 下载 DCMTK DLL:https://download.csdn.net/download/blackwoodcliff/13187054
  • 下载 DICOM 图像:https://download.csdn.net/download/blackwoodcliff/13193834
  • 更多 DICOM 图像:https://support.dcmtk.org/redmine/projects/dcmtk/wiki/DICOM_Images

参考

  • DCMTK(MD版)、QT、VS2015编写Dicom序列浏览应用程序-新建项目,配置环境
  • 第二章 基于QT和DCMTK的Dicom 图像浏览器—界面设计
  • 第三章 基于QT和DCMTK的Dicom 图像浏览器—单个Dicom图像读取类
  • 第四章 基于QT和DCMTK的Dicom 图像浏览器—检查文件夹下Dicom序列个数
  • 第五章 基于QT和DCMTK的Dicom 图像浏览器—Dicom图像序列类
  • 第六章 基于QT和DCMTK的Dicom 图像浏览器—Dicom视图类
  • 解决This application failed to start because no Qt platform plugin could be initialized问题

第一个 DCMTK 程序:显示 DICOM 图像(DCMTK 3.6.4 + Qt 5.14.2 + VS2015)相关推荐

  1. DCMTK:读取DICOM图像,添加一个Curve并将其写回

    DCMTK:读取DICOM图像,添加一个Curve并将其写回 读取DICOM图像,添加一个Curve并将其写回 读取DICOM图像,添加一个Curve并将其写回 #include "dcmt ...

  2. DCMTK:缩放DICOM图像

    DCMTK:缩放DICOM图像 缩放DICOM图像 缩放DICOM图像 #include "dcmtk/config/osconfig.h" #include "dcmt ...

  3. DCMTK:读取DICOM图像并创建匹配的演示状态

    DCMTK:读取DICOM图像并创建匹配的演示状态 读取DICOM图像并创建匹配的演示状态 读取DICOM图像并创建匹配的演示状态 #include "dcmtk/config/osconf ...

  4. DCMTK:读取DICOM图像,并使用设置创建PGM位图

    DCMTK:读取DICOM图像,并使用设置创建PGM位图 读取DICOM图像,并使用设置创建PGM位图 读取DICOM图像,并使用设置创建PGM位图 #include "dcmtk/conf ...

  5. DCMTK:读取DICOM图像,添加模态LUT并将其写回

    DCMTK:读取DICOM图像,添加模态LUT并将其写回 读取DICOM图像,添加模态LUT并将其写回 读取DICOM图像,添加模态LUT并将其写回 #include "dcmtk/conf ...

  6. DCMTK:比较DICOM图像并计算差异指标

    DCMTK:比较DICOM图像并计算差异指标 比较DICOM图像并计算差异指标 比较DICOM图像并计算差异指标 #include "dcmtk/config/osconfig.h" ...

  7. 用java编写日历添加窗口一角_Java 实训4 编写一个窗体程序显示日历

    实训要求: 1.使用BorderLayout 进行总体布局 2.在North 位置放置包含两个按钮( 上月和下月)的Panel 3.在South 位置放置一个Label 用于显示当前年份和月份 4.在 ...

  8. MATLAB读取显示DICOM图像

    查询资料总结了下 str='文件名'; Image_dicom=dicomread(str); figure;imshow(Image_dicom, 'DisplayRange',[]); title ...

  9. linux系统 qt开发,老板让我写一个Windows程序,结果我在Linux下用Qt开发

    在Linux下面编译出了错的话怎么办呢?再"搬"回Windows来修改然后再搬到Linux下编译,周而复始? 我在Linux下基本能把程序调通,因为一开始用Qt就是因为它是跨平台的 ...

  10. java编写一个邮件程序显示收件箱中所有的邮件列表_如何使用JavaMail获取邮件帐户中的可用文件夹列表...

    这里是工作的代码.这将让你处理所有的标签.要在文件夹中更深入,可以执行folder.list()或者可以使用store.getDefaultFolder().list("*")来检 ...

最新文章

  1. 从代码设计到应用开发,入坑深度学习看这本书就够了
  2. 爬虫的另一工具splash挑战selenium
  3. MySQL Transaction--快照读和当前读
  4. python面向对象之类的成员
  5. HTML5笔记——formData
  6. 前端小知识点(4):JS 运行机制和存储
  7. idea 使用sonarlint报错解决方案
  8. 链式向前星(一个优秀的存储图的数据结构)
  9. c语言二维数组错误语法,关于c语言动态分配二维数组free的错误求dalao看看怎么回事谢谢啊~~~~...
  10. GB/T 4754-2017 国民经济行业分类
  11. 【竞赛篇-申报平台】浙江省国创(大创)平台里导出的word显示不出图片、右边界溢出页面,图片空间不足的解决办法
  12. excel排序求和:如何统计前几名数据合计 下篇
  13. 为什么软件系统上云是大势所趋?
  14. UVa 12112 - Iceman
  15. oracle 存储过程误删,Oracle中怎么恢复被删掉的存储过程
  16. net view 时报错 发生系统错误 6118 解决
  17. 基于NETAPP实现内网穿透
  18. 关于λ-optimal的初始化解算法在(元)启发式中的应用
  19. 监听浏览器刷新/关闭页面
  20. 免费好用的mac字幕添加软件:ArcTime Pro中文免费版

热门文章

  1. 堪比熊猫烧香!中国新型蠕虫病毒大爆发!电脑瞬间报废
  2. 计算机毕业设计-网上购书系统【代码讲解+安装调试+文档指导】
  3. 黑苹果日记三(驱动)
  4. Wker_SQLTool注入工具(附源码)
  5. 计算机网络协议名词解析,网络协议名词解释
  6. 【原】豆瓣电台桌面版——Win7任务栏支持+托盘+快捷键+历史记录
  7. Python Twisted 简介
  8. 服务器 虚拟软驱,virtual floppy(虚拟软驱)命令行版本
  9. unityShader物体表面流光效果
  10. 阿帕奇服务器配置文件,阿帕奇服务器基本参数配置