第一个 DCMTK 程序:显示 DICOM 图像(DCMTK 3.6.4 + Qt 5.14.2 + VS2015)
文章目录
- 开发环境
- 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 Layout
和 Graphics View
,然后点击工具条上的【使用分裂器水平布局】按钮。如下图:
然后点击窗体空白处,再点击工具条上的【水平布局】按钮,使控件充满整个窗体。如下图:
再依次向 Vertical Layout
里拖拽 Push Button
、List Widget
、Progress Bar
三个控件,从上到下顺序排列。
- 设置
Push Button
的objectName
为btnOpenFolder
,text
为打开文件夹
。 - 设置
List Widget
的objectName
为lstSeriesList
。 - 设置
Progress Bar
的value
为0
,取消勾选textVisible
。 - 选中
Vertical Layout
,点击右键,选择菜单【变型为 > QFrame】,设置minimumSize
宽度为300
,maximumSize
宽度为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 ¢er, double &width) const{center = _winCenter; width = _winWidth;}//设置窗宽窗位void setWindow(const double ¢er, 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 ¢er, 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 class 为 QGraphicsView
,修改 Constructor signature 为 QWidget *parent
,点击【Finish】按钮,添加 DicomView
类。
DicomView
类继承自 QGraphicsView
,包含如下控件:
- 一个
QGraphicsPixmapItem
,用于显示图像。 - 两个
QGraphicsSimpleTextItem
,用于显示图像窗宽窗位和当前图像索引。 - 一个
QSlider
,在图像上显示一个滑动条,滑块指示当前图像索引位置,拖拽滑块可切换图像。
另外,重写了 QGraphicsView
的 resizeEvent
和 wheelEvent
两个函数。
重写 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 的版本号后,打开项目属性页对话框,选择[配置属性 > 常规],将[目标平台版本]设置为刚才记下的版本号,点击【确定】关闭对话框,保存程序之后重新生成。
也可以把 目标平台版本 下拉列表里的各项逐个试试,看看哪个能够编译通过。
运行程序
按 F5
或 Ctrl + 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)相关推荐
- DCMTK:读取DICOM图像,添加一个Curve并将其写回
DCMTK:读取DICOM图像,添加一个Curve并将其写回 读取DICOM图像,添加一个Curve并将其写回 读取DICOM图像,添加一个Curve并将其写回 #include "dcmt ...
- DCMTK:缩放DICOM图像
DCMTK:缩放DICOM图像 缩放DICOM图像 缩放DICOM图像 #include "dcmtk/config/osconfig.h" #include "dcmt ...
- DCMTK:读取DICOM图像并创建匹配的演示状态
DCMTK:读取DICOM图像并创建匹配的演示状态 读取DICOM图像并创建匹配的演示状态 读取DICOM图像并创建匹配的演示状态 #include "dcmtk/config/osconf ...
- DCMTK:读取DICOM图像,并使用设置创建PGM位图
DCMTK:读取DICOM图像,并使用设置创建PGM位图 读取DICOM图像,并使用设置创建PGM位图 读取DICOM图像,并使用设置创建PGM位图 #include "dcmtk/conf ...
- DCMTK:读取DICOM图像,添加模态LUT并将其写回
DCMTK:读取DICOM图像,添加模态LUT并将其写回 读取DICOM图像,添加模态LUT并将其写回 读取DICOM图像,添加模态LUT并将其写回 #include "dcmtk/conf ...
- DCMTK:比较DICOM图像并计算差异指标
DCMTK:比较DICOM图像并计算差异指标 比较DICOM图像并计算差异指标 比较DICOM图像并计算差异指标 #include "dcmtk/config/osconfig.h" ...
- 用java编写日历添加窗口一角_Java 实训4 编写一个窗体程序显示日历
实训要求: 1.使用BorderLayout 进行总体布局 2.在North 位置放置包含两个按钮( 上月和下月)的Panel 3.在South 位置放置一个Label 用于显示当前年份和月份 4.在 ...
- MATLAB读取显示DICOM图像
查询资料总结了下 str='文件名'; Image_dicom=dicomread(str); figure;imshow(Image_dicom, 'DisplayRange',[]); title ...
- linux系统 qt开发,老板让我写一个Windows程序,结果我在Linux下用Qt开发
在Linux下面编译出了错的话怎么办呢?再"搬"回Windows来修改然后再搬到Linux下编译,周而复始? 我在Linux下基本能把程序调通,因为一开始用Qt就是因为它是跨平台的 ...
- java编写一个邮件程序显示收件箱中所有的邮件列表_如何使用JavaMail获取邮件帐户中的可用文件夹列表...
这里是工作的代码.这将让你处理所有的标签.要在文件夹中更深入,可以执行folder.list()或者可以使用store.getDefaultFolder().list("*")来检 ...
最新文章
- 从代码设计到应用开发,入坑深度学习看这本书就够了
- 爬虫的另一工具splash挑战selenium
- MySQL Transaction--快照读和当前读
- python面向对象之类的成员
- HTML5笔记——formData
- 前端小知识点(4):JS 运行机制和存储
- idea 使用sonarlint报错解决方案
- 链式向前星(一个优秀的存储图的数据结构)
- c语言二维数组错误语法,关于c语言动态分配二维数组free的错误求dalao看看怎么回事谢谢啊~~~~...
- GB/T 4754-2017 国民经济行业分类
- 【竞赛篇-申报平台】浙江省国创(大创)平台里导出的word显示不出图片、右边界溢出页面,图片空间不足的解决办法
- excel排序求和:如何统计前几名数据合计 下篇
- 为什么软件系统上云是大势所趋?
- UVa 12112 - Iceman
- oracle 存储过程误删,Oracle中怎么恢复被删掉的存储过程
- net view 时报错 发生系统错误 6118 解决
- 基于NETAPP实现内网穿透
- 关于λ-optimal的初始化解算法在(元)启发式中的应用
- 监听浏览器刷新/关闭页面
- 免费好用的mac字幕添加软件:ArcTime Pro中文免费版
热门文章
- 堪比熊猫烧香!中国新型蠕虫病毒大爆发!电脑瞬间报废
- 计算机毕业设计-网上购书系统【代码讲解+安装调试+文档指导】
- 黑苹果日记三(驱动)
- Wker_SQLTool注入工具(附源码)
- 计算机网络协议名词解析,网络协议名词解释
- 【原】豆瓣电台桌面版——Win7任务栏支持+托盘+快捷键+历史记录
- Python Twisted 简介
- 服务器 虚拟软驱,virtual floppy(虚拟软驱)命令行版本
- unityShader物体表面流光效果
- 阿帕奇服务器配置文件,阿帕奇服务器基本参数配置