第五章 基于QT和DCMTK的Dicom 图像浏览器---Dicom图像序列类

目录:

开始  《DCMTK(MD版)编译和安装+VS2015》

第一章 《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 &center, 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 &center, 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 "";
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值