目录:
第一章 《DCMTK(MD版)、QT、VS2015编写Dicom序列浏览应用程序-新建项目,配置环境》
第二章 《第二章 基于QT和DCMTK的Dicom 图像浏览器---界面设计》
第三章 《 基于QT和DCMTK的Dicom 图像浏览器---单个Dicom图像读取类》
第四章 《基于QT和DCMTK的Dicom 图像浏览器---检查文件夹下Dicom序列个数》
第五章 《基于QT和DCMTK的Dicom 图像浏览器---Dicom图像序列类》
第六章 《基于QT和DCMTK的Dicom 图像浏览器---Dicom视图类》
第七章 《基于QT和DCMTK的Dicom 图像浏览器---收尾》
一、 描述
series UID 是区别每个序列的标识:同Dicom序列的series UID 一样,不同序列的series UID不一样。
instanceNumber是序列内Dicom单文件的标识: instanceNumber决定了Dicom单文件在序列内的位置。
二、准备工作
编写mygobal.h
#pragma once
class QMainWindow;
struct Rad_G {
static QMainWindow *m_mainWin;
};
编写mygobal.cpp
#include "myGobal.h"
#include <QMainWindow>
QMainWindow *Rad_G::m_mainWin = 0;
修改 DicomBrowse.cpp
#include "myGobal.h"
...
DicomBrowse::DicomBrowse(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
isOpeing = false;
Rad_G::m_mainWin = this; // 添加
}
...
修改Image.h
...
class DiPixel;
class Image{
public:
...
const DiPixel *getInternalPtr(); // 添加
...
}
修改Image.cpp
...
const DiPixel * Image::getInternalPtr()
{
return isNormal() ? dcmImage->getInterData() : nullptr;
}
...
修改ReadWorker.h
...
class ReadWorker : public QObject
{
public:
...
QString curUid;
public slots:
...
void readSeries();
...
}
修改ReadWorker.cpp
#include "SeriesBase.h"
...
void ReadWorker::readSeries()
{
// 读取指定的序列
if (!UID_Files.keys().contains(curUid))
{
emit finish();
return;
}
QStringList files = UID_Files[curUid];
Image *dicom = 0; // 单Dicom文件
int progressValue = 0;
foreach(QString fileName, files)
{
++progressValue;
emit progress(100 * progressValue / files.size()/* 转到0-100之间*/);
dicom = new Image(fileName);
if (!dicom->isNormal())
{
delete dicom; dicom = 0;
continue; // 读取不成功(不是Dicom文件),跳出直接读取下一个文件
}
SeriesBase::append(dicom);
}
emit finish();
}
...
三、序列基类
添加基类
修改类
#pragma once
#include <QMap>
#include <QRectF>
#include <QPixmap>
class Image;
class QSlider;
class SeriesBase
{
public:
enum ViewType
{
XY = 0,
XZ,
YZ
};
SeriesBase();
virtual ~SeriesBase();
QSlider *slider; // 控制切片位置的控件
static void readSeriesUid(QString &s); // 读取序列
static void append(Image*);
static bool isNormal(); // 读取完,检查是否是可用的dicoms
// 设置伪彩色和窗宽窗位
static void setColor(int color);
static void setWindow(const double ¢er, const double &width)
{
winCenter = center; winWidth = width;
}
static void setWindowDelta(const double &dCenter, const double &dWidth)
{
winCenter += dCenter; winWidth += dWidth;
}
static void setRoiWindow(const QRectF &rect); // 只对XY视图有效
static void setDefaultWindow()
{
winCenter = defCenter; winWidth = defWidth;
}
static void getWindow(double ¢er, double &width)
{
center = winCenter; width = winWidth;
}
static void getSeriesSize(int &w, int&h, int&s)
{
w = imageWidth; h = imageHeight; s = dicomsSize;
}
const ViewType getViewType()
{
return vt;
}
/*--虚函数--*/
virtual void nextFrame() = 0;
virtual void prevFrame() = 0;
virtual void gotoFrame(int index) = 0;
virtual bool getPixmap(QPixmap &pixmap) = 0;
virtual int getCurFrame() = 0;
virtual int getFrameCount() = 0;
virtual bool getRectSize(int &x_width, int &y_height) = 0;
virtual bool getPixSpacing(double &xs, double &ys) = 0; // 和视图比例有关
virtual QString getPixelValue(int x, int y) = 0;
virtual void update();
protected:
static QString UID; //序列唯一标识
static QMap<int, Image*> dicoms;// 序列
static const qint32** volume; // 三维体数据,对YZ、XZ视图有用
static int volumeSlices; // 三维体数据qint32*[]的个数,对YZ、XZ视图有用
static void createVolume();
static void deleteVolume();
static int curXYFrame;// 当前图像
static int curXZFrame;
static int curYZFrame;
ViewType vt; // 视图方式
static bool hasTreeView; // 彩色序列没有三视图
static double winCenter; // 窗位
static double winWidth; // 窗宽
static double defCenter;// 默认窗位窗宽
static double defWidth;
static int imageWidth;
static int imageHeight;
static int dicomsSize;
};
#include "SeriesBase.h"
#include "ReadWorker.h"
#include "Image.h"
#include "myGobal.h"
#include "dcmtk/dcmimgle/dcmimage.h"
#include <qmainwindow.h>
#include <QEventLoop>
#include <QThread>
#include <QObject>
#include <QSlider>
SeriesBase::SeriesBase()
{
SeriesBase::volume = 0;
volumeSlices = 0;
slider = new QSlider;
slider->setMinimum(0);
slider->setOrientation(Qt::Vertical);
slider->setAttribute(Qt::WA_StyledBackground, true);
slider->setStyleSheet("background-color: rgba(200,200, 200,0)");
}
SeriesBase::~SeriesBase()
{
qDeleteAll(dicoms.values());
dicoms.clear();
deleteVolume();
}
void SeriesBase::readSeriesUid(QString &uid)
{
if (UID == uid)
return;
UID = uid;
qDeleteAll(dicoms.values());
dicoms.clear();
deleteVolume();
SeriesBase::curXYFrame = 0;
SeriesBase::curXZFrame = 0;
SeriesBase::curYZFrame = 0;
ReadWorker *worker = new ReadWorker("");
worker->curUid = uid;
QThread *thread = new QThread();
QEventLoop loop;// 循环
QObject::connect(thread, SIGNAL(started()), worker, SLOT(readSeries())); // 线程开始后执行worker->readSeries()
QObject::connect(worker, SIGNAL(progress(int)), Rad_G::m_mainWin, SLOT(setProgressBarValue(int)));
QObject::connect(worker, SIGNAL(finish()), worker, SLOT(deleteLater()));// 执行完成,析构worker
QObject::connect(worker, SIGNAL(destroyed(QObject*)), thread, SLOT(quit()));// 析构worker 完成, 推出线程
QObject::connect(thread, SIGNAL(finished()), &loop, SLOT(quit())); // 退出线程, 退出循环
QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); // 退出线程, 析构线程
worker->moveToThread(thread); // 把worker 移动到线程
thread->start(); // 开始线程
loop.exec(); // 等待worker->readSeries()执行完成,退出循环
if (!isNormal())
return;
dicomsSize = dicoms.size();// 初始化dicomsSize ,即有多少序列
createVolume();//
}
void SeriesBase::append(Image *dicom)
{
if (dicom && dicom->isNormal()) {
if (UID != dicom->getSeriesUID())
{
delete dicom;dicom = 0;
return;
}
if (dicoms.isEmpty()) {
dicom->getWindow(winCenter, winWidth);// 初始化窗宽窗位
dicom->getWindow(defCenter, defWidth);// 初始化默认窗宽位
dicom->getImageSize(imageWidth, imageHeight);// 初始化图像宽高
}
dicoms.insert(dicom->getInstanceNumber(), dicom);// 把图像插入到对应的位置
}
}
bool SeriesBase::isNormal()
{
return !dicoms.isEmpty();
}
void SeriesBase::setColor(int color)
{
Image::setColor(color);
}
// 实现鼠标划框调整窗宽位
void SeriesBase::setRoiWindow(const QRectF & rect)
{
if (dicoms.isEmpty()) return;
Image *image = dicoms.values().at(curXYFrame);
image->setRoiWindow(rect);
image->getWindow(winCenter, winWidth);
}
void SeriesBase::createVolume()
{
dicoms.values().first()->isMonochrome() ? hasTreeView = true : hasTreeView = false;
if (!hasTreeView)
return;
volumeSlices = dicomsSize;
size_t length = imageWidth*imageHeight;
volume = new const qint32*[dicomsSize];
quint8 *utemp8 = 0;
qint8 *temp8 = 0;
quint16 *utemp16 = 0;
qint16 *temp16 = 0;
quint32 *utemp32 = 0;
qint32 *temp32 = 0;
qint32 *temp = 0;
for (int i = 0; i < dicomsSize; ++i)
{
const DiPixel *pixel = dicoms.values().at(i)->getInternalPtr();
EP_Representation rest = pixel->getRepresentation();
if (pixel->getCount() != length)
{
hasTreeView = false; volumeSlices = i;
return;
}
temp = new qint32[length];
volume[i] = temp;
switch (rest)
{
case EPR_Uint8:
utemp8 = (quint8*)pixel->getData();
for (int j = 0; j < length; ++j)
temp[i] = utemp8[i];
break;
case EPR_Sint8:
temp8 = (qint8*)pixel->getData();
for (int j = 0; j < length; ++j)
{
temp[j] = temp8[j];
}
break;
case EPR_Uint16:
utemp16 = (quint16*)pixel->getData();
for (int j = 0; j < length; ++j)
temp[j] = utemp16[j];
break;
case EPR_Sint16:
temp16 = (qint16*)pixel->getData();
for (int j = 0; j < length; ++j)
{
temp[j] = temp16[j];
}
break;
case EPR_Uint32:
utemp32 = (quint32*)pixel->getData();
for (int j = 0; j < length; ++j)
{
temp[j] = utemp32[j];
}
break;
case EPR_Sint32:
temp32 = (qint32*)pixel->getData();
for (int j = 0; j < length; ++j)
{
temp[j] = temp32[j];
}
break;
}
}
}
void SeriesBase::deleteVolume()
{
if (volume == 0)
return;
for (int i = 0; i < volumeSlices; ++i)
{
if (volume[i] != 0)
{
delete[] volume[i];
volume[i] = 0;
}
}
delete[] volume;
volume = 0;
volumeSlices = 0;
}
void SeriesBase::update()
{
if(slider)
slider->setMaximum(getFrameCount() - 1);
}
QString SeriesBase::UID; //序列唯一标识
QMap<int, Image*> SeriesBase::dicoms;// 序列
const qint32** SeriesBase::volume=0; // 三维体数据,对YZ、XZ视图有用
int SeriesBase::volumeSlices=0; // 三维体数据qint32*[]的个数,对YZ、XZ视图有用
int SeriesBase::curXYFrame=0;// 当前图像
int SeriesBase::curXZFrame=0;
int SeriesBase::curYZFrame=0;
bool SeriesBase::hasTreeView=false; // 彩色序列没有三视图
double SeriesBase::winCenter; // 窗位
double SeriesBase::winWidth; // 窗宽
double SeriesBase::defCenter;// 默认窗位窗宽
double SeriesBase::defWidth;
int SeriesBase::imageWidth = 0;
int SeriesBase::imageHeight = 0;
int SeriesBase::dicomsSize = 0;
四、XY面序列
XYSeries.h
#pragma once
#include "SeriesBase.h"
class XYSeries :
public SeriesBase
{
public:
XYSeries();
virtual ~XYSeries();
virtual void nextFrame();
virtual void prevFrame();
virtual void gotoFrame(int index);
virtual bool getPixmap(QPixmap &pixmap);
virtual int getCurFrame();
virtual int getFrameCount();
virtual bool getRectSize(int &x_width, int &y_height);
virtual bool getPixSpacing(double &xs, double &ys); // 和视图比例有关
virtual QString getPixelValue(int x, int y);
};
XYSeries.cpp
#include "XYSeries.h"
#include "Image.h"
#include <QSlider>
XYSeries::XYSeries():SeriesBase()
{
vt = SeriesBase::XY;
}
XYSeries::~XYSeries()
{
}
void XYSeries::nextFrame()
{
++curXYFrame;
if (curXYFrame >= dicomsSize)
curXYFrame = 0;
slider->setValue(curXYFrame);
}
void XYSeries::prevFrame()
{
--curXYFrame;
if (curXYFrame < 0)
curXYFrame = dicomsSize - 1;
slider->setValue(curXYFrame);
}
void XYSeries::gotoFrame(int index)
{
if (index < 0)
curXYFrame = 0;
else if (curXYFrame >= dicomsSize)
curXYFrame = dicomsSize - 1;
else curXYFrame = index;
slider->setValue(curXYFrame);
}
bool XYSeries::getPixmap(QPixmap & pixmap)
{
if (dicoms.isEmpty())
return false;
Image *image = dicoms.values().at(curXYFrame);
if (winWidth < 1) winWidth = 1;
image->setWindow(winCenter, winWidth);
return image->getPixmap(pixmap);
}
int XYSeries::getCurFrame()
{
return curXYFrame;
}
int XYSeries::getFrameCount()
{
return dicoms.size();
}
bool XYSeries::getRectSize(int & x_width, int & y_height)
{
if (isNormal())
{
x_width = imageWidth;
y_height = imageHeight;
return true;
}
return false;
}
bool XYSeries::getPixSpacing(double & xs, double & ys)
{
if (isNormal())
{
double zs;
return dicoms.values().first()->getPixSpacing(xs, ys, zs);
}
return false;
}
QString XYSeries::getPixelValue(int x, int y)
{
if (isNormal())
{
return dicoms.values().at(curXYFrame)->getPixInfo(x,y);
}
return "";
}
五、 XZ面序列
#pragma once
#include "SeriesBase.h"
class XZSeries :
public SeriesBase
{
public:
XZSeries();
virtual ~XZSeries();
virtual void nextFrame();
virtual void prevFrame();
virtual void gotoFrame(int index);
virtual bool getPixmap(QPixmap &pixmap);
virtual int getCurFrame();
virtual int getFrameCount();
virtual bool getRectSize(int &x_width, int &y_height);
virtual bool getPixSpacing(double &xs, double &ys); // 和视图比例有关
virtual QString getPixelValue(int x, int y);
};
#include "XZSeries.h"
#include "Image.h"
#include <QSlider>
XZSeries::XZSeries():SeriesBase()
{
vt = SeriesBase::XZ;
}
XZSeries::~XZSeries()
{
}
void XZSeries::nextFrame()
{
++curXZFrame;
curXZFrame >= imageHeight ? curXZFrame = 0 : curXZFrame = curXZFrame;
slider->setValue(curXZFrame);
}
void XZSeries::prevFrame()
{
--curXZFrame;
curXZFrame < 0 ? curXZFrame = imageHeight - 1 : curXZFrame = curXZFrame;
slider->setValue(curXZFrame);
}
void XZSeries::gotoFrame(int index)
{
index < 0 ? index = 0 : index = index;
index >= imageHeight ? index = imageHeight - 1 : index = index;
curXZFrame = index;
slider->setValue(curXZFrame);
}
bool XZSeries::getPixmap(QPixmap & pixmap)
{
if(!hasTreeView)
return false;
size_t length = imageWidth * dicomsSize;
uchar *pDIB = new uchar[length];
memset(pDIB, 0, sizeof(uchar)*length);
double center = winCenter;
double width = winWidth;
double factor = 255 / width;
double lower = center - width / 2;
/*>>>>>>>>>>>>>赋值《《《《《《《《*/
for (uint y = 0; y < dicomsSize; ++y) {
const qint32 *ptr = volume[y];
int idx = curXZFrame * imageWidth;
int bmpIdx = (dicomsSize - 1 - y) * imageWidth;
if (ptr) {
for (uint x = 0; x < imageWidth; ++x) {
qint32 val = ptr[idx + x];
if (val > lower + width) pDIB[bmpIdx + x] = 255;
else if (val > lower) pDIB[bmpIdx + x] = (val - lower)*factor;
}
}
}
bool res = Image::UcharArrayToPixmap(pDIB, imageWidth, dicomsSize, length, pixmap, 8);
delete pDIB;
return res;
}
int XZSeries::getCurFrame()
{
return curXZFrame;
}
int XZSeries::getFrameCount()
{
if (!hasTreeView)
return 0;
return imageHeight;
}
bool XZSeries::getRectSize(int & x_width, int & y_height)
{
if (isNormal() && hasTreeView)
{
x_width = imageWidth;
y_height = dicoms.size();
return true;
}
return false;
}
bool XZSeries::getPixSpacing(double & xs, double & ys)
{
if (isNormal() && hasTreeView)
{
double d;
return dicoms.values().first()->getPixSpacing(xs, d, ys);
}
return false;
}
QString XZSeries::getPixelValue(int x, int y)
{
if (isNormal()&&hasTreeView)
{
if (y < 0) y = 0;
if (y >= dicomsSize) y = dicomsSize - 1;
return dicoms.values().at(y)->getPixInfo(x, curXZFrame);
}
return "";
}
六、 YZ面序列
#pragma once
#include "SeriesBase.h"
class YZSeries :
public SeriesBase
{
public:
YZSeries();
virtual ~YZSeries();
virtual void nextFrame();
virtual void prevFrame();
virtual void gotoFrame(int index);
virtual bool getPixmap(QPixmap &pixmap);
virtual int getCurFrame();
virtual int getFrameCount();
virtual bool getRectSize(int &x_width, int &y_height);
virtual bool getPixSpacing(double &xs, double &ys); // 和视图比例有关
virtual QString getPixelValue(int x, int y);
};
#include "YZSeries.h"
#include <qslider.h>
#include "Image.h"
YZSeries::YZSeries():SeriesBase()
{
vt = SeriesBase::YZ;
}
YZSeries::~YZSeries()
{
}
void YZSeries::nextFrame()
{
++curYZFrame;
if (curYZFrame >= imageWidth)
curYZFrame = 0;
slider->setValue(curYZFrame);
}
void YZSeries::prevFrame()
{
--curYZFrame;
if (curYZFrame < 0)
curYZFrame = imageWidth - 1;
slider->setValue(curYZFrame);
}
void YZSeries::gotoFrame(int index)
{
index < 0 ? index = 0 : index = index;
index >= imageWidth ? index = imageWidth - 1 : index = index;
curYZFrame = index;
slider->setValue(curYZFrame);
}
bool YZSeries::getPixmap(QPixmap & pixmap)
{
if (!hasTreeView)
return false;
size_t size = imageHeight * dicomsSize;
uchar *pDIB = new uchar[size];
memset(pDIB, 0, sizeof(uchar)*size);
double center = winCenter;
double width = winWidth;
double factor = 255 / width;
double lower = center - width / 2;
/*>>>>>>>>>>>>>赋值《《《《《《《《*/
for (uint y = 0; y < dicomsSize; ++y) {
const qint32 *ptr = volume[y];
int bmpIdx = (dicomsSize - 1 - y) * imageHeight;
if (ptr) {
for (uint x = 0; x < imageHeight; ++x) {
qint32 val = ptr[x*imageWidth + curYZFrame];
if (val > lower + width) pDIB[bmpIdx + x] = 255;
else if (val > lower) pDIB[bmpIdx + x] = (val - lower)*factor;
}
}
}
bool res = Image::UcharArrayToPixmap(pDIB, imageHeight, dicomsSize, size, pixmap, 8);
delete pDIB;
return res;
}
int YZSeries::getCurFrame()
{
return curYZFrame;
}
int YZSeries::getFrameCount()
{
if (!hasTreeView)
return 0;
return imageWidth;
}
bool YZSeries::getRectSize(int & x_width, int & y_height)
{
if (isNormal() && hasTreeView)
{
x_width = imageHeight;
y_height = dicoms.size();
return true;
}
return false;
}
bool YZSeries::getPixSpacing(double & xs, double & ys)
{
if (isNormal() && hasTreeView)
{
double d;
return dicoms.values().first()->getPixSpacing(d, xs, ys);
}
return false;
}
QString YZSeries::getPixelValue(int x, int y)
{
if (isNormal() && hasTreeView)
{
if (y < 0) y = 0;
if (y >= dicomsSize) y = dicomsSize - 1;
return dicoms.values().at(y)->getPixInfo(curYZFrame, x);
}
return "";
}